Featured image of post WireGuard site-to-site VPN between two Proxmox hosts, step by step

WireGuard site-to-site VPN between two Proxmox hosts, step by step

Two labs in two locations, and you want the Proxmox cluster on one side to reach the NAS on the other without exposing anything to the internet. WireGuard is the right tool: small, fast, and quiet unless misconfigured — at which point it silently forwards traffic you did not intend.

This walkthrough connects two Proxmox VE hosts over UDP 51820 with a routed /30 tunnel address pair.

1. Keys

umask 077
wg genkey | tee /etc/wireguard/lab-a.key | wg pubkey > /etc/wireguard/lab-a.pub
cat /etc/wireguard/lab-a.pub

Do the same on the other host. Private keys never leave their host; only public keys are exchanged. If you script this, keep umask 077 — otherwise wg keys land in the world-readable default.

2. Interface on side A

# /etc/wireguard/wg0.conf  (lab-a, public IP a.a.a.a)
[Interface]
Address = 10.99.0.1/30
ListenPort = 51820
PrivateKey = <lab-a private key>

[Peer]
# lab-b
PublicKey = <lab-b public key>
Endpoint = b.b.b.b:51820
AllowedIPs = 10.99.0.2/32, 192.168.20.0/24
PersistentKeepalive = 25

And the mirror on side B:

# /etc/wireguard/wg0.conf  (lab-b)
[Interface]
Address = 10.99.0.2/30
ListenPort = 51820
PrivateKey = <lab-b private key>

[Peer]
PublicKey = <lab-a public key>
Endpoint = a.a.a.a:51820
AllowedIPs = 10.99.0.1/32, 192.168.10.0/24
PersistentKeepalive = 25

AllowedIPs is doing two jobs at once: it is the routing table *and* the access control list. Listing 192.168.10.0/24 on side B's peer means side B will accept packets claiming to come from that network. Be exact — list 0.0.0.0/0 and you have built a full-tunnel VPN for the whole host.

PersistentKeepalive keeps NAT mappings open. Twenty-five seconds is the documented default for a reason: long enough to be quiet, short enough to survive a typical 30-second NAT timeout.

3. Bring it up and make it persistent

wg-quick up wg0
systemctl enable wg-quick@wg0
wg show

wg show should list the peer with a recent handshake:

peer: <lab-b pubkey>
  endpoint: b.b.b.b:51820
  allowed ips: 10.99.0.2/32, 192.168.20.0/24
  latest handshake: 12 seconds ago
  transfer: 84.21 KiB received, 133.02 KiB sent

No handshake line means the UDP path is blocked or the endpoint is wrong. Test the path directly with nc -u -z -v b.b.b.b 51820 before you start debugging keys.

4. Route the LAN, and get the firewall right

WireGuard only moves packets between the two tunnel addresses until you route the LANs:

# on lab-a, allow forwarding for the tunnel
sysctl -w net.ipv4.ip_forward=1
echo 'net.ipv4.ip_forward=1' > /etc/sysctl.d/99-wg.conf

# Proxmox ships with a restrictive FORWARD policy in some setups
nft list ruleset | head -20
iptables -I FORWARD -i wg0 -j ACCEPT
iptables -I FORWARD -o wg0 -j ACCEPT

And on the router at each site, a static route pointing 192.168.20.0/24 at the Proxmox host — otherwise only the Proxmox host itself can reach the far side, which is a classic "the tunnel works but nothing does" moment.

5. Prove it end to end

# from lab-a, ping a host on lab-b's LAN
ping -c3 192.168.20.10

# and prove it goes through the tunnel, not a stray route
ip route get 192.168.20.10
# 192.168.20.10 via 10.99.0.2 dev wg0 src 10.99.0.1

# packet counters
wg show wg0 transfer

If ip route get says dev eth0, the static route is missing on the router and traffic is taking the public path (or being dropped).

Pitfalls

  • Overlapping subnets. Both labs on 192.168.1.0/24 is extremely common and cannot be routed. Renumber one side, or NAT the tunnel (ugly, and it breaks return traffic on some services).
  • MTU. wg-quick sets MTU 1420 by default. Over PPPoE links that still fragments; drop to 1380 and retest with ping -M do -s 1400.
  • Firewall on the WAN interface. The Proxmox host often has the Proxmox firewall enabled, which blocks UDP 51820. Open the port to the peer's address only, not to the world.
  • Endpoint on both sides. With two static IPs, having both endpoints set is fine. With one dynamic IP, leave the Endpoint off the dynamic side so the static side's keepalive establishes the session.
  • Keys in a git repo. Obvious, and still happens. Keep wg0.conf with chmod 600 and out of version control.

Once the tunnel is up, the two labs are one routed network — and you can start moving backups across it, which is usually the real reason to build it.

Built with Hugo
Theme Stack designed by Jimmy