Blog
4 min read

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 open http://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 -tlnp on the server).
  • bind: Address already in use — the local port is taken. Use a different one.
  • Tunnel drops after a while — add ServerAliveInterval 60 to 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 -R and GatewayPorts — 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 than 0.0.0.0.

The summary

  • -L local:dest:port brings a server-side service to your localhost — the everyday tunnel.
  • -R remote:dest:port exposes your local service on the server.
  • -D port creates a SOCKS proxy through the server.
  • Use -fN for background tunnels, LocalForward in 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