SSH Tunnels & Port Forwarding#
Local port forwarding (-L)#
Forward a local port to a remote destination via an SSH host.
# Access remote DB on localhost:5432
ssh -L 5432:localhost:5432 user@jumphost.example.com
# Access a host only reachable from the jump host
ssh -L 8080:internal-server:80 user@jumphost.example.com
# Keep alive, no shell, background
ssh -fNL 5432:db.internal:5432 user@jumphost.example.com
Remote port forwarding (-R)#
Expose a local service on a port of the remote host.
# Make local :3000 reachable as remote :9000
ssh -R 9000:localhost:3000 user@remote.example.com
# Bind to all interfaces on remote (needs GatewayPorts yes in sshd_config)
ssh -R 0.0.0.0:9000:localhost:3000 user@remote.example.com
Dynamic (SOCKS5 proxy) (-D)#
# Start SOCKS5 proxy on local :1080, route through jump host
ssh -D 1080 -fN user@jumphost.example.com
# Then configure browser/curl to use SOCKS5 localhost:1080
curl --socks5-hostname localhost:1080 http://internal-site/
Jump hosts (-J / ProxyJump)#
# Single jump
ssh -J user@jump.example.com user@target.internal
# Multi-hop
ssh -J user@jump1,user@jump2 user@final.internal
~/.ssh/config patterns#
Host jump
HostName jump.example.com
User myuser
IdentityFile ~/.ssh/id_ed25519
Host internal
HostName target.internal
User myuser
ProxyJump jump
Host db-tunnel
HostName jump.example.com
User myuser
LocalForward 5432 db.internal:5432
ServerAliveInterval 60
ExitOnForwardFailure yes
Then just: ssh -fN db-tunnel
Persistent tunnels with systemd#
[Unit]
Description=SSH tunnel to production DB
After=network.target
[Service]
User=tunnel
ExecStart=/usr/bin/ssh -NT -o ServerAliveInterval=60 \
-o ExitOnForwardFailure=yes \
-L 5432:db.prod.internal:5432 \
user@jumphost.example.com
Restart=always
RestartSec=10s
[Install]
WantedBy=multi-user.target