The terminal connects, the prompt appears, and everyone agrees the SSH access works. Then a database client on the laptop still cannot reach the private host, and the session is blamed. What was built was a shell, which is one of four quite different interfaces SSH can offer. Name the interface first, then test that the application actually uses it. The order matters, because each interface fails in its own way.

Name the interface before you compare anything

The OpenSSH client manual distinguishes four mechanisms that are easy to blur together. A shell or remote command executes on the SSH host. A local forward opens a listening socket on the client and makes the onward connection from the remote side. A remote forward does the reverse, listening on the server side. Dynamic forwarding exposes a SOCKS endpoint for programs that know how to use one.

  • Shell: the program runs on the remote machine, nothing listens locally.
  • Local forward: your laptop has a port, the remote host makes the outbound connection.
  • Remote forward: the remote side has a port, traffic comes back toward you.
  • Dynamic: a SOCKS proxy on the client, used only by proxy-aware applications.

None of these installs a route. A program that ignores proxy settings, or speaks UDP, or resolves names on its own, is unaffected by an active SSH session. That is not a bug in SSH; it is the scope of the interface you chose.

Local, remote and dynamic forwards differ in who listens

The distinction that decides most cases is who owns the listening socket. With a local forward, the client listens and the far end dials the destination, which is why the destination address is resolved from the server's point of view. With a remote forward, the server listens and the connection is carried back to your side, which is how you expose a local service without publishing it directly.

ssh -N -L 5432:db.internal.example:5432 [email protected]
ssh -N -D 1080 [email protected]
ss -ltnp | grep -E '5432|1080'

The first command binds port 5432 on the laptop and forwards it to the database as seen from the bastion. The second creates a SOCKS proxy on port 1080. The third command confirms which processes are actually listening, which is the only proof that the forward exists rather than having failed silently on a port conflict.

Dynamic forwarding deserves its own warning: it only covers applications that honour SOCKS. A browser can be pointed at it, a command-line tool can be told to use it, and a database driver with its own connection stack usually cannot. Assuming that every program on the machine now uses the tunnel is the most common mistake in this whole area.

Tunnel-device mode is the one real network exception

OpenSSH also supports -w tunnel-device forwarding, which passes a point-to-point interface rather than a TCP listener. That is a genuine network tunnel, and it is deliberately configured, not a side effect of opening a shell.

sudo ssh -w 0:0 [email protected]
ip addr show tun0

It needs the corresponding interface numbers available on both ends, the right configuration on the server, and enough privilege on the client to create the device. It also needs an addressing and routing plan, because a bare interface carries no policy about which destinations should use it.

Even then, the tunnel terminates at the far host. Whatever protection the SSH transport provided ends there, and the onward connection is a separate hop. A VPN gateway has the same shape of boundary, which is why the comparison should be about scope and termination rather than about which tool is stronger. Naming that boundary out loud also settles the argument about whether a tunnel "encrypts everything", because it plainly does not.

Test that your application uses the interface you built

Verification is per application, not per session. A working forward proves that the port is bound and the far side can dial; it does not prove that the program you care about connected through it.

curl -sS --socks5-hostname 127.0.0.1:1080 -o /dev/null -w '%{http_code}\n' https://app.internal.example/health
psql "host=127.0.0.1 port=5432 dbname=app sslmode=require" -c 'select 1'

The first command sends a request through the SOCKS endpoint with the hostname resolved remotely, and the second exercises the forwarded database port with TLS still required. Run the same tests with the forward stopped to confirm they fail, because a test that passes both ways was never using the tunnel.

Then compare with a routed VPN only on the dimensions that matter: which destinations are covered, which protocols work, where protection ends, and who has to approve the access. A single-service forward can be the narrower and better answer, while a VPN is the right shape when many applications need the same network path. Neither grants everything reachable from its far endpoint.

مۇناسىۋەتلىك ماقالىلەر