DEV Community

Cover image for SSH Tunneling: The Secret Superpower Most Developers Never Use
Mahafuzur Rahaman
Mahafuzur Rahaman

Posted on

SSH Tunneling: The Secret Superpower Most Developers Never Use

How a 30-year-old protocol lets you punch through firewalls, protect your traffic, and access anything, from anywhere


There's a feature baked into every SSH client on the planet that most developers use maybe once a year — if at all.

It's not glamorous. It doesn't have a flashy dashboard. But once you understand SSH tunneling, you'll wonder how you ever worked without it.

This article covers what SSH tunneling actually is, the three distinct types (and when to use each), real-world use cases that will make your day-to-day work easier, and the security implications you need to know.

No hand-waving. No "just trust me." Let's get into it.


What Is SSH Tunneling?

At its core, SSH tunneling — also called SSH port forwarding — is the act of routing arbitrary TCP traffic through an encrypted SSH connection.

You already know SSH as the thing you use to get a remote shell. But SSH isn't just a shell protocol. It's a full-blown encrypted transport layer. Once an SSH connection is established, you can piggyback other connections through it. Those connections inherit all the encryption and authentication of the parent SSH session.

Think of it like this: you've dug a secure, encrypted tunnel between two machines. Instead of just letting your terminal session run through it, you can route any network traffic through that same tunnel — database connections, web traffic, API calls, whatever you need.

The result: traffic that would otherwise be blocked, unencrypted, or exposed becomes secure and accessible.


The Three Types of SSH Tunnels

1. Local Port Forwarding (-L)

The most common type. Forwards a port on your local machine to a destination through the remote server.

ssh -L [local_port]:[destination_host]:[destination_port] [user]@[ssh_server]
Enter fullscreen mode Exit fullscreen mode

Example:

ssh -L 5432:db.internal:5432 ubuntu@bastion.example.com
Enter fullscreen mode Exit fullscreen mode

What this does:

  • Opens port 5432 on your local machine
  • Any connection to localhost:5432 gets forwarded through bastion.example.com
  • Which then connects to db.internal:5432 (a host only the bastion can reach)

Your local machine acts as if the remote database is running locally. You can now run psql localhost:5432 from your laptop, even though the database is behind a private network.

When to use it:

  • Accessing databases behind a firewall or bastion host
  • Connecting to internal admin UIs (Kibana, Grafana, Kubernetes dashboard)
  • Reaching development servers on a private subnet
  • Bypassing network restrictions on corporate networks

2. Remote Port Forwarding (-R)

The reverse. Forwards a port on the remote server back to a destination accessible from your local machine.

ssh -R [remote_port]:[destination_host]:[destination_port] [user]@[ssh_server]
Enter fullscreen mode Exit fullscreen mode

Example:

ssh -R 8080:localhost:3000 ubuntu@myserver.com
Enter fullscreen mode Exit fullscreen mode

What this does:

  • Opens port 8080 on myserver.com
  • Any connection to myserver.com:8080 gets forwarded back through the SSH tunnel
  • Which then connects to localhost:3000 on your machine

You've just exposed your local development server to the internet through a remote server — without configuring your router, opening firewall ports, or touching NAT settings.

When to use it:

  • Sharing a local dev server with a client or teammate
  • Receiving webhooks (Stripe, GitHub, Slack) to a local server
  • Giving remote access to a machine that's behind NAT
  • Exposing local services temporarily without deploying

Note: By default, OpenSSH binds remote forwarded ports to 127.0.0.1 only. To bind to all interfaces and make the port publicly reachable, add GatewayPorts yes to /etc/ssh/sshd_config on the remote server.


3. Dynamic Port Forwarding (-D)

Creates a SOCKS proxy. Instead of forwarding one port to one destination, all traffic through the proxy is routed via the SSH server — to any destination.

ssh -D [local_port] [user]@[ssh_server]
Enter fullscreen mode Exit fullscreen mode

Example:

ssh -D 9999 ubuntu@myserver.com
Enter fullscreen mode Exit fullscreen mode

What this does:

  • Opens a SOCKS5 proxy on your local machine at port 9999
  • Any application configured to use this proxy routes its traffic through myserver.com
  • From the outside world's perspective, the traffic originates from the server, not you

Configure your browser to use SOCKS5 localhost:9999, and all your browsing traffic flows through the remote server's network.

When to use it:

  • Browsing securely on untrusted public Wi-Fi
  • Accessing geo-restricted content via a server in another region
  • Routing specific application traffic through a known-safe exit point
  • Quick VPN-like functionality without setting up a VPN

Real-World Use Cases

Access a Private Database Without a VPN

This is probably the most common use case in modern cloud infrastructure.

Your database lives in a private subnet — it has no public IP. To access it, you'd normally need a VPN connection to the VPC, which means IT tickets, credentials, and waiting.

Instead:

ssh -L 5432:rds.internal.example.com:5432 -N ubuntu@bastion.example.com
Enter fullscreen mode Exit fullscreen mode

The -N flag tells SSH not to open a shell — just hold the tunnel open. Now connect your database GUI or CLI to localhost:5432 and you're in.

Works for PostgreSQL, MySQL, Redis, MongoDB — anything TCP.


Share Your Local Dev Server Instantly

Your designer needs to see the feature you're building. They're on the other side of the country. You don't want to deploy it yet.

ssh -R 8080:localhost:3000 ubuntu@yourserver.com -N
Enter fullscreen mode Exit fullscreen mode

Send them http://yourserver.com:8080. They see exactly what's running on your machine, live. No ngrok account needed, no tunneling service, no cost.


Receive Webhooks Locally

You're building a Stripe integration. You need webhooks to hit your local server at localhost:3000. Stripe can't reach that.

ssh -R 0.0.0.0:4567:localhost:3000 ubuntu@yourserver.com -N
Enter fullscreen mode Exit fullscreen mode

Point your Stripe webhook URL at http://yourserver.com:4567. Requests flow through the SSH tunnel directly to your local dev server.


Secure Your Traffic on Public Wi-Fi

At a coffee shop, airport, or hotel? The Wi-Fi is untrusted and potentially monitored.

ssh -D 9999 ubuntu@myserver.com -N -f
Enter fullscreen mode Exit fullscreen mode

The -f flag backgrounds the process. Configure your browser or system proxy to use SOCKS5 localhost:9999. All traffic is now encrypted through your SSH tunnel.


Access an Internal Web UI Without Touching VPN

Your Kubernetes dashboard, Grafana instance, or Elasticsearch UI runs on a private cluster. You don't want to expose it publicly, but you need access now.

ssh -L 9200:elasticsearch.internal:9200 \
    -L 5601:kibana.internal:5601 \
    -L 3000:grafana.internal:3000 \
    -N ubuntu@bastion.example.com
Enter fullscreen mode Exit fullscreen mode

One SSH command, three tunnels. Open localhost:5601 in your browser and Kibana loads — served privately from your internal cluster.


Keeping Tunnels Alive

By default, SSH connections drop when idle. For persistent tunnels, use these options:

ssh -L 5432:db:5432 \
    -N \
    -o ServerAliveInterval=60 \
    -o ServerAliveCountMax=3 \
    -o ExitOnForwardFailure=yes \
    ubuntu@bastion.example.com
Enter fullscreen mode Exit fullscreen mode
  • ServerAliveInterval=60 — sends a keepalive every 60 seconds
  • ServerAliveCountMax=3 — drops after 3 missed keepalives
  • ExitOnForwardFailure=yes — exits cleanly if the port can't be forwarded

For tunnels you always want running, use autossh:

sudo apt install autossh

autossh -M 0 -N \
    -o "ServerAliveInterval 60" \
    -o "ServerAliveCountMax 3" \
    -L 5432:db.internal:5432 \
    ubuntu@bastion.example.com
Enter fullscreen mode Exit fullscreen mode

autossh monitors the connection and automatically restarts the tunnel if it drops.


Cleaner Tunnels With ~/.ssh/config

Stop typing long commands. Put your tunnels in ~/.ssh/config:

Host db-tunnel
    HostName bastion.example.com
    User ubuntu
    IdentityFile ~/.ssh/id_ed25519
    LocalForward 5432 db.internal:5432
    LocalForward 6379 redis.internal:6379
    ServerAliveInterval 60
    ExitOnForwardFailure yes
Enter fullscreen mode Exit fullscreen mode

Now this is all you need:

ssh -N db-tunnel
Enter fullscreen mode Exit fullscreen mode

Security Considerations

SSH tunneling is powerful — which also means it can be misused or misconfigured. Here's what to keep in mind:

Tunnels bypass network controls

A tunnel routes traffic around firewalls, IDS systems, and network monitoring. In a corporate environment, this may violate policy. On your own infrastructure, it means a compromised SSH key could expose services you assumed were protected by network segmentation.

Restrict what can be forwarded on the server

In /etc/ssh/sshd_config on your SSH servers:

AllowTcpForwarding local    # Only allow local forwarding (not remote)
GatewayPorts no             # Don't expose forwarded ports publicly
PermitTunnel no             # Disable full VPN-mode tunneling (tun devices)
Enter fullscreen mode Exit fullscreen mode

Use AllowTcpForwarding no on servers where forwarding is never needed.

Scope your SSH keys

Use different keys for different purposes. A key that only needs to open a database tunnel doesn't need shell access. Use authorized_keys restrictions:

restrict,port-forwarding,permitopen="db.internal:5432" ssh-ed25519 AAAA... tunnel-key
Enter fullscreen mode Exit fullscreen mode

This key can only forward to db.internal:5432. Nothing else.

Audit open tunnels

Check what's currently being forwarded:

ss -tlnp | grep ssh
Enter fullscreen mode Exit fullscreen mode

Quick Reference

Type Flag Direction Best For
Local -L local:dest:port Local → Remote destination Accessing private services
Remote -R remote:local:port Remote → Local destination Exposing local services
Dynamic -D port All traffic via SOCKS proxy Browsing, VPN-like usage

The Bottom Line

SSH tunneling isn't a niche trick. It's a core skill that belongs in every developer and sysadmin's toolbox.

It handles use cases that would otherwise require a VPN, a third-party tunneling service, or a full infrastructure change — with nothing more than the SSH client you already have installed.

Master these three tunnel types, get comfortable with ~/.ssh/config, and you'll have a flexible, zero-cost solution for secure access in almost any situation.

The infrastructure is already there. Start using it.


If this helped, consider sharing it with your team. Follow for more practical deep-dives into the tools that power modern infrastructure.

Top comments (0)