There is no per-device switch on a router VPN; the toggle you are looking for is a shortcut that writes policy routing rules on your behalf. The moment one laptop, one port range, one address list or one LAN segment has to use the tunnel while everything else keeps the WAN, you are building a policy route rather than enabling a feature. The job has three moving parts, and each one fails differently: a marked table with no route looks like silence, a mark applied in the wrong chain looks like nothing happened, and a missing NAT rule looks like a tunnel that connects and carries nothing. Two more pieces go missing because they never appear in the mangle rule, and those are DNS and IPv6.

The myth: the tunnel is a client setting

A router-side VPN client does one blunt thing well: it puts 0.0.0.0/0 in the main routing table and points it at the tunnel. WireGuard derives that from AllowedIPs, OpenVPN from a pushed redirect-gateway, and both change the path for every host behind the router. Narrowing AllowedIPs or adding route-nopull changes what the client installs. It does not change who the install applies to, because the client never sees devices. It only sees packets.

Selection by source or destination is a routing decision, so it belongs to the router's policy layer: a mangle rule plus a second table on RouterOS, a MARK plus an ip rule on OpenWrt or DD-WRT, or a firewall rule whose gateway is the tunnel on pfSense. Netgate's WireGuard client recipe is the one to read before guessing, because on that platform the rule gateway, the interface assignment and the outbound NAT entry all have to agree before a single client uses the tunnel.

The second half of the myth is the second router. Buying another box, or moving the device onto a guest SSID, often does work, and the reason is not the hardware: it puts the device on its own subnet, which gives you the source selector the firmware already understands. If you can define that segment on the router you own, the extra box solves a problem you did not have.

Piece one: a tunnel that is not the default route

On RouterOS, add the client without a default route and give the tunnel its own table. The first route is the marked default; the second is the failure half.

/ip route add dst-address=0.0.0.0/0 gateway=VPN_GATEWAY_IP routing-mark=vpn distance=1
/ip route add dst-address=0.0.0.0/0 type=unreachable routing-mark=vpn distance=2

The unreachable route is why a marked table beats an exception list in the main table. When the tunnel drops, marked packets get an immediate unreachable instead of quietly falling back to the WAN, so the failure shows up in the application's error message instead of in a geo-location page you check an hour later.

RouterOS 7 splits what 6.x kept together: you declare the table with /routing table add name=vpn fib, then reference it by name on the route and in the mangle rule. A 6.x snippet that inlines routing-mark without defining the table loads cleanly and matches nothing. The other classic is two 0.0.0.0/1 halves, or two defaults at the same distance in one table: the kernel picks one, and the behaviour can change after a reboot.

Piece two: mark before the route lookup

The selector decides which packets enter that table. RouterOS usually starts with an address list for destination policy, or a source address for device policy:

/ip firewall mangle add chain=prerouting src-address=192.168.88.50 action=mark-routing new-routing-mark=vpn
/ip firewall mangle add chain=prerouting dst-address-list=via-vpn action=mark-routing new-routing-mark=vpn
/ip firewall address-list add list=via-vpn address=203.0.113.10

Linux firmware does the same thing with a netfilter mark and a rule that steers marked packets into a table you populated:

iptables -t mangle -A PREROUTING -p tcp --dport 27000:27050 -j MARK --set-mark 0x200
ip route add default dev tun0 table 100
ip rule add fwmark 0x200 table 100

The chain is not a style choice. By the time a packet reaches FORWARD the route lookup has already happened, so a MARK or DROP there can only rewrite or discard. The mark has to land in PREROUTING for forwarded traffic and in OUTPUT for traffic the router originates, which is where router DNS, NTP and update checks live. That output path is the usual reason a fully tunnelled setup still answers from the ISP resolver in a leak test.

Device policy needs a match that does not drift. Reserve the address in DHCP, or match an interface for a dedicated segment; an address that later moves to another device silently applies the tunnel to the wrong client. A per-SSID bridge with its own subnet is the more durable version of the same idea, and it gives the IPv6 rules a place to live as well.

Piece three: NAT, DNS and the LAN exception

Traffic leaving through the tunnel needs a source address the far side will answer. Scope the translation to the tunnel interface: out-interface=VPN_NAME on RouterOS, -o tun0 on Linux, a matching outbound NAT entry on pfSense. A masquerade rule without that scoping rewrites traffic you meant to leave alone, and the symptom is the untunnelled device losing its public address.

The LAN exception causes more support threads than the tunnel itself. If the marked table holds only a default route, a printer or NAS destination from the selected device matches that default and disappears into the tunnel. Add the LAN route to the same table, or exclude the local subnet in the selector; both work, and the exclusion is easier to keep correct when you add VLANs later.

DNS takes a separate path. Queries the router forwards are router-originated and follow OUTPUT, not the forwarded mark, so a fully tunnelled laptop can still resolve through the ISP. Point clients at a resolver reachable over the tunnel, or mark the router's own port-53 traffic, then check the resolved address rather than assuming the answer travelled with the web request.

When the rule matches but the app still uses the WAN

Destination lists age badly. A name resolves to an address that is not in the list, so the request takes the WAN while every test you ran against the listed address worked. Refresh the list from the same resolver the client uses, and prefer a source-based selector when you genuinely want all traffic from one device.

IPv6 is the quiet bypass. The device has a second address family with its own default route and its own rule set, and matching only the IPv4 address is half a policy. Apply the same selector to ip6tables or the nftables equivalent, or disable IPv6 on that segment deliberately instead of by accident.

Established connections matter too. A conntrack entry created before the change keeps following the old path until it expires, so a browser that stayed open during the edit looks exactly like a rule that never applied. Open a fresh connection, or flush the state, before you judge the policy.

Verify with two devices and a reboot

Test the device that should be tunnelled and the device that should not, and compare the second hop instead of a speed number. A traceroute from each shows either the tunnel gateway or the ISP gateway at hop two; if both look the same, the mark or the table is not doing what the rule says. Use new connections for the test.

Then reboot. Startup ordering is where a working policy becomes a broken one, because the client has to be up before the unreachable route is installed, and a saved snapshot that restores the old rule set will overwrite your table. Check that every rule is in the saved configuration and that the marked default exists after boot, not only after you typed it.

If something fails, remove the mark first and see whether normal traffic returns, then remove the table. That order tells you whether you are chasing a routing problem or a firewall problem, and it leaves the household with a working printer while you finish the job.

Nsɛm a ɛfa ho