Skip to content
shellmap

Create an SSH tunnel (port forward)

Forward a local port through an SSH bastion to a remote service (or vice versa).

How to create an ssh tunnel (port forward) in each shell

Bashunix
ssh -N -L 8080:remote-host:80 user@bastion

`-L LOCAL_PORT:DEST_HOST:DEST_PORT user@bastion` — local forward. Connects to `localhost:8080` are tunneled through `bastion` to `remote-host:80`. `-N` tells SSH "no remote command — tunnel-only" (faster connect, no shell allocated). Add `-f` to background after auth. For SOCKS proxy: `-D 1080 user@bastion` (then point browser at `localhost:1080` SOCKS5). For reverse tunnel: `-R 8080:localhost:80 user@bastion` (bastion's `:8080` forwards back to your local `:80`).

Zshunix
ssh -N -L 8080:remote-host:80 user@bastion

Same external `ssh`. macOS ships OpenSSH client by default. Pattern: `ssh -fNL 8080:db.internal:5432 jumphost` (combined flags) backgrounds a tunnel for a database connection.

Fishunix
ssh -N -L 8080:remote-host:80 user@bastion

Same external. Fish-friendly status check: `if pgrep -f "ssh.*-L 8080"; echo tunnel up; else; echo down; end`.

PowerShellwindows
ssh -N -L 8080:remote-host:80 user@bastion

Windows 10 1803+ ships OpenSSH client (`C:\Windows\System32\OpenSSH\ssh.exe`) in PATH by default. Same flag syntax as Linux/macOS. To background in pwsh: `Start-Process ssh -ArgumentList "-fN", "-L", "8080:remote-host:80", "user@bastion"` (the `-f` flag for SSH itself also works on Windows OpenSSH ≥ 8.0).

cmd.exewindows
ssh -N -L 8080:remote-host:80 user@bastion

Same `ssh.exe` from Windows OpenSSH (Win10 1803+). For older Windows or PuTTY users: `plink -L 8080:remote-host:80 -N user@bastion` (PuTTY's CLI). PuTTY GUI also has Tunnels in `Connection → SSH → Tunnels` for persistent saved sessions.

Equivalents listed for Bash, Zsh, Fish, PowerShell, cmd.exe.

Gotchas & notes

  • **Three tunnel modes**: `-L LOCAL:DEST_HOST:DEST_PORT` (local forward — most common; connect to your localhost, reach remote service through bastion). `-R LOCAL:DEST_HOST:DEST_PORT` (remote forward — bastion exposes a port that forwards BACK to your machine; useful for letting a remote dev environment reach your local dev server). `-D LOCAL_PORT` (dynamic / SOCKS5 — turn the bastion into a SOCKS proxy; configure browser/curl to use `socks5://localhost:LOCAL_PORT`).
  • **`-N` / `-f` / `-g` flags**: `-N` says "no remote command — I only want the tunnel" (skips shell, faster). `-f` says "after authentication, fork into background" (so the SSH process detaches). `-g` says "allow the LOCAL forward to bind on non-loopback addresses" (default is `127.0.0.1:8080`; with `-g`, also binds `0.0.0.0:8080` — others on your network can use the tunnel). Without `-g`, only your machine can hit the forwarded port.
  • **NAT timeouts**: many home routers / corporate NAT devices drop idle SSH connections after 30–120 seconds. Add `ServerAliveInterval 60` and `ServerAliveCountMax 3` to `~/.ssh/config` (or pass `-o ServerAliveInterval=60`) to keep the tunnel warm with periodic keepalives. Without keepalives, a long-running tunnel for database access silently dies after a coffee break.
  • **Forwarding via a multi-hop bastion**: chained forwards work with `-J jumphost1,jumphost2 user@final`. The `-J` (ProxyJump, OpenSSH 7.3+) is cleaner than the old `-o ProxyCommand="ssh -W %h:%p jumphost"` idiom. Combine with `-L`: `ssh -J bastion -L 8080:db:5432 dev-host` forwards through both bastion and dev-host to reach `db:5432`.

Related commands

Related tasks