Local Port Forwarding¶
Overview¶
Local port forwarding makes a remote service accessible on your local machine. Traffic to a local port is forwarded through SSH to a destination.
┌──────────────────────────────────────────────────────────────────────────┐
│ Local Port Forwarding │
│ │
│ Your Machine SSH Server Destination │
│ ┌───────────┐ ┌───────────┐ ┌───────────┐ │
│ │ │ │ │ │ │ │
│ │ localhost ├──────────┤ SSH ├──────────┤ Service │ │
│ │ :8080 │ SSH │ Server │ Direct │ :80 │ │
│ │ │ Tunnel │ │ Connect │ │ │
│ └───────────┘ └───────────┘ └───────────┘ │
│ │
│ Browser → localhost:8080 → SSH → webserver:80 │
│ │
└──────────────────────────────────────────────────────────────────────────┘
Use case: Access remote services that aren't directly reachable (databases, internal web apps, admin panels).
Basic Syntax¶
Examples¶
Access Remote Web Server¶
Now http://localhost:8080 shows the web server running on server.example.com.
Access Remote Database¶
# PostgreSQL
ssh -L 5432:localhost:5432 user@db.example.com
# MySQL
ssh -L 3306:localhost:3306 user@db.example.com
# Redis
ssh -L 6379:localhost:6379 user@redis.example.com
Connect with local tools:
psql -h localhost -p 5432 -U dbuser
mysql -h 127.0.0.1 -P 3306 -u dbuser -p
redis-cli -h localhost -p 6379
Access Service on Different Host¶
Forward through SSH server to another host:
Different Local Port¶
When local port is already in use:
Multiple Forwards¶
Multiple -L Options¶
ssh -L 5432:localhost:5432 \
-L 6379:localhost:6379 \
-L 8080:localhost:80 \
user@server.example.com
In SSH Config¶
Host dev-server
HostName dev.example.com
User developer
LocalForward 5432 localhost:5432
LocalForward 6379 redis.internal:6379
LocalForward 8080 localhost:80
Tunnel Only (No Shell)¶
Background Tunnel¶
-f: Fork to background after authentication-N: No remote command (tunnel only)
Keep in Foreground¶
Bind Address¶
By default, forwards bind to localhost only.
Allow Other Machines¶
ssh -L 0.0.0.0:8080:localhost:80 user@server.example.com
# or
ssh -L *:8080:localhost:80 user@server.example.com
Now other machines can connect to your-ip:8080.
Specific Interface¶
Security
Binding to 0.0.0.0 exposes the tunnel to your network. Ensure this is intentional.
Common Use Cases¶
Access Internal Jenkins¶
Access RDP Through SSH¶
ssh -L 3389:windows-server.internal:3389 user@bastion.example.com
# Connect RDP client to localhost:3389
Access Kubernetes Dashboard¶
Access Jupyter Notebook¶
Access Elasticsearch¶
With SSH Config¶
# ~/.ssh/config
Host db-tunnel
HostName bastion.example.com
User admin
LocalForward 5432 database.internal:5432
LocalForward 6379 redis.internal:6379
Host web-dev
HostName dev.example.com
User developer
LocalForward 3000 localhost:3000
LocalForward 5173 localhost:5173
Usage:
Persistent Tunnels¶
Using autossh¶
# Install
apt install autossh
# Create persistent tunnel
autossh -M 0 -f -N -L 5432:localhost:5432 user@server.example.com
-M 0: Disable monitoring port (uses SSH keepalive instead)-f: Background
Systemd Service¶
# /etc/systemd/system/ssh-tunnel.service
[Unit]
Description=SSH Tunnel to Database
After=network.target
[Service]
User=tunneluser
ExecStart=/usr/bin/ssh -N -L 5432:localhost:5432 -o ServerAliveInterval=60 -o ExitOnForwardFailure=yes user@db.example.com
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
Troubleshooting¶
Port Already in Use¶
# Check what's using the port
lsof -i :5432
ss -tlnp | grep 5432
# Use different local port
ssh -L 15432:localhost:5432 user@server.example.com
Connection Refused¶
# Check service is running on remote
ssh user@server "systemctl status postgresql"
# Check it's listening
ssh user@server "ss -tlnp | grep 5432"
# Check binding (localhost vs 0.0.0.0)
# Service might be on 127.0.0.1 only
Tunnel Drops¶
# Add keep-alive
ssh -o ServerAliveInterval=60 -o ServerAliveCountMax=3 \
-L 5432:localhost:5432 user@server.example.com
# Or use autossh
autossh -M 0 -f -N -L 5432:localhost:5432 user@server.example.com
Permission Denied for Port¶
Ports below 1024 require root:
# This fails without root
ssh -L 80:localhost:80 user@server.example.com
# Use higher port instead
ssh -L 8080:localhost:80 user@server.example.com
Security Considerations¶
- Tunnel scope: Forward only what you need
- Bind address: Keep localhost unless necessary
- Firewall: Ensure local firewall allows traffic
- Server policy:
AllowTcpForwardingmust be enabled on server - Audit: Log tunnel usage for compliance