Skip to content

emeraldtarek/pi-wireguard-bootstrap

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

19 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

pi-wireguard-bootstrap

Set up your own WireGuard VPN with a Raspberry Pi at home as the exit gateway and a cheap VPS as the public-facing hub. Designed to work even when your home ISP uses CGNAT (Carrier-Grade NAT), which makes traditional "port-forward at home" setups impossible.

Tested on:

  • Raspberry Pi Zero W (ARMv6) / Pi 4 / Pi 5 running Raspberry Pi OS Lite (Bookworm or Trixie)
  • Any cheap Linux VPS (Ubuntu 22.04+, Debian 12+) — DigitalOcean, Hetzner, Vultr, Oracle Free, etc.

What you get

Phone / Laptop (anywhere) ──UDP──> VPS (public IP) ──UDP──> Pi at home
                                                                │
                                                                ▼ MASQUERADE
                                                         Home internet
                                                       (exits with home IP)
  • VPS is the meeting point. Both phone and Pi dial outbound to it; nothing needs to be reachable at home from the public internet. This bypasses CGNAT.
  • Phone traffic exits via your home IP. Useful for geo-locked services that whitelist your home address, or to look like you're at home when you're not.
  • Home IP can change freely. The Pi just re-dials the VPS from its new IP.
  • Works from countries with VPN restrictions — the hub uses a non-default UDP port. If DPI gets aggressive one day, you can wrap WireGuard in wstunnel/udp2raw/v2ray (not automated here yet).

Prerequisites

Hardware + OS

  • A Raspberry Pi at home with Raspberry Pi OS Lite flashed.
  • A small Linux VPS (Ubuntu / Debian) with public IPv4, ~$4–6/mo. Pick a region outside the country you'll be travelling to.
  • SSH key-based access to both. CGNAT at home is fine.

Flash the Pi headlessly Use Raspberry Pi Imager → "Edit Settings" before writing, and pre-set: hostname, username/password, WiFi, locale, and SSH → "Allow public-key authentication only" with your ~/.ssh/id_*.pub pasted in. See the official headless config docs if anything's unclear. After first boot, ssh <user>@<hostname>.local should land you in.

Pick the Pi's static IP pi/02-static-ip.sh sets the static IP on the Pi itself via NetworkManager — no router DHCP reservation needed. But the STATIC_IP you pick must be outside your router's DHCP pool, or DHCP can hand the same address to another device and conflict. Find the DHCP range in your router's admin page, then pick something below or above it.

Once you can SSH into the Pi, grab the rest of the .env values from it:

ip route | awk '/default/{print $3}'   # LAN_GATEWAY (router IP)
ip -o link | awk -F': ' '{print $2}'   # NET_IFACE  (usually wlan0 or eth0)

LAN_PREFIX is almost always 24.

Repo layout

.
├── README.md            ← you are here
├── .env.example         ← copy to .env on the Pi, edit values
├── lib/common.sh
├── vps/                 ← run these ONCE on the VPS
│   ├── 01-install.sh    ← installs WG, generates keys, writes hub config, prints Pi creds
│   ├── 02-harden.sh     ← key-only SSH, sudo passwords, ufw, fail2ban — run after 01
│   ├── wg-add           ← add a client (phone, laptop, ...) — generates QR + config
│   ├── wg-rm            ← revoke a client
│   ├── wg-list          ← list clients with handshake / transfer stats
│   ├── wg-show          ← re-print an existing client's config + QR code
│   ├── wg-hub-policy-route          ← re-adds the wg0→table-100 policy rule (idempotent)
│   └── wg-hub-policy-route.service  ← runs it on every systemd-networkd restart
└── pi/                  ← run these on the Pi
    ├── 01-update.sh     ← apt update + full-upgrade (slow on Pi Zero)
    ├── 02-static-ip.sh  ← static LAN IP via nmcli, reboots
    └── 03-pi-as-exit.sh ← installs WG + dnsmasq, writes config with NAT/MSS-clamp, starts the tunnel

End-to-end setup

1. On the VPS — set up the hub

ssh root@vps
git clone https://github.com/emeraldtarek/pi-wireguard-bootstrap.git
cd pi-wireguard-bootstrap/vps
sudo ./01-install.sh

At the end it prints a block of 6 values (VPS IP, port, pubkey, Pi private key, Pi PSK, Pi VPN IP). Copy these — you'll paste them into the Pi's .env.

Then harden the host (key-only SSH, no NOPASSWD sudo, ufw, fail2ban):

sudo ./02-harden.sh

Make sure your SSH public key is in /root/.ssh/authorized_keys BEFORE running this — afterwards, password SSH is disabled.

2. On the Pi — apt update, static IP, become exit gateway

ssh pi@<pi-ip>
git clone https://github.com/emeraldtarek/pi-wireguard-bootstrap.git
cd pi-wireguard-bootstrap
cp .env.example .env
nano .env                      # paste the 6 VPS-side values + edit LAN settings
./pi/01-update.sh              # slow on Pi Zero W (~10–20 min)
./pi/02-static-ip.sh           # reboots; reconnect at the new static IP

# After the reboot, SSH back in (now at $STATIC_IP) and continue:
ssh pi@<new-static-ip>
cd pi-wireguard-bootstrap
./pi/03-pi-as-exit.sh

When pi/03 finishes, the Pi pings the VPS over the tunnel to confirm.

3. Back on the VPS — add devices

ssh root@vps
sudo wg-add phone        # generates keys, allocates next free 10.10.0.x IP, prints QR + config
sudo wg-add laptop
sudo wg-add ipad
  • Mobile: scan the QR code from the terminal output with the WireGuard app.
  • Desktop: copy /etc/wireguard/<name>.conf from the VPS, import into the WireGuard app.

4. Manage clients

sudo wg-list             # show all clients, handshake age, bytes transferred
sudo wg-show phone       # re-print an existing client's config + QR (e.g. to re-add the device)
sudo wg-rm laptop        # revoke a device (immediate; removes peer from live WG)

Troubleshooting

  • Pi can't reach the VPS: check VPS firewall (sudo ufw status / sudo iptables -L INPUT) and confirm UDP WG_PORT (default 41194) is open inbound.
  • Phone shows VPN ON but no internet: most often the Pi tunnel is down. Check ssh pi sudo wg show — look for a recent handshake with the VPS. PersistentKeepalive=25 should keep it alive; if not, the Pi may have lost WiFi.
  • Phone exits with VPS's IP instead of home IP (or no internet at all): VPS policy routing isn't sending forwarded client traffic to the Pi. Check ssh vps "sudo ip rule" — there should be a from all iif wg0 lookup 100 line. That rule is installed by 01-install.sh via the [Interface] PostUp and applies to every client automatically. If missing, restart wg-quick@wg0 (or just sudo ip rule add iif wg0 lookup 100). Note: a systemd-networkd restart (e.g. an unattended systemd upgrade) silently flushes this rule while the tunnel stays up — the VPN keeps handshaking but all clients lose internet. wg-hub-policy-route.service (installed by 01-install.sh) guards against this by re-adding the rule on every networkd restart; confirm it's active with ssh vps "systemctl status wg-hub-policy-route".
  • Pi shows handshakes sent but 0 B received: the PSK on the Pi's .env doesn't match the VPS's pi peer. Re-copy PI_PSK from vps/01-install.sh's banner output, then sudo wg-quick down wg0 && sudo wg-quick up wg0 on the Pi.
  • Slow throughput: Pi Zero W maxes out around 20–40 Mbps due to ARMv6 + WiFi. Use a Pi 4 / 5 with ethernet if you need more.
  • Clients have no DNS / can't resolve names: client configs from wg-add point DNS at the Pi's tunnel IP (10.10.0.2), where dnsmasq answers and forwards to your home router. Confirm it's up with ssh pi "systemctl status dnsmasq" and that it's listening with ssh pi "sudo ss -lunp | grep ':53'". By default it forwards to LAN_GATEWAY; set CLIENT_DNS_UPSTREAM in the Pi's .env and re-run pi/03-pi-as-exit.sh to point it elsewhere. Clients created before this change still use public resolvers — re-run wg-add (or wg-show) to regenerate them, and update the DNS field on any already-imported device to the Pi's tunnel IP. If your exit Pi runs Pi-hole, don't let this install a second dnsmasq (they clash on port 53) — point clients at the Pi and let Pi-hole answer instead.

Looking less like a VPN

This setup already exits from your home residential IP, which sidesteps the most common VPN check (datacenter-IP reputation lists). Two further tweaks reduce fingerprinting:

  • MSS clamping + a pinned 1380 MTU (VPS and Pi wg0, plus client configs) keep the negotiated TCP MSS consistent and stop large segments from black-holing across the phone→VPS→Pi double hop. A stalled, retransmit-heavy connection is both a usability bug and a behavioral tell.
  • DNS through the Pi (dnsmasq → home router) means client lookups egress your home network exactly like any other device, instead of hitting 1.1.1.1/8.8.8.8 from a residential IP and letting CDNs geo-steer somewhere inconsistent with the exit.

What this can't hide: the extra latency of the double hop. An app that measures round-trip time and compares it against the exit IP's expected geolocation (RTT triangulation) can still infer a tunnel — that's inherent to bouncing through the VPS to beat CGNAT. Mobile apps may also compare the device's SIM/carrier country, GPS, timezone and locale against the IP; align those on the device if an app keys on them.

Security notes

  • The .env file contains the Pi's WireGuard private key and PSK. Keep it readable only by the user who runs the scripts. .gitignore is set to never commit it.
  • wg-add writes client configs to /etc/wireguard/<name>.conf on the VPS. These contain client private keys. Anyone with root on the VPS sees them — acceptable since they already control the WG hub, but worth knowing.
  • vps/02-harden.sh handles the host-level basics: key-only SSH, NOPASSWD sudo removal, ufw default-deny inbound, fail2ban on the sshd jail. Run it after 01.
  • The VPS↔Pi tunnel uses a PresharedKey on top of Noise IK — a hedge against future cryptanalysis of Curve25519 ("harvest now, decrypt later").
  • The Pi rejects forwarded traffic to RFC1918 destinations (10/8, 172.16/12, 192.168/16) so a VPN client can't pivot to your home LAN through the tunnel.
  • Ubuntu cloud images enable unattended-upgrades by default. Verify with systemctl is-active unattended-upgrades.

About

Self-hosted WireGuard VPN for travellers: exit through your home IP from anywhere, even when your ISP uses CGNAT. Raspberry Pi + $5 VPS.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages