TTR Networks

Intermediate · WireGuard on Debian: users, routing and keys · section 6 of 10

Split and full tunnels

✓ Lab-tested 21 October 2026 on Debian 13.7, wireguard-tools 1.0.20210914, nftables 1.1.3

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:

  1. It marks WireGuard's own packets with a firewall mark (fwmark 51820).
  2. It puts a default route via wg0 in a separate routing table, 51820.
  3. 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.
  4. Another rule (suppress_prefixlength 0) still lets more specific routes in the main table win, such as the local LAN.
  5. 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.

alice's config for a full tunnel
# /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.
The full tunnel in action (real output)
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.