SEO-friendly terms: Raspberry Pi site-to-site VPN, WireGuard gateway, home network extension, remote office Wi‑Fi to home network, plug-and-play VPN router.
Set up a Raspberry Pi WireGuard site-to-site VPN in minutes. One Ethernet cable in, optional Pi Wi‑Fi out, and every device on that Wi‑Fi (or LAN) behaves as if it's on your home network—no router changes, no port forwarding, fully NAT-friendly. Perfect for remote offices, cabins, and temporary sites.
Build a plug-and-play site-to-site VPN with a single Raspberry Pi and one Ethernet cable. The script turns the Pi into:
- A WireGuard VPN client that extends your home network to a remote site.
- A DHCP router + NAT for a private subnet.
- An optional Wi‑Fi access point so every device on that Wi‑Fi "pretends" to be on your home network—no router changes needed.
- Works behind NAT/CGNAT (outbound WireGuard only; no port forwarding required).
- Zero router config at the remote site—plug in WAN Ethernet, run one script.
- If you enable Wi‑Fi, the Pi broadcasts an SSID that tunnels straight to home.
- Survives reboots: all services and configurations persist automatically.
- Raspberry Pi 4 (or similar Pi; Wi‑Fi-capable if you want an AP)
- One Ethernet cable (WAN to the existing onsite network)
- Optional: USB Ethernet adapter if you prefer wired LAN plus Wi‑Fi WAN/LAN
- Raspberry Pi OS Lite (Bookworm or later recommended)
- WireGuard peer config from your home network (e.g.,
wg0.conf/peer file)
- Prep the Pi
- Flash Raspberry Pi OS Lite, enable SSH, boot, then:
sudo apt-get update && sudo apt-get upgrade -y
- Flash Raspberry Pi OS Lite, enable SSH, boot, then:
- Get the project
git clone https://github.com/etokheim/raspberrypi-site2site-wireguard.git cd raspberrypi-site2site-wireguard - Copy your WireGuard peer config to the Pi (e.g.,
/home/pi/wg-peer.conf). - Run the entrypoint (prompts for everything)
sudo ./gateway-manage-or-setup.sh
- Select WAN and LAN interfaces (Enter accepts defaults). The picker now shows state/IP/MAC/driver and whether the NIC appears USB-backed.
- If LAN is Wi‑Fi (e.g.,
wlan0), enter SSID/password; hostapd is auto-configured. - Provide the WireGuard config path (tab completion enabled).
- Opt into a static WAN IP (recommended): the script detects the upstream router and suggests the second IP in the subnet (e.g. router
192.168.1.1→192.168.1.2). You can override IP, prefix, gateway and DNS. - Opt into WAN firewall hardening (allow SSH + WireGuard, drop other inbound).
- Opt into automatic security updates (nightly at 03:00).
- Opt into hardware watchdog (auto-reboot on system hang).
- With Pi-bypass: choose all LAN traffic via VPN or home subnets only (other Internet via WAN).
- Opt into a post-setup WAN vs tunnel Ookla speedtest (live comparison panel).
- Review the progress box, then confirm to apply.
- Connect devices
- Wired: plug a switch/AP into the Pi's LAN port.
- Wi‑Fi: connect to the SSID you set. Clients get
10.10.10.xand route through WireGuard to your home network.
sudo ./gateway-manage-or-setup.sh [OPTIONS]| Option | Description |
|---|---|
| (none) | Interactive menu |
--setup |
Full setup wizard |
--edit |
Change one setting (minimal reapply) |
--apply |
Re-apply vpn-gateway.conf as-is (no prompts; for hand-edits) |
--cleanup |
Remove the configured gateway |
--start |
Start gateway services (WireGuard + dnsmasq + hostapd) |
--stop |
Stop gateway services |
--speedtest |
Ookla WAN vs WireGuard tunnel comparison (live panel) |
--yes |
Non-interactive mode (use existing config, no prompts) |
--help |
Show usage |
- WireGuard at
/etc/wireguard/wg0.conf,wg-quick@wg0enabled, PostUp/PostDown iptables rules. - Routing/NAT: iptables forwarding and MASQUERADE from LAN →
wg0(WAN MASQUERADE as secondary). - Pi-bypass routing (recommended): forwarded LAN traffic goes through
wg0; the Pi's own outbound (apt, Pi Connect, NTP, DNS, the WireGuard handshake itself) stays on WAN. See How traffic is routed below. - DHCP/DNS: dnsmasq on the LAN/AP subnet. With Pi-bypass routing the dual-DNS plane separates LAN client DNS (via
HOME_DNS_SERVERSthrough the tunnel - geo-correct) from the Pi's own DNS (viaPI_DNS_SERVERSover WAN - always reachable, even with the tunnel down). See DNS plane separation. - Static IP (LAN): gateway
10.10.10.1/24on the LAN/AP interface. - Static IP (WAN, optional): fixed address on the upstream interface (suggested as the second IP in the router's subnet, e.g.
192.168.1.2if the router is192.168.1.1). - Access Point (optional): hostapd with your SSID/password when LAN is wireless.
- Firewall (optional): hardened INPUT rules on WAN (allows SSH, WireGuard; drops other inbound).
- Service watchdog: systemd restart policies for dnsmasq, WireGuard, hostapd (auto-restart on crash).
- Boot ordering hardening: a persistent
vpn-gateway-lan.serviceplus systemd drop-ins so LAN comes up before dnsmasq/hostapd/WireGuard. - Hardware watchdog (optional): kernel-level auto-reboot if system hangs.
- Auto-updates (optional): unattended-upgrades for security patches.
- Persistence: all settings survive reboots (systemd services, static IPs, iptables).
The setup wizard asks whether to enable Pi-bypass routing (recommended), then whether LAN clients should send all traffic or only home-network traffic through the VPN. The WireGuard peer config is otherwise left as-is (AllowedIPs is not rewritten).
┌───────────────────┐
LAN client ────►│ Pi (gateway) │
10.10.10.x │ │
(forwarded, │ iif=LAN_IFACE │
iif=LAN_IFACE) │ │ │ ┌─ encrypted ─┐
│ ▼ ├────►│ wg0 │──► home WG peer
│ table 200: │ │ │
│ all → default │ └─────────────┘
│ + homes │
│ home → homes only│ ┌─ direct ────┐
│ (else → main) ├────►│ WAN gateway │──► Internet
│ │ │ │
│ Pi-local │ └─────────────┘
│ (apt, Pi Connect,│
Pi process ─────│ NTP, DNS, WG ├────► WAN gateway ──► Internet
(no iif) │ handshake) → │
│ main: default │
│ via WAN gw │
└───────────────────┘
- LAN client traffic is forwarded through the Pi. The kernel sees
iif=$LAN_IFACE, matches anip ruleat priority 100, and looks up routing in table 200.LAN_FORWARD_MODE=all(default): table 200 hasdefault dev wg0plus each home subnet — full site-to-site; Internet exits via the home peer.LAN_FORWARD_MODE=home: table 200 has only non-defaultAllowedIPshome subnets. Other destinations miss in table 200 and fall through to the main table → site WAN. Use this when you only need local home apps, not all traffic through the VPN.
- Pi-local traffic (anything originating on the Pi itself) has no
iifset. It skips the priority-100 rule and falls through to the main table, where the default route is the WAN gateway. The Pi can still reach the home subnet viawg0because each non-defaultAllowedIPsentry (e.g.10.33.33.0/24) is also added as an explicit route in the main table. - WireGuard's own UDP handshake is Pi-local traffic; it goes via WAN like any other Pi process. There is no chicken-and-egg problem when the tunnel restarts.
allmode: Ifwg0is down, packets routed through table 200 hit a route whosedev wg0no longer exists — the kernel drops them. LAN clients lose Internet rather than silently leaking via the Pi's WAN.homemode: Only home destinations use table 200. Whenwg0is down, home access fails; general Internet keeps working via WAN (intentional).- The Pi itself stays online because its traffic uses the main table, which is untouched. Remote management via Pi Connect, SSH-over-Internet, and apt continue to work.
LAN clients query the Pi's dnsmasq. dnsmasq itself runs on the Pi, so where its upstream lookups go depends on which mode the operator picked at the prompt (HOME_DNS_MODE in vpn-gateway.conf):
tunnel- dnsmasq forwards to public DNS bound towg0:server=1.1.1.1@wg0,server=8.8.8.8@wg0. Each query exits via the tunnel; the home peer NATs it out from the home location, so DNS responses are geo-anchored at home. Works without any DNS server on the home network. RequiresAllowedIPsto cover the chosen public DNS IPs (typically via0.0.0.0/0). Default when the WireGuard config has noDNS =line.custom- dnsmasq forwards to a specific home-network DNS server (e.g. Pi-hole or AdGuard Home). Default when your WireGuard config hasDNS =— setup asks you to confirm those addresses, or override. The destination IP must be covered by anAllowedIPsentry; the input prompt validates this.skip- dnsmasq falls back to the Pi's/etc/resolv.conf(which points atPI_DNS_SERVERSover WAN). LAN DNS keeps working when the tunnel is down, but resolution geo-leaks to the WAN ISP's location.
In all three modes, the Pi's own DNS is configured separately via PI_DNS_SERVERS (default 1.1.1.1 8.8.8.8) and goes via WAN. apt, NTP, Pi Connect, and the WireGuard endpoint hostname keep resolving even when the tunnel is down, so remote management never breaks because of DNS.
A WireGuard DNS = line in your peer config is not applied to the Pi under Pi-bypass (setup strips it from the installed wg0.conf so Pi DNS stays on PI_DNS_SERVERS via WAN). For LAN clients, those same addresses are the default upstream: setup asks you to confirm them, or override with tunnel-exit / other IPs.
LAN client ──DHCP-supplied DNS──> Pi (10.10.10.1)
│
│ dnsmasq
▼
┌───── HOME_DNS_MODE=tunnel ─────┐
│ server=1.1.1.1@wg0 │ ─► wg0 ─► home peer NATs ─► public DNS
│ (geo-correct; default if no DNS=) │ (response geo-anchored at home)
└────────────────────────────────┘
┌───── HOME_DNS_MODE=custom ─────┐
│ server=<your home DNS IP> │ ─► wg0 ─► home DNS server (Pi-hole etc.)
│ (default when WG has DNS=) │
└────────────────────────────────┘
┌───── HOME_DNS_MODE=skip ───────┐
│ uses Pi's /etc/resolv.conf │ ─► WAN ─► public DNS (geo-leaks)
└────────────────────────────────┘
Pi-local lookups (apt, NTP, Pi Connect, wg endpoint) ─► /etc/resolv.conf
(PI_DNS_SERVERS) ─► WAN ─► always works
Kill-switch: in tunnel and custom modes, when wg0 is down LAN DNS stops resolving (the upstream packet has no route). The Pi itself keeps resolving via PI_DNS_SERVERS over WAN, so remote management is unaffected.
If you say No to Pi-bypass at the prompt, wg-quick manages routes the standard way: when your AllowedIPs contains 0.0.0.0/0, the Pi's own outbound traffic also flows through the tunnel. This is appropriate only if your home WireGuard peer NATs the Pi's traffic out to the Internet. Without that NAT, the Pi loses Pi Connect, apt, and any SSH session that comes in via its public IP the moment wg0 comes up. The setup wizard warns about this on the SSH-safety check.
- Subnet:
10.10.10.0/24 - Gateway:
10.10.10.1 - DHCP pool:
10.10.10.10 - 10.10.10.250 - Note: the script locks LAN subnets to /24. If you enter another prefix, it will coerce it to a /24 in the same third-octet block (e.g.,
192.168.50.0/20becomes192.168.50.0/24).
On the Pi (with Pi-bypass enabled, this shows the WAN egress IP — not home):
wg show
curl https://ifconfig.me # Pi-local traffic stays on WAN
sudo ./scripts/setup-vpn-gateway.sh --verify-routes # tunnel vs WAN routing reportOptional: compare site ISP vs home egress with Ookla (also offered at the end of setup):
sudo ./gateway-manage-or-setup.sh --speedtest
# or menu → 5) SpeedtestThe panel binds direct tests to the WAN interface and tunnel tests to wg0
(required under Pi-bypass, where Pi-local traffic would otherwise stay on WAN).
On a client connected to the Pi LAN/AP (this shows the home egress IP):
ping 10.10.10.1 # gateway reachability
ping 1.1.1.1 # routing/NAT via the tunnel (all-mode) or via WAN (home-mode)
curl https://ifconfig.me # home egress in all-mode; site ISP in home-mode
nslookup google.com 10.10.10.1 # DNS via dnsmasq| File | Description |
|---|---|
logs/vpn_setup.log |
Setup script log |
logs/vpn_cleanup.log |
Cleanup script log |
vpn-gateway.conf |
Saved configuration (git-ignored) |
sudo ./gateway-manage-or-setup.sh --cleanup
# or menu → Remove the configured gateway
# or directly:
sudo ./scripts/cleanup-gateway.shCleanup will:
- Stop/disable WireGuard, dnsmasq, hostapd (if running)
- Remove WAN firewall rules (if enabled)
- Remove NAT/forward iptables rules; disable IP forwarding
- Tear down Pi-bypass routing (the
iif=LANip rule, the table-200 routes, IPv6 mirrors) - Restore Pi-local DNS settings (NetworkManager
ipv4.dns, dhcpcdstatic domain_name_serversblock,/etc/resolv.conffrom backup) - Restore NetworkManager/dhcpcd to DHCP (on both LAN and WAN, if a static WAN IP was configured)
- Remove watchdog configurations
- Remove unattended-upgrades config (if auto-updates were enabled)
- Optionally delete the saved config file
| Symptom | Solution |
|---|---|
| wg-quick DNS errors | Ensure resolvconf is installed (handled by setup) and the endpoint hostname resolves. |
| Clients get DHCP but no internet | Check iptables -t nat -S | grep MASQUERADE and iptables -S FORWARD. Verify WireGuard handshake with wg show. |
| Gateway not working after reboot | Check systemctl status vpn-gateway-lan wg-quick@wg0 dnsmasq hostapd, then run sudo ./gateway-manage-or-setup.sh --start. |
| dnsmasq says unknown interface | Re-run sudo ./gateway-manage-or-setup.sh --setup, reselect the correct LAN interface using the metadata shown, then apply and reboot-test. |
| Wi-Fi AP not visible | Check rfkill list for blocked wlan. Run sudo rfkill unblock wlan. |
| Using a wired AP instead of Pi Wi‑Fi | Choose the Pi's Wi‑Fi as WAN and plug your wired AP/switch into the Pi's Ethernet as LAN. |
For deeper boot diagnostics:
sudo systemctl status vpn-gateway-lan dnsmasq hostapd wg-quick@wg0
sudo journalctl -b -u vpn-gateway-lan -u dnsmasq -u hostapd -u wg-quick@wg0 --no-pager
ip -br link
ip -br addr┌─────────────────────────────────────────────────────────────────┐
│ Remote Site │
│ ┌──────────┐ ┌─────────────────────────────────────────┐ │
│ │ Client │──────│ Raspberry Pi │ │
│ │ Devices │ WiFi │ ┌───────┐ ┌─────┐ ┌──────────┐ │ │
│ │10.10.10.x│ or │ │dnsmasq│────│ NAT │────│WireGuard │ │ │
│ └──────────┘ LAN │ │ DHCP │ │ │ │ wg0 │ │ │
│ │ └───────┘ └─────┘ └────┬─────┘ │ │
│ └───────────────────────────────┼─────────┘ │
│ │ │
└────────────────────────────────────────────────────┼────────────┘
│ UDP tunnel
▼
┌────────────────────────────────────────────────────────────────┐
│ Home Network │
│ ┌──────────────────┐ │
│ │ WireGuard Server │ │
│ │ (your router) │ │
│ └──────────────────┘ │
└────────────────────────────────────────────────────────────────┘