HxHippy

SSH Tunneling and Port Forwarding

Create SSH tunnels for local, remote, and dynamic port forwarding.

Last updated: 2025-01-15

SSH Tunneling

SSH tunnels create encrypted channels for forwarding traffic between hosts.

Types of Port Forwarding

Local:   Your machine → SSH Server → Target
Remote:  Target → SSH Server → Your machine
Dynamic: SOCKS proxy through SSH server

Local Port Forwarding (-L)

Access remote service through local port.

# Syntax
ssh -L [local_addr:]local_port:remote_host:remote_port user@ssh_server

# Access remote MySQL through local port
ssh -L 3306:localhost:3306 user@server

# Access web app on internal network
ssh -L 8080:internal-server:80 user@bastion

# Bind to all interfaces
ssh -L 0.0.0.0:8080:localhost:80 user@server

Use Case: Access Internal Database

# Forward PostgreSQL
ssh -L 5432:db.internal:5432 user@bastion

# Connect locally
psql -h localhost -p 5432 -U dbuser mydb

Remote Port Forwarding (-R)

Expose local service to remote network.

# Syntax
ssh -R [remote_addr:]remote_port:local_host:local_port user@ssh_server

# Expose local web server to remote
ssh -R 8080:localhost:3000 user@server

# Allow external connections
ssh -R 0.0.0.0:8080:localhost:3000 user@server

Server Configuration Required

# /etc/ssh/sshd_config
GatewayPorts yes

Use Case: Expose Development Server

# Run local dev server
npm run dev  # localhost:3000

# Expose to public server
ssh -R 80:localhost:3000 user@public-server

# Access at http://public-server

Dynamic Port Forwarding (-D)

Create a SOCKS proxy.

# Create SOCKS5 proxy on port 1080
ssh -D 1080 user@server

# With background mode
ssh -D 1080 -N -f user@server

Configure Browser

SOCKS Host: localhost
SOCKS Port: 1080
SOCKS v5: Yes

Use with curl

curl --socks5 localhost:1080 http://example.com

Jump Hosts (ProxyJump)

Connect through intermediate hosts.

# Single jump
ssh -J bastion user@internal

# Multiple jumps
ssh -J bastion1,bastion2 user@internal

SSH Config

Host internal-server
    HostName 10.0.0.50
    User admin
    ProxyJump bastion.example.com

Background and Persistent Tunnels

Background Mode

# Run in background (-f) without command (-N)
ssh -f -N -L 8080:localhost:80 user@server

# With control socket
ssh -f -N -M -S /tmp/tunnel.sock -L 8080:localhost:80 user@server

# Close tunnel
ssh -S /tmp/tunnel.sock -O exit user@server

Autossh for Persistent Tunnels

# Install autossh
sudo apt install autossh

# Create persistent tunnel
autossh -M 0 -f -N -L 8080:localhost:80 user@server

# With server alive checks
autossh -M 0 -o "ServerAliveInterval 30" -o "ServerAliveCountMax 3" -f -N -L 8080:localhost:80 user@server

Systemd Service

# /etc/systemd/system/ssh-tunnel.service
[Unit]
Description=SSH Tunnel to Production
After=network.target

[Service]
User=tunnel
ExecStart=/usr/bin/ssh -N -L 3306:db.internal:3306 user@bastion
Restart=always
RestartSec=30

[Install]
WantedBy=multi-user.target

Multiple Tunnels

# Multiple local forwards
ssh -L 3306:db:3306 -L 6379:redis:6379 -L 5432:postgres:5432 user@bastion

# Or in config
Host tunnel-all
    HostName bastion.example.com
    LocalForward 3306 db:3306
    LocalForward 6379 redis:6379
    LocalForward 5432 postgres:5432

Tunnel + Command

# Run command after tunnel established
ssh -L 5432:db:5432 user@server "psql -h localhost -U user dbname"

Troubleshooting

# Verbose output
ssh -v -L 8080:localhost:80 user@server

# Check if port is in use
lsof -i :8080

# Test tunnel
curl localhost:8080

Best Practices

  1. Use ProxyJump - Modern, cleaner syntax
  2. Use autossh - For persistent tunnels
  3. Limit forwards - Only what's needed
  4. Use control sockets - For connection reuse
  5. Document tunnels - Keep track of what's running
intermediate SSH Updated 2025-01-15
  • ssh tunnel
  • port forwarding
  • socks proxy
  • jump host
  • bastion