Three designs solve the same lab problem: a small LAN that must leave through a WireGuard peer while the gateway itself keeps its ordinary uplink. The tempting shortcut is to move the router's default route into the tunnel, which breaks the box's own management path and everything else it forwards. The interesting option is policy routing scoped to one interface and one prefix. Pick between them by answering three questions about the machine you actually have.

Decide where the exit lives

First question: does this host forward for anything else? A dedicated lab gateway can live with a drop-by-default forwarding policy. A router with production duties cannot, and layering a competing ruleset over a managed firewall is how outages get scheduled. Second: can you own two policy objects and their ordering across reboots? Third: does the peer you have authorize your tunnel source address and offer internet egress? A peer that only exposes a private network is a different product, and no local routing change fixes that. If any answer is no, put the exit in a namespace or a throwaway virtual machine instead.

A namespace or a VM costs memory and gives you a clean blast radius. Policy routing costs two rules and leaves you one box to debug.

Pick the exit that authorizes your source

Most failures in this lab happen before a single firewall rule exists. The exit operator must accept the source address your packets carry after translation, which in this design is the tunnel address rather than the LAN prefix. That is why masquerading belongs in the design from the start rather than as a late addition.

Confirm three things with the operator: the peer authorizes your tunnel address, it forwards IPv4 instead of terminating at itself, and it applies egress NAT. Without the first, your packets arrive and vanish. Without the third, they leave with a private source and die on the next hop. Then check that wg-quick is not installing its own default route, which is what Table = off prevents; that setting suppresses route creation, not the peer's AllowedIPs checks or its DNS handling.

Two policy objects do the steering

Steering needs a table and a rule. The table holds two routes, the LAN prefix out of the LAN interface and a default out of the tunnel. The rule sends traffic that arrives on the LAN interface and carries that prefix into the table. Use the iif selector alongside the source match, because the gateway's own WireGuard packets originate locally and never match an incoming interface.

The guard comes before the lookup, not after it. That ordering is the entire safety property of the design.

The guard is a second rule on the same selector whose verdict is unreachable. Give it a higher numeric priority so the lookup still wins while the tunnel is up. When the tunnel route disappears, the lookup returns nothing and the guard stops the search rather than letting it fall through to the WAN default in the main table.

ip rule add from 192.168.50.0/24 iif lan0 lookup 51861 pref 1000
ip rule add from 192.168.50.0/24 iif lan0 unreachable pref 1001
ip route add table 51861 default dev wg0

Close the fallback gap before you open it

Install the guard first and the lookup second. Doing it the other way leaves a window, seconds long, in which a test client's traffic can reach the uplink.

The filter completes the picture. In the forward hook, accept the LAN-to-tunnel direction for the test prefix and the established return direction, then drop everything else; NAT for that prefix onto the tunnel interface goes in postrouting. Leaving policy drop on the forward chain is what turns a missing route into silence instead of a leak. Reverse-path filtering needs loose mode on the tunnel interface, because a public source arriving decapsulated has its best reverse route on the WAN side. Linux takes the higher of the all and per-interface values, so setting the tunnel interface is not the same as weakening validation everywhere.

Prove it with the failure case

Ask the kernel a precise question by naming the client's source address and the interface it arrives on. A lookup without both answers a different question and will look correct either way. Then look at the firewall counters, the peer's transfer state, and the client's own service.

Test removal properly. Stop the tunnel while leaving the rule and the filter in place: the client should stall and the gateway's own traffic should keep working. Then stop the peer without touching the local interface, which is the case that catches designs relying on route presence alone. Finally reboot and re-check rule ordering, since a teardown hook that removes the guard hands the whole design back to the main table. Record the table number, priorities and interface names on the change ticket, because the next person to add a rule is the one who will break this.

Pennadoù liammet