Two uplinks, one tunnel, and the traffic leaves through the one you did not choose. The private subnets are reachable, the log reports a successful connection, and the metered link is the one carrying it. Nothing in the OpenVPN output mentions a gateway, because the client never picked one. It opened a socket to a remote address and let the kernel decide how to reach that address.

The tunnel address is not the endpoint

Two different paths exist in every session. The outer path carries the encrypted transport to the server's public address. The inner path carries whatever the server pushes into the tunnel, and routing rules for one have no effect on the other.

The remote entry in the profile names the outer destination, so that is the address to pin. It is also the value that changes when a hostname resolves differently or the profile lists several remotes.

Find the address the client actually dialled

Read the live socket rather than the configuration file. The socket table shows the peer the process actually connected to, which is the value an exception has to match. A profile can be edited after the session started, and the socket is the only evidence of what was dialled:

ss -tunp | grep -i openvpn
journalctl -u openvpn-client@work --no-pager | grep -i "Peer Connection Initiated"

The log line names the peer address and port at connection time. If it disagrees with the profile, something in front of the client is rewriting the destination before the connection starts.

Check the address family as well. A dual-stack profile can connect over IPv6 while your exception covers only IPv4, and the traffic then follows a different rule than the one you wrote. The same applies to a proxy that answers on behalf of the endpoint. Both cases produce a tunnel that works while your route does nothing.

Pin the endpoint and read the policy order

A host route is more specific than any default. A /32 for the peer address therefore wins inside the table that contains it. The usual form names the gateway and the device explicitly:

ip route add 203.0.113.40/32 via 192.168.10.1 dev eth0

That route only helps if the lookup reaches that table. Policy rules are evaluated in order and the first match decides, so a rule that sends everything to a second table bypasses your host route in main. Read the rules before assuming the route is authoritative:

ip rule show
ip route get 203.0.113.40

Ask the second command with the same source address or mark the client uses when policy depends on them. It reports the route the kernel would select, which is evidence about the lookup and not proof that the live socket is using that path.

When the endpoint moves

A hostname that resolves to several addresses invalidates a permanent /32 as soon as the answer changes. A profile with multiple remote lines has the same problem, because the client may land on any of them. Refresh the route from the client's own lifecycle hooks instead of adding it by hand. An up script that reads the negotiated peer and installs the host route keeps the exception aligned with reality, and a matching down script removes it.

Record the refresh in the same place as the rest of the host's routing configuration. A route that exists only until the next reboot is a test, not a fix.

Keep the inner policy separate

Moving the outer path does not grant access to a new private subnet, and it does not change which destinations the server pushes into the tunnel. The underlay route chooses the wire; the pushed routes choose what enters the tunnel. Keeping those two decisions separate is what makes the split predictable.

If the goal is only to keep the encrypted transport off a metered link, leave the default route alone and add the single host exception. Replacing the machine-wide default to move one VPN connection breaks every other application on the host.

Confirm the result with two independent observations: the socket's local address and device, and the route the private destination resolves to. When both match your intent, the split is real. The OpenVPN 2.6 manual covers the remote and local binding options that decide which address the client dials.

Аффингæй хæстæг статьялтæ