Switch on a full tunnel and every destination now belongs to the VPN interface, including the machine that terminates it. The outer packets carrying the tunnel are addressed to that machine, so the route ends up consuming the transport it depends on. One extra specific route resolves it, and the reason is worth understanding. Without that reason, the next rebuild reintroduces the fault.

A Route That Eats Its Own Transport

Encapsulation wraps each packet in a second header. The outer one is addressed from the local physical address to the server's public address, and it has to cross the physical link before any tunnel can exist.

Now let the lookup for that public address choose the tunnel. To send the packet, the machine must encapsulate it, which requires a route to the same address, which returns the tunnel. Clients handle this in one of two ways. A careful one refuses to install the default route and reports an error, while a less careful one reports success and then moves nothing. The quiet failure is the one that costs an afternoon.

Longest-prefix match decides the lookup before any metric is consulted, so a single-address route outranks every default route on the machine. That is the entire mechanism behind the repair.

Install the Exception Before the Default

Create a single-address route for the server, with the physical gateway as the next hop, before the default route moves. Order matters: installing it afterwards means living with a broken tunnel while you type.

Most clients do this unprompted. OpenVPN looks up the remote address and pins it before rewriting the default gateway, and its route-delay option exists for platforms where that sequence races. wg-quick takes a different road entirely, marking packets and consulting a policy rule rather than adding an exception. Different mechanisms, same requirement: the outer packet has to escape the tunnel it creates.

ip route add 203.0.113.10/32 via 192.168.1.1 dev eth0
ip route get 203.0.113.10

The lookup output is the acceptance test. It has to name the physical device and the physical next hop. A tunnel device in that answer means the exception is missing, or was placed after something that overrides it.

The Mirror Image on the Server

The same trap waits on the far side, reversed. Give the server a default route through another tunnel, or push it a route that covers the clients' public addresses, and its replies acquire a wrong-way habit.

Picture a client that connects and sends a request. The reply is addressed to that client's public address, which now matches a tunnel route, so it departs from a different interface carrying a source the client never dialled. The client can receive that packet and still discard it, because nothing ties it to the request it sent. Work from the server's routing decision rather than from the client's timeout, since only one of them shows the actual choice.

ip route get 198.51.100.44 from 10.90.0.1
ip rule show
sudo tcpdump -ni any host 198.51.100.44

Three repairs exist and they are not equal. Pinning the service to the physical address, or translating the source, both hide the routing decision instead of correcting it. Sending reply traffic for the tunnel pool through a dedicated table fixes the cause, and the other two are acceptable only while the routing design is frozen.

Exceptions the Tunnel Keeps Stealing

A handful of jobs need the open internet even while the tunnel owns the default route, and each one needs its own exception. The dynamic DNS updater is usually the first to break.

It resolves the provider, opens a connection, and finds its own packets travelling through a tunnel whose address changed seconds ago, so the update fails or publishes the wrong address. Three ways out: pin the provider with a host route through the physical gateway, run the update before the tunnel rises, or give the updater a policy table the tunnel never touches. Time synchronisation has the same exposure on a device that boots without a resolver, and certificate validation endpoints belong on the list for the same reason.

An exception nobody wrote down is an exception the next rebuild will silently drop.

Prove It After a Reconnect, Not Before

Test three destinations, not one: the endpoint, a private address behind the tunnel, and a public address beyond it. A single passing lookup says almost nothing about the other two.

ip route get 203.0.113.10
traceroute -n 203.0.113.10
ip route get 10.90.0.5
ip route get 1.1.1.1

Then bounce the connection, because a client that rebuilds its table on reconnect will discard a route you typed by hand. With a kill switch in play, check that the endpoint is permitted, or the tunnel cannot return after a network change and the symptom looks like an outage. Store the exception in the profile or the unit file rather than in a shell history, so the reconnect recreates it instead of erasing it.

Ũhoro ũhĩtĩte