A Practical Guide to SSH Tunnels: Local and Remote Port Forwarding

1 min read
devopsself-hostingsshport-forwarding
View as Markdown
Originally from labs.iximiuz.com
View source

My notes

Summary

A hands-on reference for the four SSH tunneling modes (local -L, remote -R, dynamic -D, and remote-dynamic -R with no destination), each demonstrated against a 4-host / 3-network lab. The core value is the exact flag syntax plus a mental model for which side starts listening and which direction traffic flows.

Key Insight

  • Two mnemonics resolve all confusion: -L (local) = the SSH client opens the listening port; -R (remote) = the sshd server opens the listening port. Direction follows from who listens.
  • -L can target any machine, not just the SSH server. In ssh -L 8081:172.16.0.40:80 bastion, the forward target (172.16.0.40) and the SSH server (bastion) are different hosts, the bastion opens a second hop on your behalf. This is the classic “reach a VPC-private OpenSearch/DB via a public EC2 jump host” pattern.
  • -R has a hidden pitfall: by default the exposed port binds only to the gateway’s localhost, so it’s reachable only from inside the gateway. To expose to the public internet you must set GatewayPorts yes in the gateway’s sshd_config, then bind 0.0.0.0:port.
  • Dynamic forwarding (-D) beats N separate -L tunnels. One ssh -D 1080 bastion turns the client into a SOCKS proxy reaching every host behind the bastion; with -L you’d need one tunnel per destination. Client must speak SOCKS (curl --socks5-hostname localhost:1080 ...).
  • Remote dynamic forwarding (ssh -R port with no destination) mirrors -D: it makes the gateway a SOCKS proxy that tunnels back to the client, exposing an entire home/private network through one proxy. Needs OpenSSH 7.6+ on the client.
  • Background pattern: add -f -N to any of these (ssh -f -N -L ...) to run the tunnel detached with no remote shell.