The setting looks like a per-application switch, so people bind the client to the WireGuard interface and expect only that client to use the tunnel. What the binding actually does is choose a source address. The kernel then looks up a route for the destination, finds the ordinary default, and sends the packet out of the physical link with the tunnel address attached. Only a route selected for that source address completes the intent.

The mechanism has two halves: a routing table that only the tunnel's traffic should use, and a rule that sends the matching source there.

Binding Sets a Source Address, Nothing More

Linux picks an egress interface by looking up the destination in a routing table, after policy rules have had a chance to redirect that lookup. Rules can select on the source address, a firewall mark, the incoming interface, a UID or even a port; each is another way of saying this traffic rather than all of it. The VPN client's configuration picks the table that hold the tunnel routes; the rule picks which traffic is allowed to consult it. Binding alone supplies neither.

That is why the failure looks so strange. The application is definitely using the tunnel address, the tunnel is definitely up, and the traffic is definitely leaving through the wrong interface. Every statement is true, and together they describe a system where nothing connected the address to the route.

Give the Tunnel Its Own Table

wg-quick installs routes wherever the profile tells it. Adding Table = 40 under [Interface] moves them into table 40 instead of the main table. Bring the interface up and confirm they arrived: ip -4 route ls table 40 should show the tunnel's routes, and ip -4 route get 10.66.127.142 shows how the kernel currently resolves one of them. With the routes in their own table, the main table keeps working for everything else.

Do the same for IPv6 rather than matching a broad prefix. ip -6 rule add from fd00::2/128 lookup 40 pins one address; fc00::/8 can collide with other interfaces on the host and produce a rule that matches more than intended.

Add the Source Rule and Watch the Counters

Then connect the halves: ip -4 rule add from 10.66.127.142/32 lookup 40, with that same address attached to the WireGuard interface. Put both rules in PostUp so a reconnect restores them. Verify with a real request rather than a setting: ip route get 1.1.1.1 from 10.66.127.142 should name the tunnel device, and curl --interface wg0 ifconfig.me should report the tunnel's address rather than the home one.

When it still fails, read ip -s rule. A rule that exists but never matched shows zero counters, which distinguishes a wrong rule from no rule at all. That one command saves a lot of guessing about whether the application ignored its interface setting.

The Reverse Case: Keep SSH Out of the Tunnel

The opposite problem is just as common: wg-quick owns the default route and the SSH session to the server dies mid-session. Its rules sit at a low priority number, so an exception has to be evaluated earlier than they are. ip rule add dport 22 ipproto tcp table main priority 123 does that on a recent iproute2; where the selector is unavailable, mark the flow in nftables and match the mark instead.

Check the reply path too. An SSH session that establishes and then goes quiet usually has asymmetric routing, with one direction leaving through the tunnel and the other through the physical interface. ip route get for the server address tells you which way the reply would go.

Record the table number, the source rule and the exception in the profile itself. All three belong to one design and will be edited together.

Mikanda mikatakata