Join two LANs with a WireGuard site-to-site tunnel on Linux
LinuxWorking
Tasks
- Turn on IPv4 forwarding on both gateways.
- Generate a key pair on each gateway and exchange the public keys.
- Write wg0.conf on each gateway with the right AllowedIPs for the peer's LAN.
- Open UDP 51820 and allow forwarding between wg0 and the LAN.
- Bring the tunnel up at boot and verify the handshake, routes and LAN-to-LAN traffic.
Solution
1Enable IPv4 forwarding (both gateways)GW1
GW1 on Linux echo 'net.ipv4.ip_forward = 1' > /etc/sysctl.d/99-forwarding.conf sysctl --systemDo the same on GW2. Without forwarding, the gateways answer pings to themselves but never route LAN traffic.
2Generate a key pair (both gateways)GW1
GW1 on Linux umask 077 wg genkey | tee /etc/wireguard/wg0.key | wg pubkey > /etc/wireguard/wg0.pub cat /etc/wireguard/wg0.pubThe private key never leaves the gateway. Only the public key goes to the peer, and it is safe to send by any channel.
3Tunnel configuration on GW1GW1
GW1 on Linux # /etc/wireguard/wg0.conf [Interface] Address = 172.16.99.1/30 ListenPort = 51820 PrivateKey = <contents of /etc/wireguard/wg0.key on GW1> [Peer] # GW2 PublicKey = AdhgM1fPr4DFGooiULo7imk0kdnXVykxN6upQFgdKDk= Endpoint = 203.0.113.20:51820 AllowedIPs = 172.16.99.2/32, 10.2.0.0/24 PersistentKeepalive = 25wg-quick adds a route for each AllowedIPs prefix that is not already covered, so 10.2.0.0/24 is routed into wg0 automatically.
4Tunnel configuration on GW2GW2
GW2 on Linux # /etc/wireguard/wg0.conf [Interface] Address = 172.16.99.2/30 ListenPort = 51820 PrivateKey = <contents of /etc/wireguard/wg0.key on GW2> [Peer] # GW1 PublicKey = XPUokPLW1NGGoMksYc6MrU2IpIqQazgHL9uww/NCJzk= Endpoint = 198.51.100.10:51820 AllowedIPs = 172.16.99.1/32, 10.1.0.0/24 PersistentKeepalive = 25AllowedIPs here must include GW1's LAN, or packets from 10.1.0.0/24 are decrypted and then dropped because their source is not allowed for that peer.
5Firewall rules (both gateways)GW1
GW1 on Linux nft add rule inet filter input udp dport 51820 accept nft add rule inet filter forward iifname "wg0" oifname "eth1" accept nft add rule inet filter forward iifname "eth1" oifname "wg0" acceptThese rules are not persistent. Add them to the nftables configuration file your distribution loads at boot.
6Bring the tunnel up now and at boot (both gateways)GW1
GW1 on Linux systemctl enable --now wg-quick@wg0
Verify
GW1# wg show wg0
interface: wg0
public key: XPUokPLW1NGGoMksYc6MrU2IpIqQazgHL9uww/NCJzk=
private key: (hidden)
listening port: 51820
peer: AdhgM1fPr4DFGooiULo7imk0kdnXVykxN6upQFgdKDk=
endpoint: 203.0.113.20:51820
allowed ips: 172.16.99.2/32, 10.2.0.0/24
latest handshake: 38 seconds ago
transfer: 18.42 KiB received, 21.07 KiB sent
persistent keepalive: every 25 secondsA recent handshake and bytes in both directions. No "latest handshake" line at all means the handshake never completed, so check keys, endpoint and UDP 51820.
GW1# ip route | grep wg0
10.2.0.0/24 dev wg0 scope link
172.16.99.0/30 dev wg0 proto kernel scope link src 172.16.99.1The remote LAN is routed into wg0. The /32 for GW2's tunnel address needed no route because the connected /30 already covers it.
GW1# ping -c 3 -I 10.1.0.1 10.2.0.10
PING 10.2.0.10 (10.2.0.10) from 10.1.0.1 : 56(84) bytes of data.
64 bytes from 10.2.0.10: icmp_seq=1 ttl=63 time=24.1 ms
64 bytes from 10.2.0.10: icmp_seq=2 ttl=63 time=23.8 ms
64 bytes from 10.2.0.10: icmp_seq=3 ttl=63 time=23.9 ms
--- 10.2.0.10 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2003ms
rtt min/avg/max/mdev = 23.812/23.933/24.104/0.124 msSourcing from the LAN address tests the real LAN-to-LAN policy, including GW2's AllowedIPs for 10.1.0.0/24. TTL 63 is SRV2's 64 minus one hop through GW2.
Mistakes people make
- Leaving the peer's LAN out of AllowedIPs. The handshake works, but LAN traffic is either not routed into wg0 or dropped after decryption.
- Forgetting
net.ipv4.ip_forward. The tunnel is up, the gateways can ping each other, and nothing behind them works. - Swapping keys, for example putting a gateway's own public key in its
[Peer]section. The handshake never completes, and WireGuard logs nothing about it by default. - Listing the same prefix in two peers on one interface. The prefix silently moves to the peer configured last.
- Hosts on the LAN whose default gateway is not the WireGuard gateway. They need a route for the remote LAN pointing at it.
Say it in the interview
"Each gateway gets a key pair, and each side lists the other as a peer with its public key, endpoint and AllowedIPs. AllowedIPs is the important line: it routes the remote LAN into the tunnel and filters the source of what comes out of it, so each side lists the other's tunnel address and LAN. Then forwarding, a firewall hole for UDP 51820 and forward rules between wg0 and the LAN. I verify with wg show for a recent handshake, the route into wg0, and a ping sourced from the LAN."