In this guide
SSH Tunnelling Explained: Port Forwarding to Reach Devices Behind NAT
Summary: Your project dashboard runs on a Raspberry Pi in your hostel room, but the hostel Wi-Fi gives it no public IP. SSH tunnelling lets you reach it — securely, from anywhere — by forwarding ports through an encrypted SSH connection. This guide explains local, remote and dynamic forwarding, shows the exact commands, and warns you about the security pitfalls students hit most.
The problem: everything sits behind NAT
Campus hostels, homes and mobile networks hand your devices private addresses (192.168.x.x, 10.x.x.x). Your router's NAT table lets your Pi reach the internet, but nothing from the internet can start a connection back to it. There is no public IP pointing at your device, and you cannot ask your hostel warden to forward ports on the building router.
So your situations look like this:
- A Flask dashboard runs on a Raspberry Pi in your room at
localhost:5000. You want to show it to your project guide from another city. - An ESP32-CAM streams on the lab subnet. You want to view it from your laptop on a different network.
- You need to pull code onto a server that only accepts connections from one jump host.
SSH tunnelling solves all three. You already have an SSH client; a tunnel just reuses that encrypted connection to carry other traffic. If you can SSH from machine A to machine B, you can forward any TCP port between them — without installing a VPN, without touching a router, and with everything encrypted end-to-end over the SSH channel.
How a tunnel actually works
Forget magic: an SSH tunnel is port forwarding through an encrypted pipe. When you create a tunnel, your SSH client listens on one machine and forwards everything it receives through the SSH connection to the other machine, which then makes a local TCP connection to the destination service.
There are three forwarding modes. The trick is remembering who listens where:
- Local forwarding (-L): your local machine listens on a port and forwards into the remote network. You use this to reach something on the far side.
- Remote forwarding (-R): the remote machine listens on a port and forwards back to your machine. You use this to expose something local to the far side.
- Dynamic forwarding (-D): your local machine becomes a SOCKS proxy that forwards anywhere through the SSH connection. You use this for general browsing-like access.
Read the syntax as "-L [listen-side]:[listen-port]:[target-host]:[target-port]", where target-host is resolved from the perspective of the SSH server.
1. Local forwarding: reach a far-side service
Scenario: the Pi in your room runs a Node-RED dashboard at localhost:1880 on the Pi itself. You SSH into the Pi as pi-user. From your laptop:
ssh -L 8080:localhost:1880 pi-user@campus-host
Now open http://localhost:8080 on your laptop — you are talking to the Pi's dashboard. The breakdown:
- Your laptop listens on port 8080.
- Traffic goes through the encrypted SSH session to the Pi.
- The Pi connects to
localhost:1880— which is the Pi's localhost, not yours — and shuttles bytes back.
The service itself never touches the public internet. This is the pattern to demo a project to an examiner: tunnel the dashboard to your laptop, share your screen, and nobody can hit the Pi directly.
A second classic use: reaching a service on a third machine only visible to the SSH server. If the Pi can ping a lab NAS at 192.168.5.20:9000:
ssh -L 9000:192.168.5.20:9000 pi-user@campus-host
192.168.5.20 is resolved from the Pi's network. Your laptop never needed a route to that subnet.
2. Remote forwarding: expose a local service to the far side
Scenario: reversed — you are home on a laptop running a demo web app on port 3000, and a teammate's server (team-server) needs to reach it. From your laptop:
ssh -R 9000:localhost:3000 teammate@team-server
Now the server can open http://localhost:9000 and reach your laptop's app. Traffic flows back through the same SSH connection you initiated — no inbound firewall rule on your side needed.
The catch students forget: by default the remote port binds to 127.0.0.1 on the server, so only the server itself can use it. If you want the whole internet to reach it (rarely a good idea — see the security section), the server's sshd_config needs GatewayPorts yes, which campus/sysadmin machines almost never allow.
3. Dynamic forwarding: a SOCKS proxy through your SSH connection
ssh -D 1080 pi-user@campus-host
Point your browser's proxy settings at socks5://localhost:1080 and every site you visit now exits from the Pi's network, over the encrypted tunnel. Handy when a journal site or a campus portal only loads from the university network, or when you want to test how your deployed project behaves from a different network without leaving your desk.
Keeping tunnels alive and usable
SSH connections idle out. Three habits save your sanity:
- Run in the background with auto-reconnect:
ssh -f -N -L 8080:localhost:1880 pi-user@campus-host
-f backgrounds the client, -N skips the shell (no remote command needed). Pair with autossh on Linux for automatic restarts: autossh -M 0 -N -L 8080:localhost:1880 pi-user@campus-host.
- Tune keepalives so NAT boxes don't silently drop the idle TCP session. In
~/.ssh/config:
Host campus-host
ServerAliveInterval 30
ServerAliveCountMax 3
- Name your tunnels in the config file so you stop retyping flags:
Host pi-dashboard
HostName campus-host
User pi-user
LocalForward 8080 localhost:1880
ServerAliveInterval 30
Then ssh -f -N pi-dashboard is the whole workflow.
Security: the part students skip
A tunnel is a hole through a firewall, and holes get found. Respect these:
- Bind locally by default.
ssh -Lbinds to127.0.0.1unless you write0.0.0.0:before the listen port. Never bind0.0.0.0on a shared machine unless you intend everyone on that network to ride your tunnel. - A tunnel is not authentication. Whatever service you expose through a tunnel gets exactly the authentication it already had. Tunnelling your dashboard does not add a login page — put one there.
- Don't tunnel around policy. If your institute's network blocks something, there is usually a reason and often a rule. Tunnels for your own project demo are normal engineering; tunnels to evade network policy can violate your institute's IT rules.
- Key-based auth only. Tunnels usually run unattended, so they must not depend on typed passwords — and unattended passwords sitting in scripts are a leak. Use Ed25519 keys with a passphrase and
ssh-agent. - Watch remote forwarding.
-Rgives the remote side a path into your machine. Only accept remote forwards from hosts you administer, and prefer them scoped to127.0.0.1on that side.
Quick diagnosis table
| Symptom | Likely cause | Fix |
|---|---|---|
channel_setup_fwd_listener: cannot listen to port |
Port already in use locally | Pick another local port, or find the process with ss -tlnp |
| Tunnel connects but the page never loads | Target host/port wrong from the server's perspective | SSH in and curl the target from the server itself first |
| Tunnel dies after minutes of idle | NAT/firewall drops idle TCP | ServerAliveInterval 30 in config |
-R port unreachable from other machines |
Bound to 127.0.0.1 by default | Expected behaviour; needs GatewayPorts on the server to change |
Permission denied (publickey) |
Key not on the server | ssh-copy-id the key, check ~/.ssh permissions (700/600) |
FAQ
Can I tunnel UDP, like a game server or DNS?
OpenSSH port forwarding is TCP-only. UDP needs other tools (WireGuard, socat relays). Plan your project accordingly — most web dashboards and APIs are TCP anyway.
Is an SSH tunnel a VPN?
No. A VPN routes whole network layers; a tunnel forwards individual TCP ports through one connection. For a student project demo, tunnels are simpler and sufficient.
How is this different from port forwarding on my router?
Router port forwarding exposes a service to the entire internet permanently. An SSH tunnel exposes it only through your encrypted session, only while the session runs, and (with default binds) only to the machine you choose.
Can I do this on Windows?
Yes — OpenSSH ships with Windows 10/11, so ssh -L/-R/-D work in PowerShell. PuTTY also supports tunnels under Connection → SSH → Tunnels if you prefer a GUI.
Limitations
- TCP only, as noted — no UDP forwarding with stock OpenSSH.
- Throughput is single-connection and CPU-bound by encryption; fine for dashboards and APIs, not for bulk file transfer (use
scp/rsyncfor that). - A tunnel is only as reliable as the underlying SSH session; for always-on exposure, a proper reverse-proxy host or a tunnel service with a static endpoint is the grown-up answer.
- Some corporate and campus firewalls block outbound SSH (port 22) entirely — in that case, no client-side trick helps; you need an allowed egress path.