Two people asked me for opposite outcomes last month: one wanted IPv6 covered by the VPN, the other wanted the office printer to keep working. A single ::/0 route cannot deliver both by itself. Whether that route belongs inside the tunnel comes down to three conditions, and the first one rules it out for most commercial exits. If the exit carries no IPv6, routing the default into it hides your traffic in a tunnel that has nowhere to send it.

Three conditions decide the answer

The default route is one line, ::/0, and it matches any IPv6 destination without a more-specific route (RFC 4291). Installing it is easy. Deciding whether you should comes down to three conditions.

Condition one: the exit you connect to has working IPv6 and is willing to forward it. Condition two: your client can keep a path to the tunnel endpoint, which usually sits inside the range you are about to route. Condition three: you can name the local IPv6 destinations that must stay off the tunnel.

Each condition fails in its own way, which is why guessing costs an evening. A failed condition one gives you silence and a route table that looks correct. A failed condition two kills the handshake a few seconds after it starts. A failed condition three leaves a working tunnel and an unhappy colleague at the printer.

What you seeDecision
Exit answers IPv6 probes, no local IPv6 devices to protectRoute ::/0 into the tunnel
Exit answers IPv6 probes, printer or NAS reachable by a global addressRoute ::/0, then give those prefixes a route of their own
Exit is IPv4-only inside the tunnelDo not route ::/0; block IPv6 on the client
Endpoint is reached over IPv6 and sits inside ::/0Install a host route for the endpoint first

Does the exit actually carry IPv6?

A tunnel interface with an IPv6 address proves nothing about the far side. Many commercial profiles are IPv4-only inside: the server hands you a v4 address and drops everything else. Install a default from AllowedIPs on top of that and the kernel will happily send IPv6 into an interface whose peer never sees it.

Test from the client while the tunnel is up. ping -6 -c 3 2001:4860:4860::8888 and ip -6 route get 2001:4860:4860::8888 answer different questions: whether a reply comes back, and which device the kernel chose. When the second names your tunnel and the first returns nothing, the exit is the problem, not the route.

If you run the exit yourself, the client test can pass and the path still dead-ends. Confirm net.ipv6.conf.all.forwarding=1 on the gateway and that its firewall accepts packets forwarded out of the tunnel. A correctly routed packet into an exit that refuses to relay looks exactly like a routing bug from the client side.

Can the client keep its path to the endpoint?

This is the one that caught me on a laptop with an IPv6-only WireGuard endpoint. The endpoint address is an ordinary global address, so it falls inside ::/0. Route the whole prefix into the interface and the handshake packets chase themselves into the tunnel they are still trying to build. The tunnel never comes up, and nothing in the client output says why.

wg-quick handles this on Linux. It derives routes from AllowedIPs, installs them in its own table, and adds a policy rule so traffic to the endpoint is resolved through the main table first: the suppress_prefixlength 0 rule you find in ip -6 rule. That is why wg-quick up succeeds where a hand-written ip -6 route add default dev wg0 cuts you off.

Check both states with ip -6 route get <endpoint-v6>, before and after bringing the tunnel up. If the answer changes to your tunnel device, you have removed the path the tunnel needs. When you write the routes yourself, add a host route for the endpoint through the original gateway, or connect to the endpoint over IPv4 and leave IPv6 to the payload.

What has to stay off the tunnel?

More-specific routes beat the default, so an on-link prefix or a ULA range with a route of its own keeps working without extra effort. That is the quiet reason so many full-tunnel IPv4 setups survive: the LAN prefixes are more specific than the default and never enter the tunnel.

It breaks when a local device is reachable by a global address, which is normal for a printer handed a public prefix by the same router, a NAS, or a television. Those addresses match ::/0 and nothing else, so they move into the tunnel with everything else and disappear.

Decide per destination, not per protocol. If two or three local hosts matter, give their prefixes a route out the physical interface, or drop them from the negotiated set. If the list keeps growing, the honest answer is split tunneling for IPv6 with a written exception list, not a default you keep patching.

If the answer is no, block it

Leaving IPv6 unrouted is not the same as leaving it alone. A host with a working IPv6 default keeps using it while the tunnel carries IPv4, so the traffic leaves beside the VPN and every service that prefers IPv6 sees your real address. That is a leak, not a split tunnel.

Pick one and write down which you picked. Route the default and accept the trade-offs above, or disable IPv6 for the duration of the tunnel. Omitting the prefix from AllowedIPs is not enough on its own, because the operating system keeps its own default. On Linux the blunt version is sysctl -w net.ipv6.conf.all.disable_ipv6=1; the finer version drops IPv6 outside the tunnel with a firewall rule. Phone clients ship an explicit block-IPv6 toggle that does the same job.

Verify it like a leak

Bring the tunnel up and read the state instead of trusting the toggle. ip -6 route show default should show one default, and ip -6 route get for a destination outside your LAN should name the tunnel device. Repeat both after a disconnect: a default that outlives the tunnel is the bug you were looking for.

Then test the other direction. From a host on the local network, reach the client on its local IPv6 address. If that stops working, condition three was never satisfied, whatever the route table claimed at the time.

Write the outcome where the next person will find it: which prefixes stay local, why, and what the client does after a disconnect. Two lines in a runbook save the next engineer from rediscovering the endpoint loop at midnight.

འབྲེལ་བའི་རྩོམ་ཡིག