Split and full tunnels
Whether a user's internet traffic goes through the VPN is decided entirely by their own AllowedIPs. Changing alice's to 0.0.0.0/0 gives her a full tunnel; bob keeps his split one. The server only has to allow and NAT the traffic, which the firewall already does for alice's address.
A full tunnel has a chicken-and-egg problem: the tunnel's own encrypted packets must still go to the server over the real network, not into the tunnel. OpenVPN solves it with two half-default routes and a host route. wg-quick does something cleverer, and its output shows it:
- It marks WireGuard's own packets with a firewall mark (
fwmark 51820). - It puts a default route via
wg0in a separate routing table, 51820. - A policy rule sends every packet without that mark to table 51820, so all ordinary traffic goes into the tunnel, and WireGuard's marked packets use the normal table and the real default route.
- Another rule (
suppress_prefixlength 0) still lets more specific routes in the main table win, such as the local LAN. - It also loads a few nftables rules of its own, which protect this setup against spoofed packets.
The web server's log is the proof: alice arrives as 203.0.113.10 (the VPN server, through NAT), and bob as himself.
# /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
# Full tunnel: everything goes through the VPN.
AllowedIPs = 0.0.0.0/0
# Send a packet every 25 s, so NAT and firewalls on the way keep the path open.
PersistentKeepalive = 25- Lines 10–11: The only change: everything belongs to the server's peer.
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
[#] wg set wg0 fwmark 51820
[#] ip -4 route add 0.0.0.0/0 dev wg0 table 51820
[#] ip -4 rule add not fwmark 51820 table 51820
[#] ip -4 rule add table main suppress_prefixlength 0
[#] sysctl -q net.ipv4.conf.all.src_valid_mark=1
[#] nft -f /dev/fd/63
[#] wg set wg0 private-key /etc/wireguard/alice.key
root@alice:~# ip rule; ip route show table 51820
0: from all lookup local
32764: from all lookup main suppress_prefixlength 0
32765: not from all fwmark 0xca6c lookup 51820
32766: from all lookup main
32767: from all lookup default
default dev wg0 scope link
root@alice:~# curl -s -o /dev/null -w "%{http_code}\n" http://203.0.113.80/
200
root@bob:~# curl -s -o /dev/null -w "%{http_code}\n" http://203.0.113.80/
200
root@web:~# tail -2 /tmp/web.log
203.0.113.10 - - [21/Oct/2026 06:48:15] "GET / HTTP/1.1" 200 -
198.51.100.22 - - [21/Oct/2026 06:48:15] "GET / HTTP/1.1" 200 -- Lines 6–10: The policy-routing trick: WireGuard's own packets carry mark 51820; everything else uses table 51820, whose default route is
wg0. - Line 11:
wg-quick's own nftables rules, protecting this setup. - Lines 15–20: The resulting rules (0xca6c is 51820). Rule 32764 lets more specific main-table routes win first.
- Lines 28–30: alice arrives from the VPN server's address; bob, on a split tunnel, from his own.
Check your understanding
Who decides whether a WireGuard user's internet traffic goes through the VPN?
- The user's own AllowedIPs; the server can only allow or block it.
- The server, by pushing a default route.
- The server's AllowedIPs for that user.
- The NAT rule.