SSH Port Forwarding and Tunneling: Practical Use Cases

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.

Scroll to Top