Dear OPNSense friends,
I seek desperately for help.
Please have a look at the following:
On an existing, working WireGuard site-to-site tunnel between two OPNsense boxes, I want to selectively route the internet traffic of a few specific LAN hosts (source-based) out through the remote site's WAN, exactly as described in the official WireGuard Selective Routing how-to.
LAN-to-LAN works perfectly in both directions. The selective internet routing does not — and I've narrowed it down with packet captures to a very specific point.
Versions
OPNsense: OPNsense 25.7-amd64
WireGuard: kernel/base
Topology
Site A (the source site): WAN A.A.A.A (static, directly on the OPNsense WAN), LAN 192.168.1.0/24, OPNsense LAN IP 192.168.1.1. WG instance listen port 51821, tunnel address 10.3.3.1/24. Hosts I want to route: 192.168.1.201, 192.168.1.202 (alias CRAWLER).
Site B (the exit site): behind a router with a port-forward for UDP 51821; LAN 172.30.0.0/16; WG tunnel address 10.3.3.2/24. Site B has a working secondary WAN and already policy-routes certain local hosts out through it.
Tunnel is up and healthy: recent handshake, transfer counters increasing, LAN-to-LAN both directions works.
Site A config (per the Selective Routing how-to)
Peer (→ Site B) Allowed IPs: 0.0.0.0/0, 172.30.0.0/16, 10.3.3.2/32 — confirmed active via wg show (allowed ips: 0.0.0.0/0, ...).
Instance: Disable Routes = enabled; Gateway field set.
Static route: 172.30.0.0/16 via the WG gateway (this works — LAN-to-LAN).
Interface: WG instance assigned + enabled, IPv4/IPv6 config = None.
Gateway GW_WG_S2S on the WG interface (Far Gateway).
Firewall rule on the LAN interface, placed above the default "allow LAN to any": source = alias CRAWLER, destination = inverted RFC1918_Networks, gateway = GW_WG_S2S.
States reset; MSS clamping present.
Symptom
From a crawler host (192.168.1.201):
ping 172.30.1.254 (LAN B, via the static route) → works
ping 10.3.3.2 (peer tunnel IP) → works
ping 8.8.8.8 (internet, should go via the tunnel) → 100% loss; traceroute shows hop 1 = 192.168.1.1, then * * *
Diagnostic evidence (the key part)
While pinging 8.8.8.8 from the crawler:
Firewall live log: the ICMP to 8.8.8.8 matches the policy rule, action = pass, source 192.168.1.201, dest 8.8.8.8 (so the rule matches and the gateway is applied — the rule is not skipped).
Packet capture on the LAN interface: the echo request arrives on LAN (correct src/dst MAC).
Packet capture on the WG interface (wg1): the packet never appears — it is never handed to the WireGuard interface.
So: the rule passes with the gateway assigned, but pf route-to does not place the packet onto the WireGuard interface — it's dropped before WireGuard. This means it is not a cryptokey-routing / Allowed-IPs problem (Allowed IPs already include 0.0.0.0/0, verified via wg show).
The contrast is the smoking gun: traffic to 172.30.0.0/16 reaches the tunnel fine — but that goes via a static route (FIB), not via firewall route-to. Same gateway, same interface: the static route delivers to wg1, the firewall route-to does not.
Already tried
Gateway IP = an arbitrary unused tunnel IP + Far Gateway, and gateway IP = the peer's real tunnel IP (10.3.3.2). Using the peer's real IP produced a routing loop (10.3.3.2 became unpingable / TTL-exceeded), so I switched back to an arbitrary IP.
Gateway monitoring on (gateway went down — the monitor probe to the WG side wasn't answered) and off (gateway forced up / green).
Verified with a genuinely green gateway + 0.0.0.0/0 in Allowed IPs.
Rule order confirmed (above the default allow); destination invert confirmed.
States reset; WireGuard restarted; instance Gateway field set to match the system gateway.
Question
Why does pf route-to on a WireGuard interface not deliver the packet to wg, while a static route via the same gateway does? Is this a known limitation / version issue with route-to on WireGuard interfaces?
What is the correct, current way to make source-based selective routing (0.0.0.0/0 for specific source hosts) work over an existing S2S tunnel — without hijacking the whole default route?
Any pointers appreciated — happy to post wg show, the exact rule, netstat -rn, or captures.