An application binds to the VPN's address, and the connection still leaves through the ordinary default route. Nothing in the bind failed; it did exactly what a bind does. Binding a socket to a local address chooses the source address the kernel puts in the header. It does not install a route, and it does not create a policy rule that sends packets carrying that source into a particular tunnel.

The rest of this runbook is the check order I use, with the command that produces each observation.

What a bind actually decides

A bind assigns a local address to the socket. The kernel then runs the same route lookup it would have run otherwise, unless a policy rule changes which table that lookup reads.

That is why a packet can leave with a VPN source address on a physical interface. On the far side it arrives from a private range the network never expected on that path, the return traffic has no route back, and the connection hangs or resets.

The Linux IP socket reference documents the bind semantics. The routing half lives in ip-rule and ip-route, and those are separate manuals for a reason.

Confirm the address belongs to this namespace

An address you can bind has to exist on an interface inside the network namespace the application runs in. A peer address, a gateway address, or an address that exists only in another namespace is not bindable, and the call fails with EADDRNOTAVAIL.

ip -br addr show
ip netns identify <pid>
ss -tanp | grep <port>

Run those three in the same context as the application: same namespace, and the same user if you can get it. An old net30 diagram is not evidence about which address your current topology assigned to the client. Read the interface that is actually up.

Read the lookup for that source and destination

One command answers the question the bind did not:

ip route get 203.0.113.10 from 10.8.0.2
ip rule show

The first line prints the route a packet from that source to that destination would take, including the outgoing interface and the preferred source. If it names the physical interface, the bind and the route disagree, and the packet will leave with a source the path cannot return to.

ip rule show then tells you whether any rule would have selected a different table. Priorities matter here, because the first matching rule wins and a catch-all above your rule makes your rule invisible.

Install a policy rule only if that is the design

Source-based routing is a deliberate design, not a repair for one application. If the policy owner wants traffic from the tunnel subnet to use the tunnel, the rule belongs in that owner's configuration rather than in a one-off command on your laptop.

A workable shape is a dedicated table with a default route through the tunnel, plus a rule that sends the VPN source range into that table. Add the exceptions the design needs, because a table containing only a tunnel default will capture destinations meant to stay local or reachable over the physical path.

ip route add default dev tun0 table 100
ip rule add from 10.8.0.2 table 100 priority 100
ip route get 203.0.113.10 from 10.8.0.2

Roll back with ip rule del and ip route flush table 100. Anything added this way disappears on reboot unless a manager writes it, which is both a safety property and a reason not to keep the only copy in your shell history.

Verify with three observations

Record the source the application requested, the route the lookup selected, and the source the server actually saw.

Those three can disagree, and each disagreement points somewhere different. A capture on the tunnel interface proves the packet left with the right source. A capture on the physical interface proves it left the wrong way. The server-side view is the only one that proves the path end to end.

If the application offers a bind-to-device option or a per-app routing mode, treat it as a different mechanism with different constraints. Similar-looking settings do not all resolve to the same kernel decision.

Artiglyn mynjee