You SSH into a build host, start the VPN client on it, and the session stops answering. Nothing crashed, and nothing looks misconfigured in the obvious sense. The client installed a default route, so replies to your workstation now leave through the tunnel, where your workstation's address is not routable. The interesting part is that this outcome was predictable, and the fix is one route written before the client starts. Assume a server with a single NIC, a gateway of 192.0.2.254, and an administrator connecting from 203.0.113.9.

Record the path before you start

ip route show default names the current gateway and interface, while ip route get 203.0.113.9 shows which interface and source address the kernel would use for your session. Keep both outputs. After the tunnel comes up those two commands should still describe the physical path, and if they describe the tunnel instead, the session is already living on borrowed time.

The established connection is the one that matters, so do not validate with a fresh login only. A new session may succeed briefly through state that the old one no longer has.

Pin the address in the client's own configuration

A hand-added route disappears on reboot and races the client's own table updates, so put the exception where the table is managed. Under OpenVPN the net_gateway keyword resolves to the gateway that existed before the tunnel, so route 203.0.113.9 255.255.255.255 net_gateway keeps exactly one address outside it. WireGuard needs a different approach because wg-quick manages routes itself: set Table = off and add the exception in PostUp, with a matching PostDown that removes it. Both forms express the same intent, and both scope the exception to one address rather than weakening the tunnel for everything.

For IPv6 the same idea takes a single-host prefix, and the wrapped-transport case is worth remembering: if the tunnel's own outer connection lives at a fixed address, that address has to stay outside the tunnel it carries, or the client routes its own transport into itself.

Or take the main table out of the argument

When several services on the host must keep their normal path, stop editing the main table and give the client its own. A network namespace with a veth pair does that cleanly: the client's default route lives in a different stack and only processes started inside it use the tunnel. Policy routing reaches a similar place for one destination, by sending matching traffic to a dedicated table whose default still points at the physical gateway. Either approach leaves the host's main table without a new default route, which is the property that keeps SSH answering.

Both are more machinery than a single host route, and both are worth it when more than one thing on the box has to survive the tunnel.

Assume the pin can fail

A route keyed to your current public address breaks the moment your provider reassigns it. A remote server then needs a path that does not depend on that address: a console, a second management interface, or a jump host that stays outside the tunnel.

Two safeguards cost ten minutes. A watchdog that stops the client if a confirmation login does not appear within a few minutes turns a lockout into a delay, and a second SSH session held open during the change proves the exception works while the tunnel is up. Keepalives alone do not help here, because the path is gone rather than idle; they only make the death easier to notice.

Verify in the order that matters

Connect the tunnel, confirm that the route to your address still leaves by the physical gateway, then open a new session. If the new session works and the old one dies anyway, the cause is connection tracking or a stateful firewall rather than routing, which is a different fix.

Write the pinned address and the command that proved it into the change record. The next person starting a client on that host will need both, and the address may have changed by then.

ບົດຄວາມທີ່ກ່ຽວຂ້ອງ