SSH Tunnels Explained: Local, Remote, and Dynamic Port Forwarding Mechanics

Secure Shell (SSH) is ubiquitous across modern IT infrastructure as the default protocol for encrypted remote administration. Beyond terminal emulation and shell execution, SSH incorporates a robust multiplexed transport layer capable of encapsulating arbitrary TCP connections inside an encrypted tunnel. This capability, commonly referred to as SSH port forwarding or SSH tunneling, serves as a versatile Swiss Army knife for systems engineers, DevOps teams, and security specialists.

However, SSH tunneling is an architectural double-edged sword. While it empowers engineers to securely reach isolated backend databases, bypass network address translation (NAT), and access management consoles across untrusted networks, it simultaneously provides adversaries and insider threats with an encrypted channel to bypass firewalls, exfiltrate data, and pivot across network segments (MITRE ATT&CK T1572: Protocol Tunneling and T1090: Proxy).

Understanding the low-level mechanics, packet flows, and configuration paradigms of SSH tunnels is critical for engineering secure remote access workflows and establishing defensive guardrails. This article provides an in-depth technical analysis of local, remote, dynamic, and multi-hop SSH tunnels, their associated threat vectors, and the governance controls required to secure them.


Executive Context & Threat Modeling

From an architectural standpoint, an SSH tunnel operates by multiplexing application-layer data over an established SSH connection (RFC 4254). The SSH client and SSH server negotiate symmetric encryption (such as AES-GCM or ChaCha20-Poly1305) and integrity checking over an asymmetric handshake (such as Ed25519 or ECDH key exchange). Once the secure channel is established, logical forwarding channels can be opened on demand without re-authenticating each encapsulated stream.

+-------------------------------------------------------------------------------+
|                             SSH Encrypted Tunnel (Port 22)                    |
|                                                                               |
|  [Client Application] <---> [Local Loopback] <===> [SSH Client]               |
|                                                        || (Encrypted Stream)  |
|                                                        \/                     |
|  [Target Service]     <---> [Remote Interface] <== [SSH Server]               |
+-------------------------------------------------------------------------------+

Strategic Value vs. Enterprise Risk

Security leaders and infrastructure architects must balance operational utility against attack surface expansion:

DimensionOperational BenefitSecurity & Compliance Risk
Perimeter TraversalEnables remote engineers to administer isolated internal databases without public IP exposure.Bypasses stateful perimeter firewalls and Network Intrusion Prevention Systems (NIPS).
Data in TransitEncrypts legacy, unencrypted protocols (e.g., HTTP, VNC, plain Redis) across public transit networks.Conceals malicious data exfiltration and Command and Control (C2) traffic from Deep Packet Inspection (DPI).
Pivoting & BastionsProvides single-entry jump host capabilities to enforce centralized logging and access control.Allows compromised jump hosts to act as pivot points for lateral movement across internal subnets.
Compliance AlignmentSatisfies ISO/IEC 27001 Control A.8.20 (Network Security) by securing remote data channels.Can violate segmentation policies if unauthorized egress tunnels bridge production and non-production environments.

Core Technical Mechanics: The Three Forwarding Modes

SSH port forwarding is categorized into three primary modes based on where the listener binds and the direction in which connections are dispatched: Local Forwarding (-L), Remote/Reverse Forwarding (-R), and Dynamic Forwarding (-D).

A. Local Port Forwarding (-L)

Local port forwarding configures the local SSH client to allocate a listening socket on the local host. Whenever a local application connects to this local port, the SSH client encrypts the traffic, forwards it across the SSH tunnel to the SSH server, and instructs the SSH server to open a TCP connection to the specified target destination host and port.

+--------------------------------------------------------------------------------+
| Local Forwarding Flow: ssh -L 8443:internal-db.corp:5432 user@bastion.corp     |
|                                                                                |
| 1. App connects to 127.0.0.1:8443 (Client Machine)                             |
| 2. SSH Client encapsulates packets -> Transits Port 22 -> Reaches Bastion Host |
| 3. SSH Server (Bastion) initiates TCP connection -> internal-db.corp:5432      |
+--------------------------------------------------------------------------------+

Syntax & Command Structure

ssh -L [local_bind_address:]local_port:target_host:target_port user@ssh_server

Concrete Use Case: Accessing a Private Database

Consider a PostgreSQL database running on 10.0.2.50:5432 inside an isolated private VPC subnet. The engineer possesses SSH access to an edge bastion server at bastion.example.com.

# Bind local port 5433 to the remote private PostgreSQL database via bastion
ssh -N -f -L 5433:10.0.2.50:5432 devuser@bastion.example.com

Flags explained:

  • -N: Do not execute a remote command (only forward ports).
  • -f: Fork the SSH process into the background immediately before execution.
  • -L 5433:10.0.2.50:5432: Listens on 127.0.0.1:5433 locally and instructs bastion.example.com to establish a connection to 10.0.2.50:5432.

The local developer can now point their database client to localhost:5433, and all queries will be securely routed through the bastion.

B. Remote / Reverse Port Forwarding (-R)

Remote port forwarding reverses the listener placement. The SSH client instructs the remote SSH server to allocate a listening socket on the remote machine. When a process connects to this remote port on the server, the SSH server routes the connection back across the encrypted SSH channel to the SSH client, which then establishes a connection to the specified target host and port.

+--------------------------------------------------------------------------------+
| Reverse Forwarding Flow: ssh -R 9000:localhost:3000 user@cloud-vps.net         |
|                                                                                |
| 1. Remote user connects to cloud-vps.net:9000                                  |
| 2. SSH Server (cloud-vps) forwards stream back across tunnel -> Local Client   |
| 3. Local SSH Client connects to localhost:3000 (Local Dev Web Server)          |
+--------------------------------------------------------------------------------+

Syntax & Command Structure

ssh -R [remote_bind_address:]remote_port:target_host:target_port user@ssh_server

Concrete Use Case: Exposing a Local Development Server Behind NAT

An engineer developing a webhook consumer locally on port 3000 needs to receive external test webhooks from a third-party service. The developer's workstation is behind a strict corporate NAT without public ingress.

# Instruct the public VPS to listen on port 8080 and forward traffic to local port 3000
ssh -N -R 8080:127.0.0.1:3000 deploy@cloud-vps.example.com

By default, the OpenSSH server binds remote forwardings to the loopback interface (127.0.0.1) on the remote server for security. To expose the port to external networks, the remote server's /etc/ssh/sshd_config must enable GatewayPorts:

# Enable external interfaces on remote port forwardings
GatewayPorts yes
# Or to allow the client to specify the bind address:
# GatewayPorts clientspecified

C. Dynamic Port Forwarding (-D) & SOCKS Proxying

While Local (-L) and Remote (-R) forwarding are point-to-point (forwarding a specific port to a pre-defined destination), Dynamic Port Forwarding transforms the SSH client into a local SOCKS (Socket Secure, RFC 1928) proxy server.

Instead of hardcoding a single destination IP and port, the application connects to the local SOCKS proxy and dynamically supplies the target hostname and destination port inside the SOCKS protocol handshake. The SSH client forwards these dynamic requests through the encrypted tunnel, and the SSH server initiates the outbound connection to the desired target on behalf of the client.

+--------------------------------------------------------------------------------+
| Dynamic Forwarding Flow: ssh -D 1080 user@bastion.corp                         |
|                                                                                |
| [Web Browser / CLI]                                                            |
|         | SOCKS5 Handshake (Target: int-wiki.corp:443, metrics.corp:9090)      |
|         v                                                                      |
| [Local SOCKS Listener (127.0.0.1:1080)]                                        |
|         | Encrypted SSH Tunnel (Port 22)                                       |
|         v                                                                      |
| [Bastion Host (SSH Server)]                                                    |
|         | Outbound TCP / DNS Queries                                           |
|         +---> int-wiki.corp:443                                                |
|         +---> metrics.corp:9090                                                |
+--------------------------------------------------------------------------------+

Syntax & Command Structure

ssh -D [local_bind_address:]local_port user@ssh_server

Concrete Use Case: Browsing an Internal VPC Network

# Establish dynamic SOCKS5 proxy on local port 1080
ssh -N -D 1080 admin@bastion.internal.net

Tools and browsers can now be configured to route all traffic through socks5://127.0.0.1:1080. For CLI tools lacking native proxy support, proxychains-ng or curl --socks5-hostname can be utilized:

# Perform an HTTP request resolving internal DNS through the SSH bastion
curl --socks5-hostname 127.0.0.1:1080 https://internal-dashboard.corp.local

Using --socks5-hostname (SOCKS5 with remote DNS resolution) prevents DNS leakage by ensuring hostname resolution occurs on the remote bastion rather than the local client.


Multi-Hop Forwarding and Bastion Architectures

In multi-tier enterprise network designs, target systems are frequently nested behind multiple boundary firewalls and intermediate jump boxes. Modern OpenSSH versions (7.3+) provide the -J (ProxyJump) directive, eliminating the historical need for complex netcat or ProxyCommand scripts.

+---------------+        +------------------+        +------------------+        +--------------------+
| Local Laptop  | -----> | Bastion Tier 1   | -----> | Bastion Tier 2   | -----> | Internal Target    |
| (Workstation) |        | (DMZ / Public)   |        | (Secure Core)    |        | 10.100.4.12:22     |
+---------------+        +------------------+        +------------------+        +--------------------+

ProxyJump CLI Syntax & OpenSSH Configuration

To connect directly to an internal target across multiple bastions in a single command:

ssh -J jumpuser@dmz-jump.corp.com:22,secuser@core-jump.corp.internal:22 appuser@10.100.4.12

Tunneling Across Jump Hosts via ~/.ssh/config

To streamline daily operations, engineer declarative configurations in ~/.ssh/config:

# First-hop DMZ Bastion
Host dmz-bastion
    HostName bastion-public.example.com
    User jumpadmin
    IdentityFile ~/.ssh/id_ed25519
    Port 22
 
# Core Internal Bastion (Chained through DMZ)
Host core-bastion
    HostName 10.0.1.10
    User coreadmin
    ProxyJump dmz-bastion
    IdentityFile ~/.ssh/id_ed25519
 
# Target Database Host with Automatic Local Tunneling
Host production-db-tunnel
    HostName 10.100.4.50
    User dbadmin
    ProxyJump core-bastion
    LocalForward 5432 127.0.0.1:5432
    IdentityFile ~/.ssh/id_ed25519

Executing ssh production-db-tunnel will automatically authenticate across both jump hosts, establish the end-to-end channel, and bind local port 5432 directly to the production database's PostgreSQL listener.

Threat Analysis: The Attack Surface of SSH Tunneling

Security architects must evaluate how attackers exploit SSH tunneling mechanisms within target environments.

1. Covert Egress & Data Exfiltration

Adversaries with compromised endpoints on an internal network can establish outbound reverse SSH tunnels (ssh -R) to external attacker-controlled infrastructure. Because egress firewalls frequently permit outbound TCP port 22 or outbound HTTPS (port 443, to which an external SSH server can be configured to listen), this creates a persistent, bidirectional interactive shell bypassing stateful boundary filters.

2. Lateral Movement & Network Pivoting

When an attacker gains credentials to a standard jump host, dynamic port forwarding (ssh -D) allows the attacker to route vulnerability scanners (e.g., Nmap, Nikto) directly against internal subnets that are completely unreachable from the public internet.

3. Loopback Hijacking

When a developer creates a local tunnel on a shared multi-user server (e.g., a shared compute cluster), binding to 0.0.0.0 or failing to restrict local bind addresses allows any other unprivileged local user on that server to connect to the forwarded port and access the remote resource without authenticating.


Actionable Hardening & Defensive Governance

Securing SSH infrastructure requires implementing defense-in-depth controls at the host, network, and policy levels.

Host-Level SSH Daemon Hardening (sshd_config)

System administrators should restrict tunneling capabilities on production servers and bastion hosts according to the principle of least privilege.

# /etc/ssh/sshd_config Hardening Directives
 
# Disable TCP port forwarding completely on high-security backend servers
AllowTcpForwarding no
 
# On Bastion hosts where forwarding is required, restrict binding to loopback only
GatewayPorts no
 
# Prevent remote execution of shell commands when users only require tunnel access
Match Group tunnel-only-users
    AllowTcpForwarding yes
    X11Forwarding no
    AllowAgentForwarding no
    PermitTTY no
    ForceCommand /usr/sbin/nologin
    # Explicitly restrict destinations reachable via port forwarding
    PermitOpen 10.0.2.50:5432 10.0.2.60:3306

Directives Breakdown:

  • PermitTTY no & ForceCommand /usr/sbin/nologin: Blocks the user from obtaining an interactive shell while allowing port forwarding to function.
  • PermitOpen host:port: Enforces strict destination allowlisting. The SSH server will reject any -L or -D request to targets not explicitly enumerated in this list.
  • AllowAgentForwarding no: Prevents malicious servers or attackers on intermediate jump boxes from hijacking the client's local SSH agent keys.

Network & Detection Controls

  1. Egress Filtering (NIST SP 800-41):
    • Restrict outbound connections on port 22 from internal workloads to strictly authorized corporate bastion IPs.
  2. Behavioral Protocol Inspection:
    • Utilize Zeek (formerly Bro) and Suricata to perform SSH session fingerprinting (e.g., HASSH signatures) and analyze session byte duration and flow heuristics to detect long-lived interactive tunneling sessions.
  3. Short-Lived SSH Certificates:
    • Replace static SSH public keys with OpenSSH Certificate Authorities (CA) issued via Identity Providers (e.g., HashiCorp Vault, Teleport, or Smallstep) with embedded validity windows limited to several hours.

Summary

SSH tunneling provides an exceptionally capable and encrypted mechanism for local, remote, and dynamic network routing. However, its dual nature as both an administrative asset and an adversary exfiltration pathway requires deliberate engineering controls. By implementing strict sshd_config configurations (PermitOpen, disabled TTYs, disabled agent forwarding), deploying short-lived SSH certificates, and monitoring encrypted session durations, organizations can leverage the operational agility of SSH tunnels while maintaining zero-trust segmentation and compliance integrity.

Love it? Share this article: