SSH is more than a remote shell. The tunneling capabilities — local forwarding, remote forwarding, and dynamic SOCKS proxying — are genuinely useful for accessing services that aren’t directly reachable, and I reach for them regularly.
Local Port Forwarding
Local forwarding creates a tunnel: you connect to a local port, and SSH forwards the connection to a remote destination.
ssh -L local_port:destination_host:destination_port user@ssh_server
Use case: Access a database on a remote server
The database is on db.internal port 5432, not accessible from the internet. A server gateway.example.com can reach db.internal.
ssh -L 5432:db.internal:5432 user@gateway.example.com
Now localhost:5432 connects through the SSH tunnel to db.internal:5432. Connect with psql:
psql -h localhost -p 5432 -U myuser mydb
Access a service running on the remote server itself:
ssh -L 8080:localhost:8080 user@remote.server.com
Here localhost in the forwarding spec is from the SSH server’s perspective. This forwards your local port 8080 to remote.server.com:8080.
Use case: Access a web UI only listening on localhost
Redis, Prometheus, Grafana, Netdata — many services bind to 127.0.0.1 by default and aren’t exposed to the network. Access them securely:
ssh -L 9090:127.0.0.1:9090 user@monitoring.server.com
# Now open http://localhost:9090 in your browser
Background Tunneling
For long-running tunnels, run in background:
ssh -fN -L 5432:db.internal:5432 user@gateway.example.com
-f — go to background before command execution.-N — don’t execute a remote command (just forward ports).
To close it later, find and kill the SSH process:
ps aux | grep "ssh -fN"
kill <pid>
Or use a more structured approach with control sockets:
# Open tunnel with control socket
ssh -fN -M -S /tmp/ssh-tunnel.sock \
-L 5432:db.internal:5432 user@gateway.example.com
# Close it cleanly
ssh -S /tmp/ssh-tunnel.sock -O exit user@gateway.example.com
Remote Port Forwarding
Remote forwarding does the reverse: connections to a port on the remote server get forwarded to your local machine.
ssh -R remote_port:local_host:local_port user@remote_server
Use case: Expose a local development server to a client
You’re running a dev server on localhost:3000 and need a client to test it without deploying:
ssh -R 8080:localhost:3000 user@public.server.com
Now public.server.com:8080 forwards to your local localhost:3000. The client connects to the public server’s IP.
For this to work, you need GatewayPorts yes in the SSH server config on public.server.com:
GatewayPorts yes
Without this, the remote port only binds to 127.0.0.1 on the remote server (not accessible externally).
Use case: Access a server behind NAT from outside
Your home server is behind NAT and not directly accessible. Set up a reverse tunnel from the home server to a cloud VPS:
On the home server:
ssh -fN -R 2222:localhost:22 user@cloud.vps.com
Now from anywhere, SSH to the cloud VPS and then jump through:
ssh user@cloud.vps.com
# Then from there:
ssh -p 2222 user@localhost # This connects back to your home server
Or in one command with ProxyJump:
ssh -J user@cloud.vps.com -p 2222 homeuser@localhost
For persistent reverse tunnels (recovering from network drops), use autossh:
apt install autossh
# Start persistent reverse tunnel
autossh -M 0 -fN -R 2222:localhost:22 \
-o "ServerAliveInterval=30" \
-o "ServerAliveCountMax=3" \
user@cloud.vps.com
autossh monitors the tunnel and restarts it if the connection drops. -M 0 disables autossh’s own monitoring (rely on SSH keepalives instead).
Dynamic SOCKS Proxy
Dynamic forwarding creates a SOCKS5 proxy — your browser or application routes traffic through it, and SSH forwards it from the server.
ssh -D 1080 -fN user@remote.server.com
Configure your browser to use 127.0.0.1:1080 as a SOCKS5 proxy. All traffic routes through the remote server. Useful for:
- Bypassing geographical restrictions when testing
- Accessing internal resources that the SSH server can reach
- Secure browsing on untrusted networks
In Firefox: Preferences → Network Settings → Manual Proxy Configuration → SOCKS Host: 127.0.0.1, Port: 1080, SOCKS5.
Or for specific applications:
# Use SOCKS proxy with curl
curl --socks5 127.0.0.1:1080 http://internal-service.example.com
# Use with git
git config --global http.proxy socks5://127.0.0.1:1080
SSH Config for Common Tunnels
Rather than typing long commands, add tunnels to ~/.ssh/config:
# Jump host configuration
Host bastion
HostName bastion.example.com
User ubuntu
IdentityFile ~/.ssh/bastion_key
# Internal database via tunnel
Host db-tunnel
HostName bastion.example.com
User ubuntu
LocalForward 5432 db.internal:5432
LocalForward 6379 redis.internal:6379
ServerAliveInterval 30
ServerAliveCountMax 3
ExitOnForwardFailure yes
# Access internal web UIs
Host monitoring-tunnel
HostName monitoring.example.com
User ubuntu
LocalForward 9090 localhost:9090
LocalForward 3000 localhost:3000
LocalForward 19999 localhost:19999
Then connect with:
ssh -fN db-tunnel
# Now localhost:5432 connects to the database, localhost:6379 to Redis
ProxyJump: Multihop SSH
For SSH through a bastion/jump host:
ssh -J user@bastion.example.com user@internal.server.com
-J specifies a jump host. SSH connects to the bastion first, then tunnels through to the destination.
In ~/.ssh/config:
Host internal-servers
HostName *.internal
User ubuntu
ProxyJump bastion.example.com
Then:
ssh app01.internal # Automatically routes through bastion
For multiple hops:
ssh -J user@hop1.example.com,user@hop2.example.com user@final.target.com
Security Considerations
Port forwarding is powerful — make sure your SSH server has appropriate restrictions:
# /etc/ssh/sshd_config
# Restrict which users can use tunneling
AllowTcpForwarding yes # Allow local/remote forwarding
GatewayPorts no # Don't allow external access to remote forwardings (default)
X11Forwarding no # Disable X11 forwarding unless needed
PermitTunnel no # Disable TUN/TAP device tunneling
For service accounts that only need tunneling (not shell access):
# In authorized_keys:
command="",no-pty,no-X11-forwarding,no-agent-forwarding ssh-rsa AAAA...
Or restrict to specific tunnel configurations. SSH’s flexibility here is a double-edged sword — be intentional about what you allow.
For setting up the SSH keys themselves for secure access, the SSH key authentication guide covers key generation, distribution, and troubleshooting.