Loading learning content...
Beyond secure shell access, SSH provides one of the most versatile tunneling capabilities in networking. Any TCP-based protocol—database connections, web traffic, mail transfers, proprietary applications—can ride through SSH's encrypted channel.
This capability transforms SSH from a remote access tool into a universal encryption wrapper. A developer connecting to a production database through SSH tunneling achieves the same encryption strength as the SSH session itself. An administrator accessing a web management interface through SSH forwards traffic securely without the application needing any cryptographic awareness.
By the end of this page, you will understand all SSH tunneling modes: local port forwarding (accessing remote services locally), remote port forwarding (exposing local services remotely), dynamic SOCKS proxying (full proxy capability), and advanced configurations like jump hosts and tunnel chaining. You'll gain practical skills for securing connections in complex network environments.
SSH tunneling builds upon the Connection Protocol's channel multiplexing. A single SSH connection carries multiple independent logical channels—each can be a shell session, file transfer, or port forward.
Channel Types:
| Channel Type | Purpose | Initiated By |
|---|---|---|
| session | Shell, command execution, subsystems | Client |
| direct-tcpip | Local port forwarding data | Client |
| forwarded-tcpip | Remote port forwarding data | Server |
| x11 | X11 forwarding | Server |
| auth-agent@openssh.com | SSH agent forwarding | Server |
Channel Lifecycle:
Multiplexing Benefits:
This multiplexing architecture is what enables SSH tunneling—each forwarded connection becomes a channel within the SSH session.
OpenSSH's ControlMaster feature takes multiplexing further: multiple SSH client invocations can share a single connection. First connection establishes the control socket; subsequent connections reuse it. This eliminates key exchange overhead for repeated connections: ControlMaster auto, ControlPath ~/.ssh/sockets/%r@%h-%p, ControlPersist 600
Local port forwarding creates a listening port on the SSH client machine. Connections to this port are forwarded through the SSH tunnel to a destination accessible from the SSH server.
Use Case: Access services on or through the remote network that aren't directly reachable.
Syntax:
ssh -L [bind_address:]local_port:destination:destination_port user@ssh_server
Data Flow:
Practical Examples:
# Forward local port 8080 to remote web server's port 80
ssh -L 8080:remote-web:80 user@ssh-server
# Access as: http://localhost:8080
# Forward to PostgreSQL database on remote network
ssh -L 5433:db.internal:5432 user@bastion
# Connect as: psql -h localhost -p 5433
# Forward to remote localhost (service only on loopback)
ssh -L 3307:localhost:3306 user@database-server
# Connects to MySQL on database-server's loopback
# Bind to specific address (allow other machines to connect)
ssh -L 0.0.0.0:8080:internal-server:80 user@ssh-server
# Other machines can connect to your_ip:8080
# Multiple forwards in one connection
ssh -L 8080:web:80 -L 5433:db:5432 -L 3307:mysql:3306 user@bastion
Configuration File Syntax:
# ~/.ssh/config
Host production-tunnel
HostName bastion.example.com
User admin
LocalForward 5433 db.internal:5432
LocalForward 8080 web.internal:80
# Now: ssh production-tunnel
The destination (db.internal in the example) is resolved by the SSH server, not the client. This means you can access servers by their internal DNS names or private IPs that don't exist in the client's DNS. The SSH server acts as the connection source to the destination.
Remote port forwarding creates a listening port on the SSH server. Connections to this port are forwarded through the SSH tunnel back to a destination accessible from the SSH client.
Use Case: Expose local services to the remote network, enable reverse connections through firewalls.
Syntax:
ssh -R [bind_address:]remote_port:destination:destination_port user@ssh_server
Data Flow:
Practical Examples:
# Expose local dev server to remote network
ssh -R 8080:localhost:3000 user@public-server
# Others can access your dev server at public-server:8080
# Create a reverse shell callback (for NAT traversal)
ssh -R 2222:localhost:22 user@public-server
# Remote users can: ssh -p 2222 localhost (on public-server)
# This connects back to your local SSH server
# Expose internal service through a bastion
ssh -R 5432:internal-db:5432 user@bastion
# Bastion can now reach internal-db through you
# Gateway mode: allow remote hosts to connect to forwarded port
ssh -R 0.0.0.0:8080:localhost:3000 user@server
# Requires: GatewayPorts yes in server's sshd_config
Server Configuration Required:
# /etc/ssh/sshd_config on the server:
# Allow binding to all interfaces for remote forwards
GatewayPorts yes
# Or: GatewayPorts clientspecified (honor client's bind address)
# Allow any remote forwards (default is restricted)
PermitOpen any
# Or restrict: PermitOpen host1:port1 host2:port2
Remote port forwarding can expose services in unexpected ways. A user establishing -R forward could expose internal services to the SSH server's network. Restrict with AllowTcpForwarding no or AllowTcpForwarding local in sshd_config for users who shouldn't create remote forwards.
Dynamic port forwarding creates a SOCKS proxy server on the SSH client. Applications configured to use this proxy send all their traffic through the SSH tunnel, with destination determined per-connection.
Use Case: Route all traffic through the SSH server—browsing, application traffic, API calls—without creating individual port forwards.
Syntax:
ssh -D [bind_address:]local_port user@ssh_server
Data Flow:
Practical Usage:
# Create SOCKS5 proxy on port 1080
ssh -D 1080 user@ssh-server
# With background and no shell:
ssh -D 1080 -f -N user@ssh-server
# -f: background after authentication
# -N: no remote command (just forwarding)
# Bind to all interfaces (for sharing the proxy)
ssh -D 0.0.0.0:1080 user@ssh-server
Configuring Applications:
# curl via SOCKS5
curl --socks5-hostname localhost:1080 https://example.com
# Firefox: Settings > Network > Manual Proxy > SOCKS Host: localhost:1080
# Enable: Proxy DNS when using SOCKS v5 (for privacy)
# System-wide (Linux with proxychains):
# /etc/proxychains.conf: socks5 127.0.0.1 1080
proxychains firefox
# Git via SOCKS
git config --global http.proxy 'socks5://localhost:1080'
git config --global https.proxy 'socks5://localhost:1080'
SOCKS Protocol:
SSH implements SOCKS4/SOCKS5 proxy protocol:
SOCKS4:
SOCKS5:
Use socks5h:// or configure "remote DNS" to ensure domain names resolve at the SSH server, preventing DNS leaks.
When using SSH as a SOCKS proxy for privacy, ensure DNS queries also go through the proxy. SOCKS5 supports proxy-side DNS resolution. In curl, use --socks5-hostname (not --socks5). In browsers, enable 'Proxy DNS when using SOCKS v5'. Otherwise, DNS queries may leak to your local resolver, revealing your browsing activity.
Jump hosts (bastion hosts) provide controlled access to internal networks. SSH's ProxyJump feature establishes direct encrypted tunnels through intermediate hosts, with the connection encrypted end-to-end to the final destination.
Traditional Multi-Hop (Not Recommended):
# Manual hop: agent forwarding risks
ssh -A user@bastion
# Then from bastion:
ssh user@internal
ProxyJump (Recommended):
# Single command, end-to-end encryption
ssh -J user@bastion user@internal
# Multiple jumps
ssh -J bastion1,bastion2 user@final-destination
# With different users/ports
ssh -J admin@bastion:2222 root@internal
How ProxyJump Works:
The Security Advantage:
With ProxyJump:
Configuration File:
# ~/.ssh/config
# Define the jump host
Host bastion
HostName bastion.example.com
User admin
# Internal servers via jump
Host internal-*
ProxyJump bastion
User root
# Specific internal server
Host internal-web
HostName 10.0.1.50
ProxyJump bastion
Host internal-db
HostName 10.0.2.100
ProxyJump bastion
# Multi-hop chain
Host deep-internal
HostName 192.168.100.50
ProxyJump bastion,dmz-gateway
Usage with configuration:
# Direct access via configured jump
ssh internal-web
ssh internal-db
# All internal hosts work transparently
scp file.txt internal-web:/tmp/
sftp internal-db
ProxyJump (-J) is the modern simplified syntax. ProxyCommand offers more flexibility for custom proxy setups: ProxyCommand ssh -W %h:%p bastion. The -W flag creates a stdio tunnel to %h:%p through bastion. ProxyJump internally uses this mechanism but with better error handling and syntax.
Complex network topologies may require chaining multiple tunnels—combining local, remote, and dynamic forwarding through multiple hops.
Scenario: Access Internal Database Through Two Bastion Layers
Solution 1: Nested Local Forwards
# First tunnel: access bastion2 through bastion1
ssh -L 2222:bastion2:22 user@bastion1 -N &
# Second tunnel: through the first tunnel, forward database
ssh -p 2222 -L 5432:database:5432 user@localhost -N &
# Now connect to database
psql -h localhost -p 5432 -U dbuser
Solution 2: ProxyJump with LocalForward
# Combined in one command
ssh -J bastion1,bastion2 -L 5432:database:5432 user@bastion2 -N
# Or in config:
Host db-tunnel
HostName bastion2
ProxyJump bastion1
LocalForward 5432 database:5432
Dynamic Proxy Through Jump Hosts:
# SOCKS proxy through multi-hop
ssh -J bastion1,bastion2 -D 1080 user@internal-server -N
# All SOCKS traffic routes through both bastions to internal network
Reverse Tunnel for Callback Access:
# From inside a NAT'd network, establish reverse tunnel
ssh -R 2222:localhost:22 user@public-server
# From anywhere, connect to the NATted machine:
ssh -p 2222 user@public-server # Actually connects to the NATted host
Complex tunnel chains add latency, failure points, and debugging difficulty. Consider whether a VPN or direct network routing solution is more appropriate for routine access. SSH tunnels excel for ad-hoc access and when you lack network infrastructure control.
Beyond TCP port forwarding, SSH can create network-layer tunnels using TUN/TAP interfaces. This enables full IP routing through SSH—a lightweight VPN without dedicated VPN software.
SSH TUN Tunnels:
SSH's -w option creates point-to-point IP tunnels:
# Create TUN tunnel (requires root on both ends)
ssh -w 0:0 root@remote-server
# This creates:
# - tun0 on local machine
# - tun0 on remote machine
# Connected via SSH
Configuration Required:
# Server /etc/ssh/sshd_config:
PermitTunnel yes # or 'point-to-point' or 'ethernet'
# After connection, configure interfaces:
# Local:
ip addr add 10.0.0.1/30 dev tun0
ip link set tun0 up
# Remote:
ip addr add 10.0.0.2/30 dev tun0
ip link set tun0 up
# Add routes for traffic to go through tunnel:
ip route add 192.168.100.0/24 via 10.0.0.2 dev tun0
TUN vs TAP:
| Type | Layer | Carries | Use Case |
|---|---|---|---|
| TUN | Layer 3 (Network) | IP packets | IP routing, most common |
| TAP | Layer 2 (Data Link) | Ethernet frames | Bridge networks, VPNs needing L2 |
sshuttle: Transparent Proxy VPN
For simpler VPN-style access, sshuttle provides transparent proxy functionality without root on the server:
# Install sshuttle
pip install sshuttle
# Route specific networks through SSH
sshuttle -r user@ssh-server 192.168.0.0/16 10.0.0.0/8
# Route all traffic (full VPN mode)
sshuttle -r user@ssh-server 0.0.0.0/0
# Exclude local network
sshuttle -r user@ssh-server 0.0.0.0/0 -x 192.168.1.0/24
# With DNS forwarding
sshuttle --dns -r user@ssh-server 0.0.0.0/0
sshuttle Mechanism:
SSH VPN (TUN tunnels or sshuttle) works well for ad-hoc access when you have SSH but no VPN infrastructure, developer access to internal networks, and temporary routing of specific subnets. For production infrastructure with many users, dedicated VPN solutions (WireGuard, IPsec, OpenVPN) offer better performance, reliability, and management.
SSH tunneling provides powerful capabilities that require careful security consideration. Tunnels can bypass network security controls if not properly managed.
Server-Side Restrictions:
# /etc/ssh/sshd_config
# Disable all TCP forwarding
AllowTcpForwarding no
# Allow only local forwarding (client listens)
AllowTcpForwarding local
# Allow only remote forwarding (server listens)
AllowTcpForwarding remote
# Restrict where forwards can connect
PermitOpen db.internal:5432 web.internal:443
PermitOpen none # Disable all forwarding destinations
# Prevent remote forwards from binding to all interfaces
GatewayPorts no
# Disable stream local (Unix socket) forwarding
AllowStreamLocalForwarding no
# Per-user restrictions
Match User limited-user
AllowTcpForwarding no
PermitOpen none
X11Forwarding no
Monitoring Tunnel Usage:
SSH logs forwarded connections. Monitor for unusual patterns:
# In /var/log/auth.log or similar:
# "debug1: Local forwarding listening on 127.0.0.1 port 8080."
# "debug1: channel 2: new [direct-tcpip]"
# Set LogLevel to VERBOSE for more detail:
LogLevel VERBOSE
SSH tunnels alone shouldn't be your only access control. Network segmentation, host-based firewalls, application-level authentication, and monitoring all contribute to security. Assume tunnels exist and design internal network security accordingly.
We've explored SSH's powerful tunneling capabilities—from simple port forwards to complex multi-hop configurations to VPN-style network tunnels. Let's consolidate the essential knowledge:
What's next:
With tunneling mastered, we'll conclude with SSH key management best practices. The next page covers key generation, storage, rotation, the authorized_keys file, and enterprise-scale SSH key management strategies.
You now understand SSH tunneling comprehensively—from basic port forwards through SOCKS proxies to VPN-style tunnels. These skills enable you to securely access resources across network boundaries, traverse firewalls legitimately, and create encrypted channels for any TCP-based service.