A prefix appears on the laptop when the tunnel comes up, so it feels like the VPN installed it. It might have, but the same result can come from a client script that runs on connect, a connection profile that lists the subnet, or a static route that was there all along and only started working once the interface existed. Picking the wrong owner means the route comes back after every cleanup, and the next person repeats the same fix. The sequence below separates the three before changing anything.

Step 1: name the mechanism that installs prefixes

Different protocols supply reachability in different ways, and calling all of it an advertisement hides the distinction. An OpenVPN server pushes a route directive over the control channel and the client installs it unless a pull filter blocks it. A WireGuard configuration lists allowed prefixes locally, which the client uses both for crypto-key routing and for the routes it adds. An IPsec tunnel agrees traffic selectors with the peer, and the kernel installs routes from those selectors.

Identify which of those your tunnel uses before reading any table. A route pushed at connect time is not a routing protocol announcement, and it carries no promise that the prefix is reachable behind the far end. Note the mechanism next to the prefix, because it decides where the fix belongs.

Step 2: diff the table across a connect

Save the main table with the tunnel down, bring it up, save it again, and compare the two captures. Anything that appeared belongs to the connection lifecycle, and anything that stayed belongs to local configuration.

Capture it with ip route show table main > /tmp/route-down.txt, connect, and capture again. Do the same for ip -6 route if the site uses IPv6, because a missing prefix there produces failures that look unrelated to routing.

Step 3: read the metric, not just the presence

Two entries for the same prefix can coexist, and the kernel picks the lower metric. A static route with metric 50 will keep winning over a pushed route at 100 even though both are visible, which is why 'the route is there' and 'traffic uses it' are different statements.

Ask the kernel directly with ip route get 10.20.5.7 and read the interface it reports. If traffic leaves through the physical adapter while a tunnel route sits in the table, metric or prefix length explains it, not the VPN client. Repeat the lookup for a host you know lives behind the tunnel and for one you know does not, and the difference shows whether the split behaves as intended.

Step 4: find the entries that outlive the tunnel

Static routes survive a disconnect by design, and that is where stale entries come from. A persistent route pointing at an interface that no longer exists can blackhole a subnet quietly, and one that points at a reused interface name can send private traffic out of the wrong doorway after a reboot.

Check the persistent side in the place your distribution uses, whether that is a systemd-networkd unit, a NetworkManager profile, or a script in the client's up directory. Fixing the live table with ip route del only holds until the next connect. Treat that delete as a test of the hypothesis rather than the repair.

Where the far end supplies the wrong coverage, raise it with the operator instead of filtering everything. Dropping all pushed settings also discards DNS and split-tunnel policy that the profile depends on.

Step 5: confirm the return path before blaming the client

For a site-to-site or a subnet behind the tunnel, the far side needs a route back to your client address and forwarding enabled on the gateway. A one-way path looks exactly like a client routing bug, except the handshake and the outbound packet both succeed.

Reverse-path filtering adds a second trap on Linux gateways. A reply arriving on a different interface than the route predicts gets dropped by strict mode, and asymmetric paths through two providers trigger it reliably. The drop counters on the gateway, not on the client, tell you whether filtering is the cause.

Step 6: test the resource, not the ping

Once the chosen route is correct, open the actual resource and confirm the return traffic. A ping reply proves the path in one direction and nothing about the service, and a service answering on a cached connection proves even less.

Then repeat the whole check after a reconnect. The value of the sequence is that it predicts what the next connect will do, which is why the diff and the metric both belong in the ticket.

Amanqephe ahlobene