SSH Tunneling (Technical Reference)
In-depth technical reference covering SSH protocol architecture, cryptographic mechanisms, channel types, and forwarding mechanics for system architects and security engineers.
What is SSH Tunneling?
SSH tunneling (SSH port forwarding) transports arbitrary TCP connections through an encrypted SSH session. The technique exploits SSH's inherent encryption to secure traffic that would otherwise be transmitted in cleartext. It was originally designed for secure remote shell access, but practitioners quickly recognized its utility for securing TCP-based protocols.
Unlike a VPN that operates at the network layer and can route all traffic, SSH tunneling is application-specific—it forwards only connections to configured ports. This makes it lightweight and secure for targeted use cases, but unsuitable for full-tunnel traffic routing.
SSH Protocol Architecture
SSH consists of three interconnected protocols organized in layers, as defined in RFC 4251. Each builds on the previous.
Transport Layer Protocol (RFC 4253)
The transport layer provides encrypted communication between client and server. It handles:
- Protocol version exchange (SSH-2.0 strings)
- Key exchange (algorithm negotiation)
- Server authentication (host keys)
- Encryption establishment
- Symmetric key derivation
- Message integrity (MAC) establishment
- Compression (optional)
The transport layer runs over TCP, typically on port 22. It provides a secure channel that the upper layers use for their own purposes.
Authentication Protocol (RFC 4252)
The authentication protocol operates over the transport layer and handles client authentication to the server. SSH-2 supports multiple authentication methods:
- publickey: RSA, DSA, ECDSA, or Ed25519 key-based auth
- password: Traditional username/password (encrypted by transport)
- hostbased: Authentication based on client host keys
- keyboard-interactive: Multi-step authentication (PAM integration)
The server lists supported methods in preference order; the client tries each until one succeeds. Public key authentication is preferred for automation.
Connection Protocol (RFC 4254)
The connection protocol multiplexes multiple logical channels over the transport layer. Each channel is a separate data stream identified by channel number. Channel types include:
- session: Remote command execution or shell
- direct-tcpip: Local port forwarding (client-initiated)
- forwarded-tcpip: Remote port forwarding (server-initiated)
- x11: X11 window system forwarding
- auth-agent@openssh.com: SSH agent forwarding
Port forwarding uses the direct-tcpip and forwarded-tcpip channel types. Each forwarded connection creates a new channel within the existing SSH session.
Key Exchange and Encryption
Key Exchange Algorithms
SSH key exchange (KEX) establishes shared secrets between client and server without transmitting private keys. The standard algorithm is Diffie-Hellman group exchange:
- Client and server negotiate a DH group (typically 2048-bit or 4096-bit MODP)
- Client generates ephemeral DH key pair and sends the public value
- Server generates ephemeral DH key pair, computes shared secret
- Both derive session keys from the shared secret using HKDF
Modern SSH implementations also support:
- Curve25519/ Curve25519-sha256: ECDH over Curve25519 (very fast, 256-bit security)
- ECDH NIST curves: P-256, P-384, P-521 via RFC 5656
- diffie-hellman-group-exchange-sha256: Client requests specific DH group size
Encryption Algorithms
Once key exchange completes, the transport layer negotiates a symmetric cipher. Modern OpenSSH prefers:
- ChaCha20-Poly1305: Stream cipher with built-in authentication. ChaCha20 encrypts 20 rounds on 64-byte blocks; Poly1305 provides keyed MAC. Immune to timing attacks that affect AES-GCM on some platforms.
- AES-GCM: Standard AES in GCM mode provides authenticated encryption. Requires hardware acceleration for optimal performance.
- AES-CTR with HMAC: Legacy mode where AES counter mode encryption is combined with HMAC for integrity (deprecated in OpenSSH 9+)
Message Authentication
SSH uses MAC-then-encrypt (historically) or AEAD ciphers. With AES-CTR + HMAC, each packet is MAC'd with:
MAC = HMAC(key, sequence_number || encrypted_packet)The sequence number prevents replay attacks. The MAC is computed over the encrypted packet, not plaintext, avoiding chosen-plaintext attacks.
Compression
SSH optionally compresses packets before encryption using zlib (RFC 1950). Compression is negotiated during key exchange and applies to all subsequent packets. Useful for slow links but disabled by default due to compression oracle vulnerabilities (CRIME, BREACH).
Types of Port Forwarding
Local Forwarding (-L)
Local forwarding binds a port on the SSH client machine. When a client connects to that local port, SSH forwards the connection through the encrypted tunnel to the SSH server, which then connects to the specified destination.
The destination is resolved by the SSH server, not the client. This is critical: ssh -L 8080:internal-web:80 gwmeans the gateway server resolves "internal-web" and connects to its port 80.
Connection Flow
- SSH client listens on local port 8080 (bind address determines accessibility)
- Application connects to localhost:8080
- SSH client accepts the connection, opens channel to server
- Server receives channel open request with destination info
- Server connects to internal-web:80
- Bidirectional data forwarding: client ↔ server ↔ destination
Bind Address Behavior
-L 8080:internal-web:80— binds 127.0.0.1:8080 (local only)-L *:8080:internal-web:80— binds 0.0.0.0:8080 (accessible from network)-L 0.0.0.0:8080:internal-web:80— same as above
Remote Forwarding (-R)
Remote forwarding binds a port on the SSH server machine. When a remote host connects to that port, the server forwards through the SSH tunnel to the client, which then connects to the specified local destination.
This enables access to internal services from outside a firewall— the internal machine runs SSH and forwards a port on a public server.
GatewayPorts Directive
By default, sshd binds remote forwardings to 127.0.0.1 only, making the port inaccessible from other hosts. To allow external access:
GatewayPorts no— only localhost can connect (default)GatewayPorts yes— bind to 0.0.0.0, any host can connectGatewayPorts clientspecified— client specifies bind address per connection
Connection Flow
- SSH server listens on remote port 8080
- External host connects to server:8080
- Server accepts connection, opens channel to client
- Client receives channel request with destination info
- Client connects to localhost:80
- Bidirectional data forwarding: server ↔ client ↔ local service
Dynamic Forwarding (-D)
Dynamic forwarding turns the SSH client into a SOCKS proxy server. The client listens on a local port and acts as a SOCKS server, parsing the SOCKS protocol to determine destination addresses.
Unlike local forwarding with a fixed destination, dynamic forwarding allows any TCP destination. The application specifies where it wants to connect; SSH handles the forwarding dynamically.
SOCKS4 vs SOCKS5
OpenSSH's dynamic forward supports SOCKS4 and SOCKS5:
- SOCKS4: CONNECT with user ID, IPv4 only
- SOCKS5: Supports IPv6, UDP associate, multiple auth methods
Application Integration
Applications must be configured to use the SOCKS proxy. Most browsers and curl support SOCKS5. The application makes a SOCKS CONNECT request to localhost:1080, SSH parses the destination, and forwards the connection through the tunnel.
Agent Forwarding
SSH agent forwarding (-A) allows the SSH agent running on your local machine to be accessed from remote servers. When you connect to a second server from the first, your local key is available for authentication.
The remote server cannot access your private key—it only receives challenges that the agent signs locally. This enables:
- Single private key for multiple server hops
- No private key stored on intermediate servers
- Agent protocol over SSH channel (auth-agent@openssh.com)
Security note: A compromised intermediate server with agent forwarding enabled can use your agent for the session duration. UseAddKeysToAgent and short session times to mitigate.
ProxyJump and Jump Hosts
ProxyJump (-J) simplifies accessing hosts through intermediate servers (jump hosts). Previously required complex port forwarding or netcat chains; ProxyJump automates the process.
# Direct connection (if gateway allows)
ssh internal-server.example.com
# Via jump host
ssh -J jump.example.com internal-server.example.com
# Multiple jump hosts
ssh -J j1.example.com,j2.example.com internal-server.example.com
# With specific user on jump host
ssh -J admin@jump.example.com internal-server.example.comBehind the scenes, ProxyJump opens a control master connection to the jump host and then connects through it to the destination. It uses the Connection Protocol's forwarded-tcpip channel type.
ControlMaster
When using ProxyJump with the same jump host repeatedly, ControlMaster reuses the existing connection, avoiding repeated authentication:
# In ~/.ssh/config
Host jump
HostName jump.example.com
User admin
ControlMaster auto
ControlPath ~/.ssh/sockets/%r@%h-%p
ControlPersist 600
Host internal-*
ProxyJump jumpServer-Side Controls
The sshd configuration file (/etc/ssh/sshd_config) controls forwarding behavior. Proper configuration prevents abuse while enabling legitimate use.
AllowTcpForwarding
# Allow all TCP forwarding (default)
AllowTcpForwarding yes
# Disable all TCP forwarding
AllowTcpForwarding no
# Allow only local forwardings (client → server direction)
AllowTcpForwarding local
# Allow only remote forwardings (server → client direction)
AllowTcpForwarding remoteWhen disabled, SSH responds with SSH2_MSG_REQUEST_FAILURE for forwarding requests. Note: this does not prevent port binding— it only prevents the forwarding from succeeding.
GatewayPorts
# Remote forwardings bound to localhost only (default)
GatewayPorts no
# Remote forwardings bound to all interfaces
GatewayPorts yes
# Client specifies bind address in the request
GatewayPorts clientspecifiedWhen set to clientspecified, clients can request specific bind addresses, but still cannot bind to 0.0.0.0 unless explicitly permitted in the authorized_keys file.
ChrootDirectory
For SFTP-only accounts, combine with chroot to isolate users:
Match Group sftponly
ChrootDirectory /srv/sftp/%u
ForceCommand internal-sftp
AllowTcpForwarding no
X11Forwarding noAuthorizedKeysCommand
For dynamic inventory systems, AuthorizedKeysCommand fetches keys from a database rather than files:
AuthorizedKeysCommand /usr/local/bin/get-ssh-keys %u
AuthorizedKeysCommandUser nobodyPuTTY Configuration
PuTTY provides graphical configuration for all SSH forwarding types. Each forwarding configuration persists in saved sessions.
Local Forwarding in PuTTY
- Enter hostname and port in Session (e.g., user@gateway.example.com, port 22)
- Connection type: SSH
- Save the session baseline before configuring tunnels
- Navigate to Connection > SSH > Tunnels
- Source port: 8080 (or any unused local port)
- Destination: internal-server:80 (resolved by server)
- Select "Local" radio button
- Click "Add" — displays as
L8080 internal-server:80 - Return to Session and Save
- Click "Open" to connect
Dynamic Forwarding in PuTTY
- Navigate to Connection > SSH > Tunnels
- Source port: 1080 (standard SOCKS port)
- Destination: leave empty
- Select "Dynamic" radio button
- Click "Add"
- Configure applications to use localhost:1080 as SOCKS proxy
Remote Forwarding in PuTTY
- Navigate to Connection > SSH > Tunnels
- Source port: 8080 (port on server to listen on)
- Destination: localhost:80 (resolved by client)
- Select "Remote" radio button
- Click "Add"
Command Line SSH Tunneling
OpenSSH provides flexible command-line options for all forwarding types:
# Basic local forwarding
ssh -L 8080:internal-web:80 user@gateway
# Local forwarding with explicit bind address
ssh -L 127.0.0.1:8080:internal-web:80 user@gateway
ssh -L *:8080:internal-web:80 user@gateway
# Remote forwarding (expose local service)
ssh -R 8080:localhost:80 user@public-server
# Remote forwarding bound to all interfaces on server
ssh -R *:8080:localhost:80 user@public-server
ssh -R 0.0.0.0:8080:localhost:80 user@public-server
# Dynamic forwarding (SOCKS proxy)
ssh -D 1080 user@gateway
# Multiple forwardings in one command
ssh -L 2222:internal-ssh:22 -L 5432:internal-db:5432 -L 8080:internal-web:80 user@gateway
# Agent forwarding
ssh -A user@gateway
# ProxyJump (jump host)
ssh -J jump.example.com internal-server.example.com
# Combined: forward + jump + agent
ssh -A -J admin@jump.example.com user@internal-server.example.comProxyChains
ProxyChains forces outgoing TCP connections through a chain of proxies. It intercepts socket calls via LD_PRELOAD, redirecting connections through the configured proxy sequence.
Implementation
When an application calls connect(), ProxyChains's preloaded library intercepts this and routes the connection through the first proxy in the chain. That proxy connects to the next proxy, and so on.
# Install
sudo apt install proxychains
# Configure in /etc/proxychains.conf
[ProxyList]
socks4 127.0.0.1 9050 # Tor
socks5 192.168.1.50 1080 # SSH dynamic forward
http 10.0.0.5 8080 # HTTP proxyChaining Modes
- dynamic_chain: Proxies contacted in order; skips dead proxies. Good for unreliable proxy chains.
- strict_chain: All proxies must respond; chain fails on any proxy failure. Default.
- round_robin_chain: Cycles through proxies in rotation.
- random_chain: Random proxy selection from the list.
DNS Proxying
Enable proxy_dns to prevent DNS leaks:
proxy_dns
# Now DNS queries go through the proxy chain
# Warning: adds latency to all DNS resolutionUsage Examples
# Chain curl through proxies
proxychains curl https://example.com
# Chain nmap scan (TCP only)
proxychains nmap -sT -p 22 192.168.1.0/24
# Chain SSH through proxy chain
proxychains ssh user@internal-server
# View what's happening
proxychains curl ifconfig.meLimitations
- TCP only—UDP and ICMP not supported
- Applications with statically linked binaries require recompilation with ProxyChains
- Some applications check for LD_PRELOAD and refuse to run
- Chain speed is limited by the slowest proxy
- Cannot chain through Tor hidden services (requires Tor with isolated proxy)
HTTP Tunnel (httptunnel)
httptunnel creates bidirectional virtual data paths through HTTP requests. The client (htc) encapsulates TCP data in HTTP requests; the server (hts) extracts and forwards it.
Architecture
# Server: hts listens for HTTP connections, forwards to local service
hts -F localhost:22 8080
# hts listens on TCP 8080, decapsulates HTTP, connects to SSH on localhost:22
# Client: htc connects to server, exposes local port
htc -F 2222:localhost:22 proxy.example.com:8080
# htc listens on TCP 2222, encapsulates, connects to proxy:8080Detection
httptunnel has distinctive traffic patterns that are easily detected:
- Regular HTTP POST/GET requests with suspicious Content-Length headers
- Persistent connections with continuous bidirectional data transfer
- HTTP requests without typical browser User-Agent patterns
- Traffic timing patterns (data sent at regular intervals)
Modern alternatives like obfs4 (Tor pluggable transport) provide similar functionality with better traffic obfuscation.
SSH Keys and Authentication
SSH tunneling works seamlessly with key-based authentication, enabling automated, passwordless connections.
Key Types and Generation
# Ed25519 (preferred) - fast, small keys, modern
ssh-keygen -t ed25519 -C "comment"
# RSA (legacy compatibility)
ssh-keygen -t rsa -b 4096 -C "comment"
# ECDSA (NIST curves, less preferred)
ssh-keygen -t ecdsa -b 521 -C "comment"Key Distribution
# Manual copy
cat ~/.ssh/id_ed25519.pub | ssh user@host "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys"
# Using ssh-copy-id
ssh-copy-id user@host
# Verify key is installed
ssh user@host cat ~/.ssh/authorized_keysSSH Config for Tunneling
# ~/.ssh/config
Host tunnel-gateway
HostName gateway.example.com
User admin
IdentityFile ~/.ssh/id_ed25519
LocalForward 2222 internal-ssh:22
LocalForward 5432 internal-db:5432
ServerAliveInterval 60
ServerAliveCountMax 3
Host jump-host
HostName jump.example.com
User admin
ForwardAgent yes
Host internal-*
ProxyJump jump-hostX11 Forwarding
X11 forwarding tunnels the X Window System protocol through SSH, displaying remote graphical applications locally.
Mechanics
- Client requests X11 forwarding during connection
- Server sets DISPLAY variable to localhost:10.0
- SSH creates virtual X server (xuccc) on display :10
- Applications' X protocol forwarded through SSH channel
- Client connects to local xuccc, which proxies to real X server
Security
X11 forwarding has historically been a security risk because X servers can execute commands on clients. OpenSSH mitigates this by providing a dedicated xuccc socket per session.
- -X: Untrusted X11 forwarding (restricts X11 extensions)
- -Y: Trusted X11 forwarding (full X11 access)
Configuration
# Command line
ssh -X user@server
ssh -Y user@server # trusted
# Server-side in sshd_config
X11Forwarding yes
X11DisplayOffset 10
X11UseLocalhost yes
# Client-side
Host *
ForwardX11 no
ForwardX11Trusted yesSecurity Considerations
Legitimate Uses
- Securing legacy protocols (IMAP, POP3, SMTP without TLS)
- Accessing internal corporate resources through bastion hosts
- Protecting data in transit for compliance (HIPAA, PCI-DSS)
- Encrypted access to databases, internal web apps, file shares
- SFTP/SCP for secure file transfers
- Port forwarding for development against remote services
Risks and Abuse
- Backdoor tunnels: Compromised servers can create reverse tunnels to external attacker machines
- Data exfiltration: Tunneled data is encrypted, evading DLP systems
- Persistence: Attackers with valid keys can create tunnels at will
- Lateral movement: Stolen keys + tunnels = pivot from internet to internal network
- Port scanning: Tunnel + internal network = internal port scanning from outside
Detection
While SSH tunnels are encrypted, anomalous patterns can indicate abuse:
- Unusual SSH connection durations (perpetual tunnels)
- Large data transfers through SSH servers
- Connections to unusual ports on SSH servers (not 22)
- SSH from servers that shouldn't have outbound access
- Known malicious SSH key patterns
Comparison with VPN
| Feature | SSH Tunnel | VPN |
|---|---|---|
| Layer | Application (TCP) | Network (IP) |
| Scope | Specific ports only | All traffic or routes |
| Setup | Per-connection config | System-wide interface |
| Protocols | TCP only | TCP, UDP, ICMP |
| Performance | Good for specific uses | Overhead for full tunnel |
| Client requirements | SSH client only | TAP/TUN driver, VPN client |
| Server requirements | SSHD running | VPN server daemon |
Timeline
Sources & Further Reading
- RFC 4251 - SSH Protocol Architecture
- RFC 4252 - SSH Authentication Protocol
- RFC 4253 - SSH Transport Layer Protocol
- RFC 4254 - SSH Connection Protocol
- SSH.com - SSH Protocol Explained
- SSH.com - SSH Tunneling
- SSH.com - SSH Key Exchange
- RFC 4419 - SSH Diffie-Hellman Group Exchange
- RFC 5656 - ECC for SSH
- OpenSSH Security
- ProxyChains Documentation