SSH Tunnel Configuration (Port Forwarding): Complete Guide

Introduction

SSH tunneling, also known as SSH port forwarding, is a powerful technique that leverages the secure SSH protocol to create encrypted tunnels for forwarding network traffic between systems. This versatile method allows you to securely access services running on remote networks, bypass firewall restrictions, encrypt otherwise insecure protocols, and create temporary VPN-like connections without requiring administrative privileges or complex VPN infrastructure.

SSH tunneling works by encapsulating arbitrary network traffic within SSH connections, providing the same encryption and authentication benefits that protect your SSH sessions. Whether you need to access a database server behind a firewall, secure web browsing on untrusted networks, or expose local development servers to the Internet temporarily, SSH tunneling provides elegant solutions that system administrators and developers rely on daily.

This comprehensive guide covers all types of SSH tunneling including local port forwarding, remote port forwarding, dynamic port forwarding (SOCKS proxy), and advanced multi-hop scenarios. You'll learn practical use cases, security considerations, troubleshooting techniques, and best practices for implementing SSH tunnels in development, testing, and production environments.

Understanding SSH Tunneling Types

Local Port Forwarding

Local port forwarding creates a tunnel from your local machine to a remote destination through an SSH server. Traffic sent to a local port is forwarded through the SSH connection to a specified destination.

Syntax:

ssh -L [local_port]:[destination_host]:[destination_port] [user]@[ssh_server]

Flow:

Your Computer:local_port → SSH Server → destination_host:destination_port

Use Cases:

  • Access remote databases securely
  • Connect to services behind firewalls
  • Secure web browsing through remote server
  • Access internal network resources

Remote Port Forwarding

Remote port forwarding creates a tunnel from the SSH server back to your local machine or another destination. Traffic sent to a port on the SSH server is forwarded back through the SSH connection.

Syntax:

ssh -R [remote_port]:[destination_host]:[destination_port] [user]@[ssh_server]

Flow:

SSH Server:remote_port → Your Computer → destination_host:destination_port

Use Cases:

  • Expose local development server to Internet
  • Allow remote access to services on your machine
  • Create reverse tunnels through NAT/firewalls
  • Provide temporary external access

Dynamic Port Forwarding (SOCKS Proxy)

Dynamic port forwarding creates a SOCKS proxy server on your local machine. Applications configured to use this proxy have their traffic forwarded through the SSH server.

Syntax:

ssh -D [local_port] [user]@[ssh_server]

Flow:

Application → SOCKS Proxy:local_port → SSH Server → Destination

Use Cases:

  • Secure all browser traffic on public WiFi
  • Bypass geographical restrictions
  • Route multiple applications through tunnel
  • Temporary VPN alternative

Prerequisites

Before configuring SSH tunnels, ensure you have:

  • SSH client installed (OpenSSH on Linux/macOS, PuTTY on Windows)
  • SSH access to remote server with valid credentials
  • Knowledge of target service's host and port
  • Understanding of basic networking concepts
  • Firewall permissions for required ports (if applicable)
  • SSH key-based authentication configured (recommended)

Verify SSH Installation

# Check SSH client version
ssh -V

# Check SSH server status (on remote server)
sudo systemctl status sshd

Local Port Forwarding

Basic Local Port Forwarding

Forward local port to remote service:

# Forward local port 3307 to MySQL server on remote host
ssh -L 3307:localhost:3306 [email protected]

# Now connect to MySQL locally
mysql -h 127.0.0.1 -P 3307 -u dbuser -p

Explanation:

  • Traffic to localhost:3307 is forwarded through remote-server.com
  • To MySQL running on localhost:3306 (from remote server's perspective)
  • SSH session must remain open for tunnel to work

Binding to Specific Interface

# Bind to localhost only (default)
ssh -L 127.0.0.1:3307:localhost:3306 [email protected]

# Bind to all interfaces (allow connections from other machines)
ssh -L 0.0.0.0:3307:localhost:3306 [email protected]

# Bind to specific IP
ssh -L 192.168.1.100:3307:localhost:3306 [email protected]

Forwarding to Different Host

Forward to a service accessible from SSH server but on different host:

# Access database server through jump host
ssh -L 5432:db-server.internal:5432 [email protected]

# Connect to PostgreSQL
psql -h localhost -p 5432 -U dbuser database

Scenario:

  • jump-host.com can access db-server.internal
  • Your machine cannot directly access db-server.internal
  • Tunnel through jump-host.com to reach database

Multiple Port Forwards

Create multiple tunnels in single SSH session:

# Forward multiple services
ssh -L 3307:localhost:3306 \
    -L 6380:localhost:6379 \
    -L 8080:localhost:80 \
    [email protected]

# Now access:
# MySQL on localhost:3307
# Redis on localhost:6380
# Web server on localhost:8080

Background SSH Tunnel

Run SSH tunnel in background:

# -f: background process
# -N: don't execute remote command
ssh -f -N -L 3307:localhost:3306 [email protected]

# Verify tunnel is running
ps aux | grep ssh

# Kill background tunnel
pkill -f "ssh.*3307"

Persistent SSH Tunnel with autossh

Install and use autossh for automatic reconnection:

# Install autossh
sudo apt install autossh  # Ubuntu/Debian
sudo dnf install autossh  # RHEL/CentOS

# Create persistent tunnel
autossh -M 0 -f -N -L 3307:localhost:3306 [email protected]

# With reconnection options
autossh -M 0 -f -N \
    -o "ServerAliveInterval 30" \
    -o "ServerAliveCountMax 3" \
    -L 3307:localhost:3306 [email protected]

Practical Example: Secure Database Access

Scenario: Access remote MySQL database securely

# Create tunnel to MySQL server
ssh -f -N -L 3307:localhost:3306 [email protected]

# Connect using local MySQL client
mysql -h 127.0.0.1 -P 3307 -u admin -p

# In database client
mysql> SELECT @@hostname;
# Shows remote server hostname, confirming tunnel works

Benefits:

  • Encrypted connection to database
  • No need to expose MySQL port to Internet
  • Can use local database tools with remote servers

Remote Port Forwarding

Basic Remote Port Forwarding

Make local service accessible from remote server:

# Forward port 8080 on remote server to local port 3000
ssh -R 8080:localhost:3000 [email protected]

# On remote server, access:
curl http://localhost:8080
# Connects to your local machine's port 3000

Use Case: Expose local development server for testing

Expose Local Service to Internet

# Make local web app accessible from remote server
ssh -R 0.0.0.0:8080:localhost:3000 [email protected]

# Now accessible at:
# http://remote-server.com:8080

Note: Requires GatewayPorts yes in remote server's /etc/ssh/sshd_config

Configure GatewayPorts on SSH Server

# On remote SSH server
sudo nano /etc/ssh/sshd_config

# Add or modify:
GatewayPorts yes
# Or for more security:
GatewayPorts clientspecified

# Restart SSH service
sudo systemctl restart sshd

Reverse Tunnel Through Firewall

Access machine behind NAT/firewall:

# From machine behind firewall
ssh -R 2222:localhost:22 [email protected]

# From public server, SSH back to internal machine
ssh -p 2222 user@localhost

Scenario:

  • Home/office machine behind NAT
  • Need remote access without port forwarding on router
  • Create reverse tunnel to public server
  • Connect back through tunnel

Persistent Reverse Tunnel

# Use autossh for persistent reverse tunnel
autossh -M 0 -f -N \
    -o "ServerAliveInterval 30" \
    -o "ServerAliveCountMax 3" \
    -R 2222:localhost:22 [email protected]

Create systemd service for automatic startup:

sudo nano /etc/systemd/system/reverse-tunnel.service
[Unit]
Description=Reverse SSH Tunnel
After=network.target

[Service]
User=tunneluser
ExecStart=/usr/bin/autossh -M 0 -N \
    -o "ServerAliveInterval 30" \
    -o "ServerAliveCountMax 3" \
    -o "ExitOnForwardFailure yes" \
    -R 2222:localhost:22 [email protected]
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target

Enable and start:

sudo systemctl enable reverse-tunnel
sudo systemctl start reverse-tunnel
sudo systemctl status reverse-tunnel

Dynamic Port Forwarding (SOCKS Proxy)

Create SOCKS Proxy

# Create SOCKS proxy on local port 1080
ssh -D 1080 [email protected]

# Keep tunnel open
# Configure applications to use SOCKS5 proxy at localhost:1080

Configure Browser to Use SOCKS Proxy

Firefox:

  1. Settings → General → Network Settings
  2. Select "Manual proxy configuration"
  3. SOCKS Host: localhost, Port: 1080
  4. Select "SOCKS v5"
  5. Check "Proxy DNS when using SOCKS v5"

Chrome/Chromium (Linux):

google-chrome --proxy-server="socks5://localhost:1080"

System-wide proxy (Linux):

export ALL_PROXY=socks5://localhost:1080

Test SOCKS Proxy

# Check IP address before tunnel
curl ifconfig.me

# Start SOCKS tunnel
ssh -D 1080 -f -N [email protected]

# Check IP through proxy
curl --socks5 localhost:1080 ifconfig.me
# Should show remote server's IP

Using proxychains with SOCKS Tunnel

# Install proxychains
sudo apt install proxychains

# Configure proxychains
sudo nano /etc/proxychains.conf

# Add at end:
socks5 127.0.0.1 1080

# Use with any command
proxychains curl ifconfig.me
proxychains wget http://example.com
proxychains git clone https://github.com/user/repo

Secure Browsing on Public WiFi

# Create tunnel to your home/office server
ssh -D 1080 -f -N -C [email protected]

# Configure browser to use SOCKS proxy
# All traffic now encrypted and routed through home server

Advanced SSH Tunneling Techniques

Multi-Hop SSH Tunneling

Jump through multiple SSH servers:

# Method 1: ProxyJump (OpenSSH 7.3+)
ssh -J jump1.example.com,jump2.example.com [email protected]

# Method 2: ProxyCommand
ssh -o ProxyCommand="ssh -W %h:%p [email protected]" [email protected]

# With port forwarding through jump hosts
ssh -J jump.example.com -L 3307:db.internal:3306 [email protected]

Configure in ~/.ssh/config:

nano ~/.ssh/config
Host jump
    HostName jump.example.com
    User jumpuser

Host final
    HostName final-destination.com
    User finaluser
    ProxyJump jump

Host db-tunnel
    HostName final-destination.com
    User finaluser
    ProxyJump jump
    LocalForward 3307 db.internal:3306

Usage:

# Connect through jump host
ssh final

# Create database tunnel through jump host
ssh db-tunnel

X11 Forwarding with Tunneling

# Forward X11 and create port tunnel
ssh -X -L 5901:localhost:5901 [email protected]

# Run GUI applications remotely
firefox &

VPN-like Configuration

Route all traffic through SSH tunnel:

# Create tunnel with IP forwarding
ssh -D 1080 -f -N -C [email protected]

# Configure routing (requires root)
# Route all traffic through SOCKS proxy
# Use tools like redsocks or proxychains-ng

Compressed Tunneling

# Enable compression for slow connections
ssh -C -D 1080 [email protected]

# Compression useful for:
# - Slow network connections
# - Text-heavy protocols
# - Transferring compressible data

Tunnel with Specific Cipher

# Use specific cipher for performance
ssh -c [email protected] -D 1080 [email protected]

# List available ciphers
ssh -Q cipher

SSH Tunnel Configuration Files

Using SSH Config File

Simplify complex tunnel commands:

nano ~/.ssh/config
# Database tunnel
Host dbtunnel
    HostName remote-server.example.com
    User dbadmin
    LocalForward 3307 localhost:3306
    LocalForward 6380 localhost:6379

# SOCKS proxy
Host socksproxy
    HostName proxy-server.example.com
    User proxyuser
    DynamicForward 1080

# Reverse tunnel
Host reversetunnel
    HostName public-server.example.com
    User tunneluser
    RemoteForward 8080 localhost:3000

Usage:

# Create database tunnel
ssh dbtunnel

# Create SOCKS proxy
ssh socksproxy

# Create reverse tunnel
ssh reversetunnel

Persistent Options

Host *
    ServerAliveInterval 60
    ServerAliveCountMax 3
    TCPKeepAlive yes
    ExitOnForwardFailure yes

Port Forwarding Use Cases

Use Case 1: Secure Database Administration

# Access MySQL without exposing to Internet
ssh -L 3307:localhost:3306 [email protected]

# Connect with MySQL Workbench
# Host: 127.0.0.1
# Port: 3307

Use Case 2: Access Internal Web Applications

# Access internal corporate app
ssh -L 8080:internal-app.company.local:80 [email protected]

# Open browser to http://localhost:8080

Use Case 3: Development Server Testing

# Share local development with remote team
ssh -R 0.0.0.0:8080:localhost:3000 [email protected]

# Team accesses: http://demo-server.com:8080

Use Case 4: Bypass Geographic Restrictions

# Connect through server in different country
ssh -D 1080 [email protected]

# Configure browser to use SOCKS proxy
# Access region-restricted content

Use Case 5: Secure Remote Desktop

# Forward VNC through SSH
ssh -L 5901:localhost:5901 [email protected]

# Connect VNC client to localhost:5901
vncviewer localhost:5901

Use Case 6: Git Through Corporate Firewall

# Access GitHub through corporate firewall
ssh -D 1080 [email protected]

# Configure Git to use SOCKS proxy
git config --global http.proxy socks5://127.0.0.1:1080
git config --global https.proxy socks5://127.0.0.1:1080

Monitoring and Managing Tunnels

List Active SSH Tunnels

# Show SSH processes
ps aux | grep ssh

# Show detailed SSH connections
ss -tuln | grep ssh
netstat -tuln | grep ssh

# Show forwarded ports
lsof -i -n | grep ssh

Kill Specific Tunnel

# Find SSH process
ps aux | grep "ssh.*3307"

# Kill by PID
kill 12345

# Or by pattern
pkill -f "ssh.*3307"

Monitor Tunnel Traffic

# Monitor bandwidth usage
iftop -i tun0

# Monitor connections through tunnel
sudo tcpdump -i lo port 3307

# Check tunnel is forwarding
nc -zv localhost 3307

Debugging Tunnels

# Verbose SSH output
ssh -v -L 3307:localhost:3306 [email protected]

# Extra verbose
ssh -vvv -L 3307:localhost:3306 [email protected]

# Check specific error
journalctl -u sshd | grep "forward"

Security Considerations

Use SSH Key Authentication

# Generate SSH key
ssh-keygen -t ed25519 -C "tunnel-key"

# Copy to remote server
ssh-copy-id -i ~/.ssh/id_ed25519.pub [email protected]

# Use key for tunnel
ssh -i ~/.ssh/id_ed25519 -L 3307:localhost:3306 [email protected]

Restrict Port Forwarding

On SSH server, limit forwarding capabilities:

sudo nano /etc/ssh/sshd_config
# Disable all forwarding
AllowTcpForwarding no

# Allow local forwarding only
AllowTcpForwarding local

# Allow remote forwarding only
AllowTcpForwarding remote

# Disable X11 forwarding if not needed
X11Forwarding no

# Restrict to specific users
Match User tunneluser
    AllowTcpForwarding yes

Restart SSH:

sudo systemctl restart sshd

Limit Binding Interfaces

# Bind to localhost only (most secure)
ssh -L 127.0.0.1:3307:localhost:3306 [email protected]

# Avoid binding to 0.0.0.0 unless necessary
# This exposes port to all network interfaces

Use Firewall Rules

# Allow only localhost connections to forwarded port
sudo ufw allow from 127.0.0.1 to any port 3307

# Or with iptables
sudo iptables -A INPUT -p tcp --dport 3307 -s 127.0.0.1 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 3307 -j DROP

Monitor for Unauthorized Tunnels

# Check for unexpected SSH processes
ps aux | grep ssh

# Monitor SSH authentication logs
sudo tail -f /var/log/auth.log | grep sshd

# List all listening ports
sudo ss -tuln

Audit Tunnel Usage

# Enable SSH logging
sudo nano /etc/ssh/sshd_config

# Set:
LogLevel VERBOSE

# Monitor logs
sudo journalctl -u sshd -f

Troubleshooting Common Issues

Issue 1: Port Already in Use

Symptoms:

bind: Address already in use
channel_setup_fwd_listener_tcpip: cannot listen to port: 3307

Solutions:

# Find process using port
sudo lsof -i :3307
sudo ss -tulpn | grep 3307

# Kill process
sudo kill -9 <PID>

# Or use different local port
ssh -L 3308:localhost:3306 [email protected]

Issue 2: Connection Refused

Symptoms:

channel 2: open failed: connect failed: Connection refused

Diagnosis:

# On remote server, verify service is running
sudo systemctl status mysql
sudo ss -tuln | grep 3306

# Check service is listening on correct interface
netstat -tuln | grep 3306

Solutions:

# Start service on remote server
sudo systemctl start mysql

# Verify service binds to correct interface
# Edit service config to listen on 127.0.0.1 or 0.0.0.0

Issue 3: Tunnel Disconnects

Symptoms:

  • SSH tunnel drops after period of inactivity
  • "Broken pipe" error

Solutions:

# Enable keepalive in SSH config
nano ~/.ssh/config
Host *
    ServerAliveInterval 60
    ServerAliveCountMax 3
# Or use in command
ssh -o ServerAliveInterval=60 -L 3307:localhost:3306 [email protected]

# Use autossh for auto-reconnect
autossh -M 0 -o "ServerAliveInterval 30" -L 3307:localhost:3306 [email protected]

Issue 4: Permission Denied (Port < 1024)

Symptoms:

bind: Permission denied

Solutions:

# Use port >= 1024
ssh -L 8080:localhost:80 [email protected]

# Or run with sudo (not recommended)
sudo ssh -L 80:localhost:80 [email protected]

Issue 5: GatewayPorts Not Working

Symptoms:

  • Remote forward only accessible from localhost on remote server

Solutions:

# On SSH server
sudo nano /etc/ssh/sshd_config

# Change:
GatewayPorts yes

# Restart SSH
sudo systemctl restart sshd

# Verify setting
sudo sshd -T | grep gatewayports

Best Practices

1. Use SSH Config File

Organize tunnels in ~/.ssh/config:

Host prod-db
    HostName production.example.com
    User dbadmin
    IdentityFile ~/.ssh/prod_key
    LocalForward 3307 localhost:3306
    ServerAliveInterval 60

2. Implement Least Privilege

  • Create dedicated SSH users for tunneling
  • Limit forwarding permissions
  • Restrict shell access if only tunneling needed
# In sshd_config
Match User tunneluser
    AllowTcpForwarding yes
    X11Forwarding no
    PermitTunnel no
    ForceCommand /bin/false

3. Document Tunnel Configurations

Maintain documentation:

  • Purpose of each tunnel
  • Source and destination details
  • Security considerations
  • Contact information for troubleshooting

4. Monitor Tunnel Activity

# Create monitoring script
#!/bin/bash
# check-tunnel.sh

TUNNEL_PORT=3307

if ! nc -z localhost $TUNNEL_PORT; then
    echo "Tunnel down, recreating..."
    autossh -M 0 -f -N -L $TUNNEL_PORT:localhost:3306 [email protected]
fi

Add to cron:

*/5 * * * * /usr/local/bin/check-tunnel.sh

5. Use Automation Tools

For production environments:

  • Ansible for tunnel deployment
  • systemd services for persistent tunnels
  • Monitoring tools (Nagios, Zabbix) for tunnel health checks

6. Secure Tunnel Endpoints

  • Keep SSH server updated
  • Use strong SSH keys (ED25519, RSA 4096)
  • Enable fail2ban for brute force protection
  • Regularly audit SSH access logs

Conclusion

SSH tunneling provides a versatile, secure method for port forwarding and creating encrypted network pathways without requiring VPN infrastructure or administrative privileges. Whether you need local port forwarding for secure database access, remote port forwarding to expose local services, or dynamic forwarding for SOCKS proxy functionality, SSH tunneling offers elegant solutions for common networking challenges.

Key takeaways:

  • Local forwarding provides secure access to remote services
  • Remote forwarding exposes local services through remote servers
  • Dynamic forwarding creates SOCKS proxies for flexible routing
  • SSH config files simplify complex tunnel management
  • autossh ensures persistent tunnel connectivity
  • Security considerations are essential for safe tunnel deployment
  • Monitoring and documentation maintain reliable tunnel infrastructure

Master SSH tunneling techniques to enhance your system administration toolkit, improve security posture, and solve complex networking challenges with minimal overhead. The skills covered in this guide apply across development, testing, and production environments, making SSH tunneling an indispensable technique for modern infrastructure management.

For advanced scenarios, explore tools like sshuttle for transparent proxying, stunnel for legacy protocol encryption, and WireGuard for dedicated VPN requirements beyond SSH capabilities.