Back to blog

Port forwarding, and what it does not encrypt

September 5, 2026 8 min read Kubexer Team
SSHPort ForwardingTunnels

SSH port forwarding is one of those features that is easy to use and easy to misunderstand. The commands are short; the mental model of what is encrypted and who resolves what is where people go wrong. Here are the three kinds, and the two details that matter most.

Local forwarding (-L): bring something to you

A local forward opens a port on your machine and sends anything that connects to it through the SSH session, to be delivered by the SSH host.

ssh -L 5432:db-primary.internal:5432 deploy@bastion.example.com

Now localhost:5432 on your laptop behaves like port 5432 on db-primary.internal. Point a database client at localhost:5432 and it connects to a machine you have no route to.

Remote forwarding (-R): expose something to the far side

A remote forward is the mirror image: it opens a port on the SSH host and sends connections back down to your machine.

ssh -R 8080:localhost:3000 deploy@edge-01.example.com

A colleague on edge-01 hitting localhost:8080 reaches the dev server running on port 3000 on your laptop. This is how you show a work-in-progress to something that cannot reach your machine — and it is why GatewayPorts defaults to off on the server: without it, the forwarded port only listens on the SSH host's loopback rather than being open to its whole network.

Dynamic forwarding (-D): a SOCKS5 proxy

A dynamic forward does not pin a single destination. It opens a SOCKS5 proxy locally, and each connection through it tells the SSH host where to go.

ssh -D 1080 deploy@bastion.example.com

Configure a browser or a CLI tool to use socks5://localhost:1080 and its requests are dialled from the bastion. One rule instead of one rule per host — useful when you do not know in advance which internal addresses you will need.

The part people get wrong: who resolves the destination

In -L 5432:db-primary.internal:5432, the name db-primary.internal is resolved by the SSH host, not by you. Your machine never looks it up and never needs to. That single fact is what makes local forwarding work for databases you cannot reach: you are not asking your laptop to find the host, you are asking a machine that already can.

It also explains a common confusion. -L 5432:localhost:5432 does not mean your localhost — it means the SSH host's localhost. The destination is always interpreted from the far end.

The same applies to -D, where the SOCKS client passes the hostname along and the SSH host resolves it per connection. Traffic dialled through the proxy uses the remote resolver, which is usually the behaviour you want.

The honest caveat: the last hop is plain TCP

Forwarding is often described as "encrypting" the connection. That is true of the part that travels over SSH, and false of the last segment.

Trace a local forward end to end:

  1. Your client connects to localhost:5432 — a loopback connection on your own machine, never on a network.
  2. That traffic goes through the SSH session to the SSH host — encrypted.
  3. The SSH host terminates the encryption, opens an ordinary TCP connection to db-primary.internal:5432, and relays your bytes into it in the clear.

Step 3 is the part to be honest about. The SSH host has full plaintext visibility of what passes through, and the segment between the SSH host and the destination carries whatever the application itself protects — nothing more. If the destination protocol is unencrypted, that hop is unencrypted, and anything able to observe that network segment sees it.

Usually this is fine, because that last hop is a private network you are choosing to trust. But it is a trust decision, and it should be made deliberately. If the data must be protected all the way, the application protocol has to do it — for example TLS to the database, tunnelled inside the forward.

Combining with jump chains

A forward can run over a multi-hop chain. Each hop carries SSH inside SSH, so every intermediate link is encrypted and no bastion in the middle can read the payload. The structure is unchanged: everything up to the final hop is protected, and the last link — from the machine that terminates the chain to the destination — is plain TCP.

Put another way, adding hops changes how far the encryption reaches, not whether that final segment is encrypted. It never is.

Rules rather than commands

In Kubexer Terminal, forwards are saved rules rather than commands you retype: a kind (local, remote, dynamic), the ports, the destination, and the host — including a host behind a jump chain — with live throughput so you can see whether anything is actually flowing. A tunnel keeps running when you close the tab it was created from, because a forward is infrastructure for whatever you are doing, not part of one shell session.

Port forwarding is a Pro feature.