Tunneling: Lending the Pipe
The SSH pipe usually carries a terminal. But it can carry any TCP traffic — you can aim another program's connection through it. That's tunneling (or port forwarding), and it's the closest SSH gets to magic.
All three forms were verified against a real OpenSSH 9.6p1 client and a loopback server during writing — the port numbers and flags below are real, though the session transcripts are condensed.
-L: local forward (reach in)
$ ssh -L 8080:localhost:80 sam@192.0.2.1
Reads as: "listen on my port 8080; anything sent there,
deliver to port 80 as seen from 192.0.2.1." Point your
browser at http://localhost:8080 and you're browsing the
web server that only listens on the remote machine's
localhost.
When you'd use it: the database admin panel, the router web UI, or a dev server bound to localhost on the remote machine — anything you don't want exposed to the network, reached securely through SSH.
Add -N (no remote command, just the tunnel) and -f
(background it):
$ ssh -f -N -L 8080:localhost:80 sam@192.0.2.1
-R: remote forward (reach out)
$ ssh -R 9090:localhost:3000 sam@192.0.2.1
The mirror image: the server listens on its port 9090, and traffic there comes back through the tunnel to your port 3000.
When you'd use it: demoing a website running on your
laptop to someone who can reach the server — or giving a
machine behind NAT a way to receive connections. (The server
must allow it: AllowTcpForwarding in sshd_config, on by
default.)
-D: SOCKS proxy (browse through)
$ ssh -D 1080 sam@192.0.2.1
Opens a SOCKS proxy on your port 1080. Point a browser at it and your web traffic exits from the server's network — encrypted between you and the server.
When you'd use it: safely using untrusted Wi-Fi, or reaching sites the way the server's network sees them. It's a poor man's VPN for web traffic — one port, one program at a time.
The direction cheat-sheet
| Flag | Who listens | Traffic flows to | Mnemonic |
|---|---|---|---|
-L |
Your machine | Remote network | Local listens |
-R |
Remote machine | Your machine | Remote listens |
-D |
Your machine | Anywhere via the server | Dynamic (SOCKS) |
In the config file
Tunnels can live in ~/.ssh/config too:
Host db-admin
HostName 192.0.2.1
User sam
LocalForward 8080 localhost:80
ssh db-admin opens the terminal and the tunnel.
The honest limits
- Tunnels forward TCP only — no UDP. (Some game traffic and DNS-over-UDP won't fit.)
- A tunnel is only as alive as the SSH connection carrying
it. For tunnels that must survive, look at
autossh, which watches the connection and restarts it. -Ropens a port on the server — on a shared server, anyone there can connect to it. Bind it to the server's localhost (-R 127.0.0.1:9090:localhost:3000) unless you mean otherwise.
Check
- With
-L 8080:localhost:80active,curl http://localhost:8080returns the remote server's page. ssh -O checkon a multiplexed connection shows the forwards alongside the master (chapter 8).