SSH Port Forwarding Explained: Local, Remote, and Dynamic Tunnels
SSH tunnels let you reach a database or web app on a server as if it were on localhost — without opening ports to the internet. Local (-L), remote (-R) and dynamic (-D) forwarding with practical examples, background tunnels, and the security caveats.
Your database runs on a server and, sensibly, only accepts connections from that server itself. But you'd like to browse it with a GUI on your laptop. Or your app is running on the server's port 3000 and you want to see it in your browser without exposing it publicly. SSH port forwarding (SSH tunnelling) solves both: it carries a network connection through your existing, encrypted SSH connection.
Local forwarding (-L): bring a remote port to your machine
The one you'll use most.
ssh -L 5433:localhost:5432 you@your-server.com
Read it as: "listen on my port 5433; send anything that arrives there through SSH to the server, which connects it to localhost:5432" — localhost from the server's point of view.
Now, on your laptop, connect your database tool to localhost:5433, and you're talking to the Postgres on the server. (How to view your Postgres database.)
The general form:
-L [local_port]:[destination_host]:[destination_port]
The destination is resolved from the server, so it can also be another machine on the server's private network:
ssh -L 6380:redis.internal:6379 you@bastion.example.com
Common local-forwarding uses
- Databases — Postgres (5432), MySQL (3306), Redis (6379) that are only listening on the server.
- Web apps in development —
ssh -L 3000:localhost:3000 you@server, then openhttp://localhost:3000. (VS Code Remote SSH does this automatically — see VS Code Remote SSH.) - Admin dashboards you deliberately don't expose publicly.
Remote forwarding (-R): expose your local port to the server
The reverse direction:
ssh -R 8080:localhost:3000 you@your-server.com
"Listen on the server's port 8080; send anything arriving there back through SSH to my machine's localhost:3000."
Uses: letting a server reach something running on your laptop — for example, receiving webhooks on a local dev server via a public machine. By default the server only listens on its own localhost; making it public requires the GatewayPorts setting on the server, which you should enable only deliberately. (Dedicated tunnel services exist for exposing local apps to the internet, and are often simpler.)
Dynamic forwarding (-D): a SOCKS proxy
ssh -D 1080 you@your-server.com
Creates a SOCKS proxy on your port 1080. Configure a browser or tool to use it, and all its traffic goes out through the server. Useful for reaching several internal services at once, or for browsing as if you were on the server's network.
Useful flags
-N— don't open a shell; just hold the tunnel.-f— go to the background after connecting.-o ExitOnForwardFailure=yes— fail loudly if the port can't be forwarded (e.g. already in use).
A background tunnel:
ssh -fN -o ExitOnForwardFailure=yes -L 5433:localhost:5432 you@your-server.com
To stop it, find and kill the process (ps aux | grep ssh), or use a control socket. For a tunnel you use daily, put it in your SSH config with LocalForward, so ssh db-tunnel sets it up. (The SSH config file explained.)
Picking local ports
Choose a local port that isn't already in use — that's why the examples use 5433 rather than 5432 (in case Postgres is running on your laptop too). If you see "Address already in use", pick another. (Ports explained.)
Troubleshooting
channel 2: open failed: connect failed: Connection refused— the tunnel works, but nothing is listening at the destination. Check the service is running on the server and on which address/port (ss -tlnpon the server).bind: Address already in use— the local port is taken. Use a different one.- Tunnel drops after a while — add
ServerAliveInterval 60to your SSH config. - Forwarding refused by the server — the server's SSH config may disable forwarding (
AllowTcpForwarding no). That's the server admin's choice.
Security notes
- Tunnels are encrypted end to end between you and the SSH server — that's the point.
- Prefer tunnels over opening database ports to the internet. Exposed databases are scanned and attacked constantly.
- Anyone who can SSH to a server can usually forward ports through it. Restrict SSH access with keys and remove old keys you no longer use. (SSH keys explained.)
- Be careful with
-RandGatewayPorts— you're making your laptop reachable through the server. - On shared machines, other local users can connect to your forwarded local port. Bind explicitly to
127.0.0.1(the default) rather than0.0.0.0.
The summary
-L local:dest:portbrings a server-side service to yourlocalhost— the everyday tunnel.-R remote:dest:portexposes your local service on the server.-D portcreates a SOCKS proxy through the server.- Use
-fNfor background tunnels,LocalForwardin your config for regular ones. - Tunnels beat exposing database ports to the internet.
EasySpawn servers run your databases alongside your app and are reachable over SSH — so a one-line tunnel puts your production Postgres in your favourite GUI without exposing it to the internet. See how it works or join the waitlist.
Related: Reverse Proxies Explained · What Is localhost? · Container Networking Internals · Egress Control for AI Agents
Keep reading
The SSH Config File Explained: Stop Typing Long SSH Commands
~/.ssh/config turns ssh -i ~/.ssh/key -p 2222 deploy@203.0.113.10 into ssh prod. Where the file lives on each OS, the options worth knowing, multiple GitHub accounts, jump hosts, keep-alives, and the precedence rule that trips people up.
VS Code Remote SSH: Develop on a Remote Server as if It Were Local
The Remote - SSH extension lets VS Code edit files, run terminals and debug on a remote Linux server. How it works, setup step by step, SSH config and keys, extensions and port forwarding, and fixes for the common connection problems.