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.

Period1995 - Present

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:

  1. Client and server negotiate a DH group (typically 2048-bit or 4096-bit MODP)
  2. Client generates ephemeral DH key pair and sends the public value
  3. Server generates ephemeral DH key pair, computes shared secret
  4. 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

  1. SSH client listens on local port 8080 (bind address determines accessibility)
  2. Application connects to localhost:8080
  3. SSH client accepts the connection, opens channel to server
  4. Server receives channel open request with destination info
  5. Server connects to internal-web:80
  6. 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 connect
  • GatewayPorts clientspecified — client specifies bind address per connection

Connection Flow

  1. SSH server listens on remote port 8080
  2. External host connects to server:8080
  3. Server accepts connection, opens channel to client
  4. Client receives channel request with destination info
  5. Client connects to localhost:80
  6. 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.com

Behind 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 jump

Server-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 remote

When 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 clientspecified

When 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 no

AuthorizedKeysCommand

For dynamic inventory systems, AuthorizedKeysCommand fetches keys from a database rather than files:

AuthorizedKeysCommand /usr/local/bin/get-ssh-keys %u
AuthorizedKeysCommandUser nobody

PuTTY Configuration

PuTTY provides graphical configuration for all SSH forwarding types. Each forwarding configuration persists in saved sessions.

Local Forwarding in PuTTY

  1. Enter hostname and port in Session (e.g., user@gateway.example.com, port 22)
  2. Connection type: SSH
  3. Save the session baseline before configuring tunnels
  4. Navigate to Connection > SSH > Tunnels
  5. Source port: 8080 (or any unused local port)
  6. Destination: internal-server:80 (resolved by server)
  7. Select "Local" radio button
  8. Click "Add" — displays as L8080 internal-server:80
  9. Return to Session and Save
  10. Click "Open" to connect

Dynamic Forwarding in PuTTY

  1. Navigate to Connection > SSH > Tunnels
  2. Source port: 1080 (standard SOCKS port)
  3. Destination: leave empty
  4. Select "Dynamic" radio button
  5. Click "Add"
  6. Configure applications to use localhost:1080 as SOCKS proxy

Remote Forwarding in PuTTY

  1. Navigate to Connection > SSH > Tunnels
  2. Source port: 8080 (port on server to listen on)
  3. Destination: localhost:80 (resolved by client)
  4. Select "Remote" radio button
  5. 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.com

ProxyChains

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 proxy

Chaining 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 resolution

Usage 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.me

Limitations

  • 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:8080

Detection

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_keys

SSH 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-host

X11 Forwarding

X11 forwarding tunnels the X Window System protocol through SSH, displaying remote graphical applications locally.

Mechanics

  1. Client requests X11 forwarding during connection
  2. Server sets DISPLAY variable to localhost:10.0
  3. SSH creates virtual X server (xuccc) on display :10
  4. Applications' X protocol forwarded through SSH channel
  5. 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 yes

Security 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

FeatureSSH TunnelVPN
LayerApplication (TCP)Network (IP)
ScopeSpecific ports onlyAll traffic or routes
SetupPer-connection configSystem-wide interface
ProtocolsTCP onlyTCP, UDP, ICMP
PerformanceGood for specific usesOverhead for full tunnel
Client requirementsSSH client onlyTAP/TUN driver, VPN client
Server requirementsSSHD runningVPN server daemon

Timeline

1995SSH-1 released — Tatu Ylonen creates SSH as secure replacement for rlogin, telnet, and rsh
1996SSH-2 standardized — improved security, exchange of security attributes
1999OpenSSH founded — open-source SSH-2 implementation
2000PuTTY 0.50 — popular Windows SSH client with tunneling support
2006RFC 4250-4256 — SSH protocol standardized
2014OpenSSH adds ChaCha20-Poly1305 and Curve25519
2019SSH.com acquired Tectia — enterprise SSH solutions consolidated
2020OpenSSH 8.4 deprecates SHA-1 for public keys