SSH port forwarding explained with real examples
SSH port forwarding is one of those features most people learn about once and then forget, until they actually need to access a database behind a firewall or test a service running on a remote machine. Then it becomes useful enough that you use it regularly.
There are three types. They do different things and people mix them up.
Local port forwarding
Local forwarding takes traffic from a port on your local machine and sends it through the SSH connection to a destination the remote server can reach.
ssh -L 8080:localhost:3000 user@remote-server
This command opens port 8080 on your local machine. Any traffic you send to localhost:8080 gets forwarded through the SSH tunnel to port 3000 on the remote server.
A common use case: the remote server runs a web app on port 3000 that isn't exposed to the internet. You open the tunnel, then visit http://localhost:8080 in your browser and see the app.
Another use case: a database behind a firewall.
ssh -L 5432:db.internal:5432 user@bastion-host
This tunnels your local port 5432 through the bastion to a database server at db.internal. Your local database client connects to localhost:5432 and talks to the remote database without that database ever being exposed to the internet.
Remote port forwarding
Remote forwarding is the reverse. It takes traffic from a port on the remote server and forwards it to your local machine.
ssh -R 9090:localhost:3000 user@remote-server
Anyone connecting to port 9090 on the remote server gets routed to port 3000 on your local machine. This is how you can share a local development server with someone who can SSH into the same remote host, or how you temporarily expose a local service to the internet without a domain or static IP.
For remote forwarding to work between arbitrary external clients, the remote server needs GatewayPorts yes in its sshd config.
Dynamic port forwarding (SOCKS proxy)
Dynamic forwarding creates a SOCKS proxy on your local machine that routes all traffic through the SSH connection.
ssh -D 1080 user@remote-server
Then configure your browser or system to use localhost:1080 as a SOCKS5 proxy. All browser traffic goes through the remote server. This is how people browse through a server in a different location, or route traffic through a trusted network when on an untrusted one.
Keeping tunnels alive
If you are running a tunnel in the background and you want it to stay up, add -N (don't execute a remote command) and -f (go to background):
ssh -N -f -L 8080:localhost:3000 user@remote-server
To kill it later, find the process:
ps aux | grep ssh
kill <PID>
For persistent tunnels you rely on daily, look at autossh. It restarts the tunnel if the connection drops.
Port forwarding from the browser
If you connect to your server through SSHDock, port forwarding works through the jump host feature. For local port forwarding specifically, a browser-based client has limitations because the browser itself controls which ports it can bind to. For complex tunnel setups, a native client or bastion host architecture is usually the better path.