The first user
A user's configuration is the mirror image of the server's: their own address and key, and one [Peer], the server, with its public key and where to find it (Endpoint).
On the client, AllowedIPs decides what goes through the tunnel. alice's lists the VPN subnet, the office LAN and the branch LAN, so that's a split tunnel: everything else goes straight to the internet. There's no server-pushed configuration in WireGuard. If the office adds a network, every client's file needs it, which is why tools that generate client files are popular.
wg-quick up wg0 prints every command it runs: create the interface, load the peers, add the address, set the MTU, add a route for each of the peer's AllowedIPs, then the PostUp that loads the key. It's a good way to see that WireGuard really is just an interface plus routes.
WireGuard has no "connect" step. The first packet towards the server triggers a handshake, and wg show on both ends then shows latest handshake and traffic counters. The server has also learned alice's current address and port (her endpoint) from that packet.
Reaching the office
The office host replies via its own gateway, lanrtr, so lanrtr needs a route back to the VPN subnet (and the branch LAN), exactly as in the OpenVPN lesson, which shows the failure in detail with a packet capture. With the routes in place, alice reaches the office.
Forwarding and the forward rule
Getting from the tunnel to the office takes two things on the server besides the tunnel itself, and it's worth seeing each one fail. Both captures come from a second run of the lab, where the server was left with Debian's default of forwarding off (the lab's containers would otherwise have it on, from Docker).
- IP forwarding. With
net.ipv4.ip_forward = 0, alice can ping the server's own tunnel address, but nothing beyond it: the server receives her packets for the office and has no permission to route them. The/etc/sysctl.d/99-wireguard.conffile, loaded withsysctl --system, turns it on now and at every boot. - The forward rule. The firewall's forward chain drops everything by default, so forwarding only works where a rule allows it. Delete the "VPN users to the office LAN" rule and alice's pings are counted by the final drop counter instead. Loading the ruleset again puts it back.
So when traffic through the server fails, check forwarding, then the firewall counters, then the return route.
# /etc/wireguard/wg0.conf on alice's computer (wg-quick up wg0)
[Interface]
Address = 10.9.0.10/24
PostUp = wg set %i private-key /etc/wireguard/alice.key
[Peer]
# The VPN server
PublicKey = /S03YCIRbPGjlhE53GYhxFEh8LVo/Joel3BkEgWeeXk=
Endpoint = 203.0.113.10:51820
# Split tunnel: only the VPN, the office LAN and the branch LAN go through it.
AllowedIPs = 10.9.0.0/24, 10.10.0.0/24, 10.20.0.0/24
# Send a packet every 25 s, so NAT and firewalls on the way keep the path open.
PersistentKeepalive = 25- Line 3: alice's fixed VPN address, matching her
AllowedIPson the server. - Lines 8–9: The server: its public key, and where to send packets.
- Line 11: What goes through the tunnel: the VPN, the office LAN and the branch LAN. Nothing else. A split tunnel.
- Line 13: Keep the path through NAT open; see Roaming and keepalives.
root@alice:~# wg-quick up wg0
[#] ip link add wg0 type wireguard
[#] wg setconf wg0 /dev/fd/63
[#] ip -4 address add 10.9.0.10/24 dev wg0
[#] ip link set mtu 1420 up dev wg0
[#] ip -4 route add 10.20.0.0/24 dev wg0
[#] ip -4 route add 10.10.0.0/24 dev wg0
[#] wg set wg0 private-key /etc/wireguard/alice.key
root@alice:~# ping -c 2 -W 1 10.9.0.1 && wg show
PING 10.9.0.1 (10.9.0.1) 56(84) bytes of data.
64 bytes from 10.9.0.1: icmp_seq=1 ttl=64 time=0.253 ms
64 bytes from 10.9.0.1: icmp_seq=2 ttl=64 time=0.347 ms
--- 10.9.0.1 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1044ms
rtt min/avg/max/mdev = 0.253/0.300/0.347/0.047 ms
interface: wg0
public key: hwCdM7ImFlIKfF0sQHngu9THMAenVRLSD1JYIrqR2Qw=
private key: (hidden)
listening port: 44820
peer: /S03YCIRbPGjlhE53GYhxFEh8LVo/Joel3BkEgWeeXk=
endpoint: 203.0.113.10:51820
allowed ips: 10.9.0.0/24, 10.10.0.0/24, 10.20.0.0/24
latest handshake: 1 second ago
transfer: 348 B received, 436 B sent
persistent keepalive: every 25 seconds
root@vpn:~# wg show | sed -n "/hwCdM7/,/^$/p"
peer: hwCdM7ImFlIKfF0sQHngu9THMAenVRLSD1JYIrqR2Qw=
endpoint: 198.51.100.21:44820
allowed ips: 10.9.0.10/32
latest handshake: 17 seconds ago
transfer: 980 B received, 604 B sent- Lines 2–8: Everything
wg-quickdoes: an interface, the peers, an address, MTU 1420, a route per allowed network, then thePostUpkey. - Lines 10–17: The first packet triggers the handshake; the ping simply works.
- Lines 23–28: alice's view: a handshake a second ago, and traffic both ways.
- Lines 31–35: The server's view: it learned alice's endpoint, 198.51.100.21:44820, from her packets.
root@alice:~# ping -c 2 -W 1 10.10.0.20
PING 10.10.0.20 (10.10.0.20) 56(84) bytes of data.
--- 10.10.0.20 ping statistics ---
2 packets transmitted, 0 received, 100% packet loss, time 1062ms
root@lanrtr:~# ip route add 10.9.0.0/24 via 10.10.0.1 && ip route add 10.20.0.0/24 via 10.10.0.1 && ip route
10.9.0.0/24 via 10.10.0.1 dev eth0
10.10.0.0/24 dev eth0 proto kernel scope link src 10.10.0.254
10.20.0.0/24 via 10.10.0.1 dev eth0
root@alice:~# ping -c 2 -W 1 10.10.0.20
PING 10.10.0.20 (10.10.0.20) 56(84) bytes of data.
64 bytes from 10.10.0.20: icmp_seq=1 ttl=62 time=0.269 ms
64 bytes from 10.10.0.20: icmp_seq=2 ttl=62 time=0.329 ms
--- 10.10.0.20 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1017ms
rtt min/avg/max/mdev = 0.269/0.299/0.329/0.030 ms- Lines 1–5: The ping reaches the office host, but its reply goes to its gateway, lanrtr, which doesn't know 10.9.0.0/24.
- Lines 8–11: Return routes for the VPN and the branch LAN, via the VPN server's office address.
- Lines 13–19: Now it works. TTL 62: the reply crossed two routers, lanrtr and the VPN server.
root@alice:~# ping -c 2 -W 1 10.9.0.1 | tail -2; ping -c 2 -W 1 10.10.0.20 | tail -2
2 packets transmitted, 2 received, 0% packet loss, time 1047ms
rtt min/avg/max/mdev = 0.195/0.206/0.218/0.011 ms
2 packets transmitted, 0 received, 100% packet loss, time 1023ms
root@vpn:~# sysctl net.ipv4.ip_forward; nft list chain inet filter forward | grep -E "wg0.*eth1"
net.ipv4.ip_forward = 0
iifname "wg0" oifname "eth1" accept comment "VPN users to the office LAN"
root@vpn:~# cp 99-wireguard.conf /etc/sysctl.d/ && sysctl --system | grep ip_forward
net.ipv4.ip_forward = 1
root@alice:~# ping -c 2 -W 1 10.10.0.20 | tail -2
2 packets transmitted, 2 received, 0% packet loss, time 1016ms
rtt min/avg/max/mdev = 0.200/0.209/0.218/0.009 ms- Lines 1–4: The tunnel works (the server answers on 10.9.0.1), but the office host doesn't.
- Lines 7–9: Forwarding is off, even though the firewall would allow the traffic.
- Lines 11–12: Install the sysctl.d file and load it: forwarding on, now and after a reboot.
- Lines 14–16: alice reaches the office.
root@vpn:~# nft -a list chain inet filter forward | grep -E "policy|office LAN|everything"
type filter hook forward priority filter; policy drop;
iifname "wg0" oifname "eth1" accept comment "VPN users to the office LAN" # handle 9
counter packets 0 bytes 0 comment "everything else, including user to user, is dropped" # handle 12
root@vpn:~# nft delete rule inet filter forward handle 9
root@alice:~# ping -c 2 -W 1 10.10.0.20 | tail -2
2 packets transmitted, 0 received, 100% packet loss, time 1033ms
root@vpn:~# nft list chain inet filter forward | grep everything
counter packets 2 bytes 168 comment "everything else, including user to user, is dropped"
root@vpn:~# systemctl restart nftables && nft list chain inet filter forward | grep -c "office LAN"
1
root@alice:~# ping -c 2 -W 1 10.10.0.20 | tail -2
2 packets transmitted, 2 received, 0% packet loss, time 1061ms
rtt min/avg/max/mdev = 0.200/0.206/0.212/0.006 ms- Lines 1–4: The forward chain: policy drop, the rule letting VPN users into the office, and the final counter.
-ashows each rule's handle. - Lines 6–9: Delete the office rule, and alice's pings stop.
- Lines 12–13: The final counter caught them.
- Lines 15–20: Reloading the saved ruleset brings the rule back, and alice gets through.
Check your understanding
alice's tunnel shows a fresh handshake, but she can't reach the office host. The server's wg show looks fine. Where do you look first?
- The office network's route back to 10.9.0.0/24.
- alice's private key.
- The server's ListenPort.
- The MTU.