The problem in context
SSH into a home Raspberry Pi gives you a black text console — perfect for editing configs, useless for anything with a mouse. Most of the time that is fine, but there is always a stubborn 10%: a graphical config tool with no CLI equivalent, or a camera app whose live preview you genuinely need to see. For those, you need VNC — screen-sharing that streams the Pi's desktop to your laptop and sends clicks and keystrokes back.
The naive path is to install a VNC server, forward port 5900 on the router, and connect. Two things kill that plan. First, a Pi behind carrier-grade NAT has no router port to forward — the same constraint that forces an outbound tunnel for SSH in the first place. Second, and more important, exposing raw VNC to the internet is a genuinely bad idea. The protocol's own spec, RFC 6143, concedes its password check is "cryptographically weak and is not intended for use on untrusted networks," and recommends tunneling it inside IPsec or SSH. So the real problem is not "how do I reach VNC" but "how do I get a graphical desktop remotely without adding any new attack surface."
The principle
The principle here is that an SSH tunnel is not limited to one port — the same encrypted connection that already carries SSH can carry a desktop too, so you multiplex a pipe you already own rather than opening a new one. If a reverse SSH tunnel already forwards the Pi's port 22 out to a cloud bridge, VNC is just a second reverse forward on that same connection. Nothing new is exposed; the Pi still only ever dials outward; the traffic is wrapped in SSH the entire way.
The mental model that makes it click is three ports and one path:
- Port 5900 on the Pi is where the VNC server already listens — per the official Raspberry Pi remote-access docs, a VNC server (wayvnc on current Raspberry Pi OS) is built in, so nothing gets installed.
- Port 5901 on the bridge becomes the public front door that forwards into the tunnel.
- Everything hitting
EC2:5901gets redirected, encrypted, down toRPi:5900.
VNC Viewer --> EC2:5901 --> encrypted SSH tunnel --> RaspberryPi:5900 --> desktop image back the same way
There is a detailed treatment of the full VNC-over-tunnel build with the port map and security-group walkthrough; the mechanical change is a single added flag. An existing systemd tunnel unit carrying only SSH looks like:
ExecStart=/usr/bin/ssh -i /home/pi/.ssh/id_tunnel ... -R 2222:localhost:22 tunnel@YOUR-EC2-IP
Adding a second -R — the same remote-forwarding mechanism from the ssh(1) man page, just another instance of it — carries the desktop:
ExecStart=/usr/bin/ssh -i /home/pi/.ssh/id_tunnel ... -R 2222:localhost:22 -R 5901:localhost:5900 tunnel@YOUR-EC2-IP
After systemctl daemon-reload and a restart, ps aux | grep ssh | grep tunnel showing -R 5901:localhost:5900 in the running process is the checkpoint that the tunnel picked up the new forward.
Trade-offs
Multiplexing an owned tunnel beats every VNC-in-the-cloud SaaS on the axes that matter for a homelab, but the choice carries real security decisions you have to make deliberately:
| Decision | Convenient / default | Safer choice | Why it matters |
|---|---|---|---|
| Bridge port binding | Loopback (SSH default) | GatewayPorts yes |
A loopback-bound forward is reachable from the bridge but never from your laptop |
| Security-group source |
0.0.0.0/0 on 5901 |
Scope to your own IP | Open means the whole internet can reach your VNC login prompt |
| VNC transport | Raw VNC over the internet | Wrapped in the SSH tunnel | The protocol's own auth is weak by its spec's admission |
| VNC server | RealVNC free tier | TigerVNC / wayvnc | RealVNC increasingly pushes cloud-brokered connections, the opposite of a self-owned tunnel |
The GatewayPorts line is the one that eats an hour. Check the bridge with sudo ss -tulpn | grep 5901 and you will likely see it bound to 127.0.0.1 — localhost only, reachable from the bridge but not from across the internet. This is SSH's safe default; the sshd_config(5) man page notes sshd binds remote forwardings to the loopback address unless told otherwise. One line, GatewayPorts yes, plus systemctl restart ssh and a tunnel restart, rebinds it to 0.0.0.0:5901. The rule worth internalizing: a 127.0.0.1-bound forward is not broken, it is just missing GatewayPorts.
The deeper trade-off is ownership versus convenience. Routing desktop pixels through a third-party VNC broker is easier, but the whole point of the owned tunnel is that no pixels ever leave infrastructure you control — the bridge is your box, the transport is plain SSH, and the only thing installed is a viewer on your laptop. That is worth defending, which is why the RealVNC-pushing-toward-cloud-brokering drift matters: if a direct address:port connection gets gated behind a subscription, TigerVNC or wayvnc speaks the same protocol over the same -R 5901:localhost:5900 forward, and the tunnel does not care which server sits on port 5900.
How to adopt
This assumes the reverse SSH tunnel already exists — VNC rides on top of it, so build that foundation first if you are starting from scratch.
-
Add the second
-Rforward to the existing systemd unit and confirm it in the running process. -
Flip
GatewayPorts yeson the bridge, restart sshd, then restart the tunnel on the Pi so the forward re-establishes against the updated server. Order matters — re-check ports on the bridge after each restart. A localnc -zv localhost 5901on the bridge confirms the forward actually reaches the Pi before you touch the firewall. -
Open exactly one firewall port, scoped tight: a security-group inbound rule for TCP 5901 sourced to your own IP, not
0.0.0.0/0. If you need roaming access, keep the VNC password strong and layer SSH key auth in front. -
Point a viewer at
EC2-IP:5901, enter the VNC password, and the desktop fills the window — mouse and keyboard, live. - Make the session pleasant, not just working. Drop the viewer's color depth for a big responsiveness win over a home uplink; set a sane fixed resolution on the headless Pi so the remote window fits your laptop; and prefer wired Ethernet on the Pi, which removes a whole class of "why is this laggy" questions before you ask them.
Two operational choices keep it maintainable. Keep the VNC forward in the same systemd unit as SSH — one process, one thing to monitor, one place to look when something is wrong, and removing VNC is deleting one flag and reloading. And understand the reconnection behavior: because VNC rides the same managed tunnel, a network drop restarts both, and the viewer simply reconnects once the forward is back, usually within the ten-second restart window. A frozen screen after a Wi-Fi blip is not a failure; it comes back on its own.
Where this goes next
The immediate generalization is that any service on the Pi can ride this same connection — a metrics endpoint, a database, an internal dashboard — each one a -R forward on the tunnel you already trust, none of them exposed at home. Once you have internalized "one outbound connection, many services multiplexed back," the pattern scales from a single Pi to a small fleet without ever revisiting the router.
The larger direction of travel is that this hand-built setup is a concrete instance of where secure remote access is heading industry-wide: identity- and key-gated access to individual services over an outbound-initiated, encrypted transport, rather than exposed inbound ports on a trusted network. That is the same premise underneath zero-trust access and the managed desktop-brokering services — the difference is only who owns the hops. Building it yourself, one -R flag at a time, is the best way to understand what those products are actually doing, and to keep the option of owning every hop when the traffic is your own desktop. The tooling will keep getting easier; the principle of multiplexing an owned, outbound tunnel is the part worth keeping.
Sources & further reading
- RFC 6143 — The Remote Framebuffer Protocol — VNC's own spec, on why its auth is weak and should be tunneled.
- Raspberry Pi — Remote access documentation — enabling the built-in VNC server.
-
OpenSSH
ssh(1)man page andsshd_config(5)man page — the-Rforward andGatewayPorts. - TigerVNC — an open-source VNC server/viewer that speaks the same protocol without a subscription.
- A longer reference treatment of VNC over a reverse SSH tunnel — the port map and AWS security-group walkthrough behind this framing.
Top comments (0)