Pi-Tether

A self-hosted remote-access setup built on a Raspberry Pi. WireGuard and DuckDNS turn the home network into a private tunnel a MacBook can join from anywhere, to stream a Windows desktop with Sunshine and Moonlight (Remote Desktop stays as the fallback) and reach its localhost dev servers. Built to replace Tailscale or a rented VPS with something owned outright, at the cost of doing the networking and the ongoing maintenance by hand.

Status Live, Personal Use

Deployed at home and confirmed with real remote sessions over a cellular hotspot, now streaming with Moonlight by default. Security hardening is done; a full write-up is still in progress.

Build Type Solo

Planning, hardware build, WireGuard and DNS configuration, and end-to-end testing.

Primary Focus

Self-hosted networking, WireGuard VPN internals, and the trade-offs of DIY infrastructure versus a managed service.

Project Overview

Purpose: Give a MacBook a private tunnel into the home network from anywhere, so it can stream a Windows desktop and reach its localhost dev servers, with no files carried back and forth.

Problem: Commercial options solve this instantly. Tailscale is free for personal use and a small VPS runs a few dollars a month. Neither one is free forever, and neither one teaches how the tunnel actually works.

Approach: Self-host the WireGuard server on a Raspberry Pi already living on the home network, paying about $174 once instead of a subscription, and treating the build itself as a hands-on networking project.

Hardware

A Raspberry Pi 4 Model B (2GB), the official USB-C power supply, the official case, and a high-endurance 32GB microSD card, chosen for the continuous 24/7 write workload a standard card is not built for. All parts, no subscription, about $75 total.

Pi-Tether unit in its case, labeled and mounted on the wall
The Pi-Tether unit, labeled and mounted on the wall, connected over Wi-Fi since it can't sit next to the router.
Raspberry Pi 4 Model B board exposed inside its open case
Inside the case: a Raspberry Pi 4 Model B running Raspberry Pi OS Lite, headless.

How It Works

Internal addressing runs on a private 10.0.0.0/24 subnet: the Pi at 10.0.0.1, the desktop pinned to 10.0.0.2, the MacBook pinned to 10.0.0.3. Clients use a split tunnel, with AllowedIPs scoped to that subnet instead of 0.0.0.0/0, so only home-network traffic crosses the tunnel and everything else on the MacBook stays on its normal connection.

Only the Pi has a forwarded port From anywhere, a MacBook at 10.0.0.3 looks up the home address through DuckDNS and reaches the router through WireGuard on UDP port 51820. The router forwards that port to the Raspberry Pi WireGuard server at 10.0.0.1. The Pi updates DuckDNS through cron and sends a heartbeat to an AWS Lambda and DynamoDB status page every 60 seconds. The desktop PC runs Sunshine and RDP at 10.0.0.2 and reaches the Pi through its LAN address. Only the 10.0.0.0/24 subnet crosses the split tunnel. Only the Pi has a forwarded port Anywhere Internet Home network MacBook WireGuard, Moonlight, RDP Tunnel IP 10.0.0.3 DuckDNS Hostname to home IP Status page AWS Lambda, DynamoDB Heartbeat every 60 s Router UDP 51820 forwarded Raspberry Pi WireGuard server, 10.0.0.1 Desktop PC Sunshine, RDP, 10.0.0.2 look up WireGuard, UDP 51820 cron: update IP Pi's LAN IP Split tunnel: only 10.0.0.0/24 crosses the tunnel
The MacBook finds home through DuckDNS and joins the tunnel through the one forwarded port. The desktop stays home, so it reaches the Pi by its LAN address, and the Pi pushes its health out to a status page that stays up when the Pi does not.
Tunnel topologyFour roles in the setup, from the roaming MacBook down to the desktop hosting the actual session.
RoleDeviceWhat it does
01 · RemoteMacBook, AnywhereRuns the WireGuard app, Moonlight as the main streaming client, and Microsoft's Windows App for RDP as the fallback. Joins the tunnel over its normal internet connection, wherever that happens to be, cellular included.
02 · EdgeHome RouterA Spectrum-managed gateway with UDP 51820 forwarded to the Pi and a static DHCP reservation, so the forwarded port always points at the right device. Configured through the My Spectrum app, since there is no browser admin panel.
03 · ServerRaspberry PiRuns the WireGuard server and a cron job that keeps DuckDNS pointed at the home network's current public IP. It is the only device in the whole setup with a forwarded port.
04 · HostDesktop PCA WireGuard client reached over Wi-Fi on the home network. Runs Sunshine to stream its primary display to Moonlight, and still hosts RDP as the fallback; it was upgraded to Windows 11 Pro specifically to support RDP hosting.

From Remote Desktop to Sunshine + Moonlight

RDP through the tunnel worked, but over a cellular hotspot it felt a little laggy. Sunshine (the host, on the desktop) and Moonlight (the client, on the MacBook) are a game streaming pair that encodes the screen as video on the GPU and sends it over UDP. Tested side by side through the same tunnel, Moonlight was much smoother, so it is now the default way in, with RDP kept as the fallback.

RDP vs MoonlightSame MacBook, same iPhone hotspot, both through the WireGuard tunnel.
AspectRDP (Windows App)Moonlight
TransportTCP (the client picked TCP even though the host allows UDP)UDP
PictureA desktop session, with no frame rate readoutHEVC at 1920x1080 and 60 fps, encoded on the GPU (NVENC)
Latency44 to 45ms round trip55 to 71ms network, 3ms decode, under 2ms on the host
Frame deliveryNo readout59.9 to 61.8 fps with 0.00% dropped frames over a six minute run
FeelA little laggyFelt good; on the home network, way smoother than RDP
How it was set upThe settings that made the difference, and what was kept locked down.
PieceDetail
TuningTurning V-Sync off in Moonlight removed about 24ms of frame queue delay and the dropped frames it caused. A 20 Mbps cap held steady on the hotspot (real use averaged 13.4 Mbit/s); higher caps were too jittery. HEVC instead of AV1, because the MacBook Air's M2 has no hardware AV1 decode.
ExposureSunshine's installer adds firewall rules that accept connections from any address. Those were left out and replaced with two rules scoped to the home LAN and the tunnel subnet. Its web admin page has no rule at all, so it only opens on the desktop itself. Nothing is forwarded on the router; WireGuard is still the only way in from outside.
Why RDP StaysMoonlight streams one display. RDP still gives the full three monitor desktop, so it stays set up as the fallback.

Status Page & Remote Controls

The first status page ran on the Pi itself, which meant it went dark exactly when it was needed: during a Pi outage. It was rebuilt off the Pi as a small serverless page.

What runs todayThe status page lives off the Pi, so it can still report when the Pi goes quiet.
PieceWhat it does
Status PageAn AWS Lambda (Function URL, no API Gateway) with DynamoDB, at $0 a month. The Pi pushes a heartbeat every 60 seconds with WireGuard peer status, uptime, memory, and load, so nothing ever has to connect in to the Pi.
Remote RestartA token gated Restart Pi button queues a graceful restart that the Pi runs on its next check-in. It can't help if the Pi is fully down.
Wake-on-LANRetired after a motherboard swap: the new board's Wi-Fi chip has no wake support in Windows, and the system only supports S3 sleep. Ethernet would work, but there is no cable run, so the desktop has to be awake to connect.
HardeningSSH uses a dedicated key pair with password auth off, ufw denies all incoming traffic except the WireGuard port (SSH is limited to the LAN and the tunnel), and unattended-upgrades keeps the Pi patched.

Tools + Build

Raspberry Pi 4 Model BRaspberry Pi OS LiteWireGuardCurve25519ChaCha20-Poly1305DuckDNScronUDP 51820Static DHCP ReservationSplit-Tunnel VPNWindows 11 ProSunshineMoonlightHEVC / NVENCRemote Desktop ProtocolMicrosoft Windows AppSpectrum GatewayAWS LambdaDynamoDBPythonsystemdufwWake-on-LANOpenSSH

Decisions Worth Calling Out

Self-Hosted Over Tailscale or a VPS. Chose to run the WireGuard server itself rather than a managed mesh network or a rented VPS, to keep ongoing cost near zero and turn the build into a hands-on networking project. Accepted trade-off: more setup effort and ongoing maintenance, patching and monitoring, than a managed service would require.

Pi 4 Over the Cheaper Pi Zero 2 W. The Zero 2 W is Wi-Fi only with no ethernet port, a downside even before placement is factored in. The Pi 4 costs more but leaves headroom for later additions, like a status dashboard or automation scripts, with no hardware change.

Wi-Fi Over Ethernet for the Pi. The Pi cannot physically sit near the router, so it connects over Wi-Fi. WireGuard does not care about the underlying transport, and tunnel traffic sits far below Wi-Fi's bandwidth ceiling, a minor reliability trade for real placement flexibility.

High-Endurance microSD Over a USB SSD. A continuous 24/7 write workload wears out a standard microSD card. A high-endurance card, the same class used in dash cams and security cameras, is built for exactly that. Actual storage needs are a few gigabytes, well under what an SSD's capacity is priced for.

Split Tunnel, Not Full Tunnel. AllowedIPs is scoped to the tunnel's internal subnet rather than 0.0.0.0/0, so only traffic bound for the home network crosses the tunnel and the MacBook's normal internet use is untouched.

Skipped Raspberry Pi Connect. Left off Raspberry Pi's own cloud-relay remote access option. It is redundant with the WireGuard setup already built and reintroduces the managed-service dependency this project was built to avoid. SSH covers admin access, WireGuard covers everything else.

Off the Pi, Pushed Not Polled. The first status page was bound to the Pi's WireGuard address (10.0.0.1:8080), so the tunnel was its access gate. That also meant it went dark during the very outages it was meant to report. Moving it to a Lambda that the Pi pushes a heartbeat to keeps it up when the Pi is not, and nothing has to connect in to the home network.

Retired Instead of Chased. Wake over Wi-Fi worked on the original board, but only from sleep, and it needed a Windows change plus two BIOS settings the Windows side didn't cover: Resume By PCI-E Device enabled, ErP Ready disabled, since ErP mode cuts standby power to onboard devices and silently kills WOL. After a motherboard swap, the new Wi-Fi chip had no wake support at all. Ethernet would fix it, but with no cable run to the desktop, Wake-on-LAN was retired rather than worked around.

Moonlight by Default, RDP as the Fallback. Moonlight's UDP video stream was much smoother than RDP over the same tunnel, so it became the default. It only streams one display, though, and RDP still gives the full three monitor desktop, so RDP stays set up instead of being removed.

Notable Challenges

Problems worth documenting because none of them produced a normal error message.

A Tunnel That Looked Active and Wasn't

Symptom: The first real test, the desktop connecting through the public DuckDNS hostname, failed silently: WireGuard showed the tunnel as active, bytes sent but zero received, no handshake, and no error anywhere.

Fix: two endpoints, one per role. Diagnosed as NAT hairpinning: the router will not loop traffic back inside when it is addressed to its own public IP from a device already on the home network. Confirmed by pointing the desktop's endpoint at the Pi's LAN IP instead, which handshook instantly, then made that permanent. The desktop, which never leaves home, uses the Pi's LAN IP; the MacBook, which roams, keeps the public DuckDNS hostname.

Windows 11 Home Can't Host RDP

Symptom: With the tunnel verified end-to-end via wg show on the Pi, live handshakes and real traffic counters for both peers, RDP itself still failed. Not a bug: Windows 11 Home does not support hosting a Remote Desktop session at all.

Fix: weighed three fixes, paid for the supported one. Compared a $99 one-time Pro upgrade against RDP Wrapper (free, unofficial, fragile against Windows updates) and VNC (free, works on Home, different client and less WAN-optimized). Took the Pro upgrade to keep the build fully supported. Final proof was the MacBook completing a real RDP session over a cellular hotspot, confirming the full chain end-to-end from an actual outside network.

A Wi-Fi Chip That Failed Silently

Symptom: The tunnel was up and the round trip was a healthy 22 to 28ms, but RDP crawled and test traffic lost more than half its packets. Signal strength and every network counter on the Pi looked clean.

Fix: isolate one segment at a time, then reboot on the signature. MacBook to Pi was fine, but anything the Pi sent collapsed, even straight to the internet with no tunnel involved. The kernel log showed why: a bus timeout between the Pi's processor and its Wi-Fi chip, followed by thousands of driver errors. A reboot restored full speed (Pi to MacBook went from under 1 Mbit/s to 34). A small watchdog now reboots the Pi automatically when that error pattern appears, keyed to the driver errors so a router or ISP outage can never trigger it.

What It Feels Like

Real-world performanceMeasured over the live tunnel, not a lab benchmark.
AspectResult
Latency16ms round trip and about 19.8 Mbps available bandwidth from a school network, read straight off the Windows App's connection stats overlay. Comfortably under the 50 to 70ms range where typing and mouse input start to feel laggy.
Dev serversClose to native: hitting a localhost dev server through the tunnel is just a normal HTTP request with the same small overhead.
CeilingFull-screen RDP smoothness is capped by the home ISP's upload bandwidth, common on cable internet, not by the Pi or by WireGuard itself.

What It Actually Cost

Total spendOne-time hardware and licensing costs, plus what it runs day to day.
CategoryCostDetail
Hardware$75, one timePi 4 Model B (2GB), official USB-C power supply, official case, and a high-endurance 32GB microSD card.
Licensing$99, one timeWindows 11 Home to Pro upgrade, needed only because RDP hosting requires it.
Ongoing$0 a monthDuckDNS, WireGuard, Sunshine, and Moonlight are all free, and the status page runs at $0 a month on AWS. The only recurring cost is maintenance time, not money, versus Tailscale's free tier or a low-cost VPS billed every month.

What's Next

  • Hardening write-up. SSH key-only auth, ufw, and unattended-upgrades, plus the Raspberry Pi OS defaults that quietly undid two of them (cloud-init turning password auth back on at boot, logs kept in memory only), documented as deliberate security decisions rather than just setup steps.
  • Infrastructure as code. A script or Ansible playbook to provision the whole build reproducibly, instead of repeating the manual setup by hand.