sshnetworkingsecuritydevops

SSH tunnels: local, remote and dynamic port forwarding explained

How SSH tunnels work: local and remote forwarding, dynamic SOCKS proxy, real use cases and the options that avoid common mistakes.

August 25, 2026·8 min read

The ssh command hides three different network tools behind a single flag -L/-R/-D. With them you turn any SSH server into an encrypted bridge to private services: databases with no public exposure, internal panels, development APIs. It's the most underrated Swiss Army knife in the trade, and it requires installing nothing on the server — you already have it.

The mental model: a tunnel between two points

Every SSH port forwarding follows the same principle: your SSH client opens a listening port on one end, and everything entering it travels inside the encrypted SSH channel until exiting at the other end toward the indicated destination. The middle connection crosses the internet encrypted; only the local legs (your app → ssh; ssh → destination) are plaintext inside trusted machines.

Three possible directions, three modes:

Local forwarding (-L): bringing a remote service to you

The most used one. Typical scenario: PostgreSQL running on your VPS, listening on localhost only because it must never touch the internet. You need DBeaver from your laptop:

ssh -L 5433:localhost:5432 user@myserver.com

Syntax: -L <local_port>:<destination_as_seen_by_server>:<destination_port>. Now localhost:5433 on your machine IS the remote database. Connect any client to localhost:5433.

A detail confusing beginners: the destination resolves from the server's perspective. That localhost:5432 is the server's localhost. That's why you can reach services listening only on its loopback — and third-party destinations too (-L 8080:internal.intranet:80), where the server hops into its private network.

Add -N when you want no shell, just the tunnel, and -f for background:

ssh -fN -L 5433:localhost:5432 user@myserver.com

Remote forwarding (-R): exposing your local machine

The inverted direction: a port opened on the server routes back to your machine. Star use case: showing a client or external webhook what runs on your laptop.

# On your machine:
ssh -R 9000:localhost:3000 user@myserver.com

Now https://myserver.com:9000 (or behind proxy) serves your localhost:3000 — the Stripe webhook can hit it even though your laptop sits behind NAT. Modern alternatives exist (ngrok, Cloudflare Tunnel), but SSH does it free and third-party-free when you already own a VPS.

Two server-side settings make this work well: GatewayPorts yes if the port must listen on all interfaces (not just loopback), plus awareness that you're temporarily exposing your dev environment — close the tunnel when done.

Dynamic forwarding (-D): the SOCKS proxy

The most powerful mode: instead of mapping concrete ports, your SSH client opens a local SOCKS5 proxy routing any destination on demand:

ssh -D 1080 -C -N user@myserver.com

Configure browser or system to use SOCKS5 → localhost:1080 and your browsing traffic exits from the server. It's your improvised VPN for hostile networks (hotel, coworking): encrypted up to your VPS, exiting with its IP. Unlike previous modes you needn't know ports in advance — the tunnel decides per request.

Firefox allows per-browser proxy config (better hygiene than routing the whole system); also enable network.proxy.socks_remote_dns so DNS queries travel through the tunnel too, avoiding leaks.

Security and hygiene

Tunnels inherit SSH security — Ed25519 keys, no passwords, as covered in the keys guide — but add their own rules:

  • Fine-grained permissions on shared servers: AllowTcpForwarding in sshd_config can be limited or disabled per user when only some should tunnel.
  • Bind address: by default -L listens only on your loopback (correct). -L 0.0.0.0:5433:... would open the port to your LAN — do it consciously or not at all.
  • Keepalive for long connections: ServerAliveInterval 60 prevents NAT and firewalls killing idle tunnels.
  • Auto-reconnect: autossh (or a systemd service with Restart=always) keeps critical tunnels alive through outages.
# ~/.ssh/config
Host db-tunnel
  HostName myserver.com
  User miguel
  LocalForward 5433 localhost:5432
  ServerAliveInterval 60
  ExitOnForwardFailure yes

With that config, ssh -N db-tunnel and done: aliases turn kilometer-long commands into two words.

Everyday use cases

Reaching a Dockerized app's admin panel listening internally, debugging webhooks against local development, hopping a corporate firewall via your VPS, administering remote-office routers/printers without formal VPN, protecting management sessions while traveling. One pattern, twenty problems solved.

FAQ

SSH tunneling or VPN? To reach 2-3 specific services, SSH tunnel: zero infrastructure. For whole teams with many protocols and routes, a real VPN (WireGuard). They don't compete: different scales.

Why does my tunnel drop every few minutes? Almost always NAT/firewall closing idle connections. ServerAliveInterval 30-60 fixes it; aggressive networks may need 15.

Can UDP be tunneled? Not natively — SSH carries TCP. socat tricks on both ends can wrap UDP, but at that point mount WireGuard and move on.


Generate keys for your tunnels with our SSH Key Generator, right in your browser.

Try it without code

SSH Key Generator

Ed25519/RSA in your browser.

Open SSH Key Generator

Built by

Miguel Ángel Colorado Marin (MACM)

Full-Stack Developer · Guadalajara, España

I develop web apps, digital tools and full projects — from design to deployment.

Contact me