How to Configure SSH Port Forwarding and Tunneling

Learn how to configure SSH port forwarding and tunneling step-by-step for secure remote access and service forwarding. Beginner-friendly commands and examples for Linux and DevOps.

How to Configure SSH Port Forwarding and Tunneling

Introduction

SSH port forwarding (also called SSH tunneling) lets you securely send traffic from one machine to another by encapsulating it inside an SSH connection. It’s a powerful tool for bypassing firewalls, exposing services temporarily, or creating a simple SOCKS proxy. This guide explains the three main modes — local, remote, and dynamic forwarding — with practical examples and verification commands.

Understanding SSH port forwarding basics

At a high level, port forwarding maps a local or remote TCP port to another host and port over an SSH connection. The three modes are:

  • Local forwarding (-L): forward a local port to a remote host:port through the SSH server.
  • Remote forwarding (-R): request the SSH server listen on a remote port and forward connections back to a host:port on the client side (or another machine reachable from the client).
  • Dynamic forwarding (-D): create a SOCKS proxy on a local port to tunnel arbitrary TCP connections through the SSH server.

Common flags used in examples:

  • -N: do not run a remote command (useful for tunnels)
  • -f: put ssh in the background after authentication
  • -C: compress data
  • -p PORT: connect to custom SSH port

Example: show the concept in a quick command (local forwarding)

ssh -L 8080:internal-host:80 user@gateway.example.com -N

This command makes http://localhost:8080 act like http://internal-host:80, via gateway.example.com.

Commands table

Command pattern Purpose
ssh -L [lport]:[dst_host]:[dst_port] user@ssh_server Local port forward — open local lport, forward to dst_host:dst_port via ssh_server
ssh -R [rport]:[dst_host]:[dst_port] user@ssh_server Remote port forward — ssh_server listens on rport, forwards to dst_host:dst_port
ssh -D [lport] user@ssh_server Dynamic (SOCKS) proxy on local lport
ssh -f -N ... Background tunnel (use with -L/-R/-D)
ssh -p PORT user@host Connect to SSH on custom port

Local Port Forwarding (ssh -L)

Local forwarding is the most common use: you expose a remote internal service on a local port through the SSH server. Typical use: access a web server reachable only from a bastion host.

Example: access an internal web server (10.0.0.12:80) through a bastion (bastion.example.com) on your laptop port 8080:

ssh -L 8080:10.0.0.12:80 alice@bastion.example.com -N
# or run in background
ssh -f -N -L 8080:10.0.0.12:80 alice@bastion.example.com

Now open http://localhost:8080 in your browser and the traffic goes to 10.0.0.12:80 via bastion.

Verify the local port is listening (Linux/macOS):

ss -ltnp | grep 8080
# or
lsof -iTCP:8080 -sTCP:LISTEN

Test the forwarded service from your machine:

curl -I http://localhost:8080

Notes:

  • If you want other machines to connect to your forwarded local port, add -g to allow remote hosts to connect: ssh -g -L 0.0.0.0:8080:10.0.0.12:80 user@host -N. The server may also need to be configured to permit that.

Remote Port Forwarding (ssh -R)

Remote forwarding is the reverse: you ask the remote SSH server to open a port that forwards back to a host reachable from the SSH client. This is useful to expose a service running on your local machine to a remote machine (or the public internet) without configuring firewall/NAT.

Example: expose a local web server (localhost:3000) on remote host remote.example.com port 9000:

# From local machine:
ssh -R 9000:localhost:3000 bob@remote.example.com -N

Now anyone who can reach remote.example.com:9000 will be forwarded to your local port 3000 (via the SSH connection).

Bind address: by default, many servers bind remote port to localhost only. To bind to all interfaces (so others can connect), use the bind_address form or ensure GatewayPorts is set in the server's /etc/ssh/sshd_config:

# Allow non-loopback remote forwards (on the SSH server)
# /etc/ssh/sshd_config on remote server
GatewayPorts yes
systemctl restart sshd

Or specify an explicit bind address:

ssh -R 0.0.0.0:9000:localhost:3000 bob@remote.example.com -N

Verify from the remote server:

# On remote.example.com
ss -ltn | grep 9000
curl -I http://localhost:9000

Security note: exposing local services has risk. Use authentication, firewall rules, or restrict which remote port is opened.

Dynamic Port Forwarding (ssh -D) and SOCKS Proxy

Dynamic forwarding creates a local SOCKS proxy that applications can use to route arbitrary TCP connections through the SSH server. This is useful for browsing through the remote network or tunneling applications that support SOCKS.

Create a SOCKS proxy on local port 1080:

ssh -D 1080 -C -N alice@bastion.example.com

Configure your browser to use SOCKS5 proxy at 127.0.0.1:1080 (Firefox supports this in network settings). Or use curl with SOCKS:

curl --socks5-hostname 127.0.0.1:1080 http://ifconfig.me

This will show the remote server’s IP address as the origin for the HTTP request.

Dynamic forwarding routes DNS when using --socks5-hostname (or SOCKS5 with hostname resolution) so remote network resolves hostnames — helpful if the remote side has access to internal names.

Using SSH config and verifying connections

Long commands are tedious. Put tunnels in your ~/.ssh/config for ease and reuse.

Example entries:

Host bastion
  HostName bastion.example.com
  User alice
  IdentityFile ~/.ssh/id_rsa
  # Local forward
  LocalForward 8080 10.0.0.12:80

Host remoteexpose
  HostName remote.example.com
  User bob
  IdentityFile ~/.ssh/id_rsa
  # Remote forward (server must allow it)
  RemoteForward 9000 localhost:3000

Host socks
  HostName bastion.example.com
  User alice
  DynamicForward 1080

Start configured tunnels:

ssh bastion     # will set up LocalForward defined above
ssh -f socks    # background the DynamicForward proxy

Verification tips:

  • Use ss/netstat/lsof to check listeners.
  • Use curl or telnet/nc to test TCP connectivity.
  • Use tcpdump/wireshark on server side if you must trace packets.

Example verification using nc (netcat):

# From your machine after -L 8080:
nc -vz localhost 8080
# Should report succeeded

Common Pitfalls

  • SSH server disallows remote binds by default: check GatewayPorts in sshd_config and use proper bind address for -R.
  • Port conflicts and permission issues: non-root cannot bind ports <1024; pick higher ports or run with appropriate privileges.
  • Tunnel backgrounding without -f or -N: if you forget -N and don't specify a command, you might get an interactive shell or lose the tunnel when you close the terminal.

Next Steps

  • Practice: create each type of tunnel on a test VM or lab environment and confirm with curl/netcat.
  • Secure: use SSH keys, restrict allowed ports, and configure firewalls or access control on the tunnel endpoints.
  • Automate: add persistent tunnels via systemd units or autossh for resiliency in production scenarios.

👉 Explore more IT books and guides at dargslan.com.