CodeBrowser

SSH Tunneling: A Practical Guide to Port Forwarding

SSH tunneling lets you reach services that aren’t open to the internet, share a local app with a remote server, or browse through a secure proxy, all over an encrypted SSH connection. Here’s how local, remote and dynamic port forwarding work, with copy-and-paste commands.

Local forwarding (-L)Remote forwarding (-R)Dynamic SOCKS (-D)PuTTY & OpenSSH

The basics

What is SSH tunneling?

An SSH tunnel carries other network traffic inside an encrypted SSH connection. You connect to a server you can already SSH into, and that server passes traffic on to another port or machine for you.

It’s the go-to way to reach a database that only listens on localhost, open an internal admin page, get past a restrictive firewall, or encrypt traffic for apps that don’t support encryption themselves. The only requirement is an SSH login on the server. New to SSH? Start with how to SSH into a server.

A bit of CodeBrowser history

In the mid-2000s, codebrowser.org was the home of zaTunnel, an SSH tunneling app for Windows Mobile phones and Pocket PCs, alongside the zaTelnet SSH client and zaSFTP. The phones are long gone, but SSH tunnels are as useful as ever.

Three types

Local, remote and dynamic port forwarding

Local (-L)

Bring a remote service to your computer. A port on your machine forwards through the SSH server to a service on the other side. Use it to reach a remote database or admin panel.

Remote (-R)

Send a local service out to the server. A port on the SSH server forwards back through the tunnel to your machine. Use it to share a local dev site or reach a computer behind NAT.

Dynamic (-D)

Turn the SSH server into a SOCKS proxy. Apps on your computer send any traffic through the tunnel. Use it to browse securely on public Wi-Fi or reach many internal sites.

Local port forwarding

Local port forwarding (ssh -L)

The pattern is -L local_port:destination_host:destination_port. The destination is resolved from the SSH server, so localhost means the server itself.

Your computerlocalhost:5433SSH serverencrypted tunnelDatabaselocalhost:5432

Reach a remote PostgreSQL database

ssh -N -L 5433:localhost:5432 user@server

Open an internal web app on another host

ssh -N -L 8080:intranet.local:80 user@server

With the first tunnel running, connect your database tool to localhost port 5433 and you’re talking to PostgreSQL on the server, even though port 5432 is never exposed to the internet. With the second, open http://localhost:8080 in your browser.

Remote port forwarding

Remote port forwarding (ssh -R)

The pattern is -R remote_port:destination_host:destination_port. This time the port opens on the SSH server, and the destination is resolved from your computer.

SSH serverlocalhost:9000Encrypted tunnelback to youYour computerlocalhost:3000

Share a local dev server with the remote machine

ssh -N -R 9000:localhost:3000 user@server

Reach a home computer behind NAT from your VPS

ssh -N -R 2222:localhost:22 user@vps

By default the remote port only listens on the server’s loopback address, so only users on that server can reach it. To expose it on the server’s public IP, the server needs GatewayPorts yes (or clientspecified) in /etc/ssh/sshd_config. Only do this if you understand who can reach that port.

Dynamic port forwarding

Dynamic port forwarding: a SOCKS proxy (ssh -D)

Instead of forwarding one port to one destination, -D starts a SOCKS5 proxy on your computer. Any app you point at it sends its traffic through the SSH server, which connects to whatever site or host the app asks for.

Browser or appSOCKS5 localhost:1080SSH serverencrypted tunnelAny websiteor internal host

Start a SOCKS5 proxy on port 1080

ssh -N -D 1080 user@server

Test it with curl

curl --socks5-hostname localhost:1080 https://ifconfig.me

In Firefox, open Settings → Network Settings, choose Manual proxy configuration, set SOCKS Host to localhost and port 1080, select SOCKS v5 and tick Proxy DNS when using SOCKS v5 so your DNS lookups go through the tunnel too.

Tips

Useful SSH tunnel options

Run in the background (-f) with no shell (-N)

ssh -f -N -L 5433:localhost:5432 user@server

Keep idle tunnels from dropping

ssh -N -o ServerAliveInterval=60 -o ServerAliveCountMax=3 -L 5433:localhost:5432 user@server

Reconnect automatically with autossh

autossh -M 0 -N -o ServerAliveInterval=30 -L 5433:localhost:5432 user@server

Tunnel through a jump host (-J)

ssh -N -J user@bastion -L 5433:localhost:5432 user@db-server

Use the same tunnel every day? Save it in ~/.ssh/config (our SSH Config Generator can build it), then start it with ssh -N db-tunnel:

Host db-tunnel
    HostName server.example.com
    User deploy
    LocalForward 5433 localhost:5432
    ServerAliveInterval 60

SSH tunneling with PuTTY

  1. Enter your server under Session as usual.
  2. Go to Connection → SSH → Tunnels.
  3. Type the Source port (for example 5433) and Destination (for example localhost:5432).
  4. Choose Local, Remote or Dynamic, then click Add.
  5. Save the session so you don’t have to repeat this, then click Open.

Server settings and security

Forwarding is controlled by AllowTcpForwarding in /etc/ssh/sshd_config. It’s on by default, but some hardened servers turn it off.

Tunnels give anyone with an SSH login a path into your network, so on shared servers consider AllowTcpForwarding no for users who don’t need it, and use PermitOpen to limit where tunnels can go. Restart sshd after changing these settings.

Troubleshooting

Common SSH tunnel errors and fixes

ErrorWhat it meansFix
bind: Address already in useSomething on your computer already uses that local port.Pick a different local port, or stop the other program or old tunnel.
channel 2: open failed: connect failed: Connection refusedThe tunnel works, but nothing is listening at the destination.Check the service is running and that the host and port are correct from the server’s point of view.
Warning: remote port forwarding failed for listen portThe SSH server couldn’t open the remote port.Choose a free port above 1024, or ask the admin whether AllowTcpForwarding is disabled.
Privileged ports can only be forwarded by rootPorts below 1024 need admin rights.Use a port above 1024, like 8080 instead of 80.
Tunnel drops after a few minutesAn idle connection was closed by a firewall or router.Add ServerAliveInterval 60, or use autossh.

FAQ

SSH tunneling questions, answered

Is SSH tunneling the same as a VPN?

Not quite. A VPN usually routes all of your device’s traffic through another network. An SSH tunnel forwards specific ports, or acts as a SOCKS proxy for apps you configure. It’s lighter and needs only an SSH login, but it isn’t a full VPN.

Is SSH tunneling secure?

Traffic inside the tunnel is encrypted between your computer and the SSH server. Traffic from the SSH server to the final destination is not encrypted by SSH, so for the best protection keep the destination on the server itself or on a trusted private network.

What does the -N option do?

It tells SSH not to run a remote command or open a shell. The connection only carries your tunnel, which is what you want for a dedicated port forward.

How do I stop an SSH tunnel?

Press Ctrl+C in the terminal running it. If you started it in the background with -f, find the process with ps aux | grep ssh and end it with kill followed by the process ID.

Can I forward more than one port at once?

Yes. Add as many -L, -R or -D options as you need to a single ssh command, or list several LocalForward lines for one host in your SSH config file.

Does SSH tunneling work on Windows?

Yes. The OpenSSH client built into Windows 10 and 11 supports the same -L, -R and -D options in PowerShell, and PuTTY and MobaXterm offer the same features through their settings.

Set up key-based login for your tunnels

Tunnels that run in the background or reconnect on their own work best with an SSH key instead of a password. Create one in seconds.

Open the SSH Key Generator