field noteWireGuard · Tailscale · Linux routing
Two VPNs fighting over the same packets
With a full-tunnel WireGuard VPN up, SSH over the tailnet timed out. The fix was one routing rule in the right place.
An admin laptop ran a full-tunnel WireGuard VPN and Tailscale at the same time. Both came up cleanly, yet SSH to any tailnet address (100.64.0.0/10) timed out.
What was going on
A full-tunnel VPN sends everything through itself using Linux policy routing: a rule, consulted before the normal routing table, that points traffic at the VPN's own table. Tailscale also uses policy routing, with its routes in table 52. Whichever rule matches first wins, and here the VPN's rule caught the tailnet traffic before Tailscale's table was ever looked at, so the packets left through the wrong tunnel.
ip rule show # the order rules are tried in ip route get 100.64.0.12 # which table and device win for one address
The fix
Add one rule, ahead of the VPN's, that sends the tailnet range to Tailscale's table, and keep it with the VPN config so it comes and goes with the tunnel.
[Interface] # ... PostUp = ip rule add to 100.64.0.0/10 lookup 52 priority 2500 PreDown = ip rule del to 100.64.0.0/10 lookup 52 priority 2500
What I took from it
- When two VPNs disagree, read
ip rulebefore anything else: rule order decides, not the order the tunnels came up. - Put the fix where the cause lives. A rule added by hand disappears on the next reconnect.