WireGuard Setup - Road Warrior and Site-to-Site VPN

Status: Active
Last Updated: 2026-08-26
Category: Networking - Phase 2: Services
Prerequisites: wireguard-vpn, firewalls-nftables, ip-addressing-subnets
Time: 2-3 hours
Tags: wireguard, vpn, site-to-site, road-warrior, netplan, systemd

Summary

Stand up two WireGuard patterns used across the fleet: a road-warrior server for laptops/phones to reach the LAN, and a persistent site-to-site tunnel between two locations. Covers key management, the AllowedIPs mental model, forwarding rules in nftables, and bringing interfaces up with wg-quick/systemd or netplan.

๐ŸŽฏ What You'll Learn

By the end of this article, you'll be able to:


Table of Contents

  1. Context / Why This Matters
  2. Implementation / Core Content
  3. Practical Examples
  4. Troubleshooting & Common Pitfalls
  5. Next Steps / Ops Actions

Context / Why This Matters

wireguard-vpn explains what WireGuard is and why we chose it over OpenVPN/IPsec. This article is the operational how-to with fleet-specific addressing: the VPN transit network lives at 10.10.0.0/24, road-warrior clients get .x addresses from that pool, and site-to-site uses dedicated /30s from 10.10.1.0/24. All remote access funnels here instead of exposing services directly โ€” see network-segmentation.

Implementation / Core Content

Install and generate keys (every node)

sudo apt install wireguard
umask 077
wg genkey | tee peer1.key | wg pubkey > peer1.pub

Rule: one keypair per device, never shared. Track them in a password manager or vault (vault-secrets); treat private keys like SSH keys.

Pattern 1: Road-warrior hub

Server /etc/wireguard/wg0.conf:

[Interface]
Address = 10.10.0.1/24
ListenPort = 51820
PrivateKey = <server-private-key>

# NAT for client traffic leaving toward LAN/internet (nftables alternative below)
PostUp   = sysctl -w net.ipv4.ip_forward=1
PostUp   = iptables -A FORWARD -i wg0 -j ACCEPT
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT

# Optional: push split-DNS by pointing clients' DNS at the hub's dnsmasq
# (see dhcp-dns-split-horizon)

[Peer]  # laptop
PublicKey = <laptop-public-key>
AllowedIPs = 10.10.0.2/32

[Peer]  # phone
PublicKey = <phone-public-key>
AllowedIPs = 10.10.0.3/32

Client config:

[Interface]
PrivateKey = <laptop-private-key>
Address = 10.10.0.2/24
DNS = 192.168.20.2        # internal resolver via tunnel

[Peer]  # hub
PublicKey = <server-public-key>
Endpoint = vpn.fogserv.cloud:51820
AllowedIPs = 10.10.0.0/24, 192.168.20.0/24    # split tunnel: LAN + peers only
PersistentKeepalive = 25                        # needed behind NAT'd client networks

Open the port: sudo ufw allow 51820/udp or an nftables input rule.

Pattern 2: Site-to-site

Site A (LAN 192.168.20.0/24) โ†” Site B (LAN 192.168.30.0/24).

Site A /etc/wireguard/wg1.conf:

[Interface]
Address = 10.10.1.1/30
ListenPort = 51821
PrivateKey = <a-key>
Table = off                      # we manage routes explicitly
PostUp = ip route add 192.168.30.0/24 dev wg1
PostUp = sysctl -w net.ipv4.ip_forward=1

[Peer]
PublicKey = <b-pub>
Endpoint = siteb.fogserv.cloud:51821
AllowedIPs = 10.10.1.0/30, 192.168.30.0/24
PersistentKeepalive = 25

Site B mirrors it with swapped addresses/routes. Because both sides know each other's CIDRs in AllowedIPs, WireGuard performs the crypto-key routing; the explicit routes make the kernels forward.

Bringing it up

sudo systemctl enable --now wg-quick@wg0     # road warrior
sudo systemctl enable --now wg-quick@wg1     # site-to-site

# Verify
sudo wg show
interface: wg0
  public key: ...
  listening port: 51820
peer: ...  endpoint: 203.0.113.7:51820
  allowed ips: 10.10.0.2/32
  latest handshake: 34 seconds ago      <-- the health signal
  transfer: 1.2 MiB received, 3.4 MiB sent
ping 10.10.0.2

Netplan alternative (no wg-quick)

On servers managed declaratively, netplan can own the interface:

network:
  tunnels:
    wg0:
      mode: wireguard
      addresses: [10.10.0.1/24]
      port: 51820
      key: /etc/wireguard/server.key
      peers:
        - keys:
            public: <laptop-public-key>
          allowed-ips: [10.10.0.2/32]

Pick one owner per interface โ€” don't define the same tunnel in both netplan and wg-quick.

Practical Examples

Add a new device in 60 seconds

wg genkey | tee tablet.key | wg pubkey > tablet.pub
# append [Peer] block on hub with AllowedIPs = 10.10.0.4/32
sudo systemctl reload wg-quick@wg0 || sudo wg syncconf wg0 <(wg-quick strip wg0)

Watch handshakes live while testing

watch -n2 'sudo wg show | grep -E "peer|handshake"'

Route check through tunnel

traceroute -n 192.168.20.10   # from road-warrior laptop
# hop 1 should be 10.10.0.1 (hub), then LAN IP

Troubleshooting & Common Pitfalls

Problem Cause Fix
No handshake ever Wrong Endpoint/port, UDP blocked, keys swapped Confirm pub keys match; test UDP 51820 externally
Handshake OK but no traffic Missing ip_forward, or AllowedIPs too narrow Enable forwarding; include remote LAN CIDR in AllowedIPs
Works then dies after idle Client behind aggressive NAT PersistentKeepalive = 25 on the client side
Intermittent stalls on big transfers MTU overhead Set MTU = 1420 in [Interface]
Two devices same key Key reuse breaks cryptokey routing One keypair per device, rotate the offender
Whole internet stops on laptop AllowedIPs = 0.0.0.0/0 full tunnel + DNS mismatch Split tunnel, or add exclusion routes for local nets

Next Steps / Ops Actions

Sources & Related

External references consulted:

Related knowledge-base articles:

Change Log

2026-08-26

Choose Theme

Your selection is saved locally.

Neural Cacophony
Aperture v2
Flux v1
Mosaic Chaos
Nexus v1
Nexus Zest
Prism v2
Synapse