Putting 0.0.0.0/0, ::/0 in a peer's AllowedIPs is the step everyone remembers, and it only decides which traffic the peer is allowed to carry. Those prefixes create no routes, forward nothing on the far side, and leave every application's resolver exactly where it was. Four separate things have to hold at once for a full tunnel to be a full tunnel, and they are best checked in the order below, because a later check cannot rescue an earlier one. Each check below is a fact you can read off a machine, not a setting you hope was applied.

What the peer prefix selects, and what it does not

AllowedIPs is both a selector and an authoriser. It tells the local side which destinations may be sent to this peer, and it tells the receiving side which source addresses this peer is allowed to present. That is the entire job. Nothing in that description creates a route on the host, enables forwarding on the remote side, or touches name resolution.

This is why a configuration can look complete and behave like a split tunnel. The prefix says the peer would accept everything; the host's routing table decides what actually arrives there, and the gateway decides whether anything leaves. None of those three decisions lives in the peer stanza.

Routing the client, not just the peer list

Managers such as wg-quick program the host's routing table for you, which is why two identical config files behave differently depending on how the interface came up. A minimal profile is short enough to read in one go:

[Interface]
Address = 10.8.0.2/32
DNS = 10.8.0.1

[Peer]
AllowedIPs = 0.0.0.0/0, ::/0
Endpoint = vpn.example.net:51820

Two details carry the work. The Address is a single host prefix rather than a subnet the client believes it owns, and it is the manager that converts the default AllowedIPs into an actual default route. Bring the same file up with ip link and wg setconf and no routes appear at all, which looks like a broken tunnel and is a healthy one with the routing step skipped. The wg-quick manual documents the routing and Table behaviour those managers add.

Read the result instead of the file: ip route get 1.1.1.1 should name the tunnel interface, and wg show should report a recent handshake with a transfer counter that moves. A handshake with no route means the manager never ran, and editing the profile again will not fix it. Start the interface the way the user will start it, or the test measures your shell instead of the deployment.

Keeping the endpoint outside the tunnel

A full tunnel captures every destination, including the one it must talk to in order to exist. Managers handle that by adding a host route for the endpoint through the original gateway, which is why a correct configuration almost always contains one deliberate exception. Applying half of the configuration with a manager and the other half by hand is the usual way this exception goes missing.

If the endpoint is a hostname that resolves to several addresses, or to a family you do not carry, the exception ages badly and the tunnel can fail after a reconnect that looked routine. Pin the endpoint to an address you control, or confirm the manager re-evaluates it, and re-read the host route after every network change.

The same mechanism explains why a full tunnel and a captive portal fight. The portal expects to intercept the traffic your tunnel now captures, so on hotel and airport networks you bring the tunnel up after the portal login, never before.

Forwarding and the reply path on the gateway

On the gateway, enable forwarding for the family you carry, allow the forward chain in both directions when you translate, and confirm the return path with a source-specific lookup. None of that follows from a successful handshake, because a handshake only proves the two peers can exchange UDP. It says nothing about what happens to the decapsulated packet afterwards.

sysctl net.ipv4.ip_forward net.ipv6.conf.all.forwarding
ip route get 1.1.1.1 from 10.8.0.2

The lookup runs on the gateway rather than on the client, or you will be reading a different routing table. If it names the wrong interface, or reports no route, then no client-side change will produce an internet exit. When the gateway translates client addresses on the way out, the reply depends on that translation state, so the rule has to exist for both directions of the flow and not just the outbound one.

DNS and the failure state, tested apart

The protocol itself configures no application's resolver. wg-quick applies the DNS line through the distribution's resolver integration, containers and browsers with their own stacks may ignore it, and a name can therefore fail while the tunnel carries packets perfectly. Test with a hostname rather than an address, and note which server answered instead of assuming the one in the profile did.

Address families are a separate test with a separate answer. A v4 default says nothing about what an application's v6 connection will do, and a tunnel that silently leaks the second family is worse than one that fails loudly. Query a name over each family, read the interface each request used, and check translation counters before and after, since a rule that matches nothing looks identical to a rule that is missing.

Finally, test the state you want when the tunnel stops. A full tunnel should fail closed for the traffic it claims: if the interface goes down and applications quietly continue over the physical link, the design is a split tunnel with extra steps. Write the intended failure behaviour down, check it deliberately, and finish by recording the four answers — which manager brings the interface up, which route carries the default, who forwards and translates at the gateway, and which resolver replies.

Nuŋlɔɖi siwo ku ɖe eŋu