TailscaleGuide
Isometric subnet routing setup with servers, a central router, network adapter, switch and monitor linked by cyan cables beside a padlock.
Networking

Tailscale Subnet Router: Advertise, Approve, Accept Routes

Advertise and approve LAN routes, set --accept-routes on Linux clients, scope access with grants, and fix the SNAT, MTU and overlapping-subnet traps.

By TailscaleGuide Editorial · ·Updated · 11 min read

A Tailscale subnet router is a tailnet device that advertises a physical LAN’s CIDR so authorized tailnet devices can reach hosts that cannot run Tailscale. Setup takes four steps: enable IP forwarding, run sudo tailscale set --advertise-routes=10.0.20.0/24, approve the route in the admin console, then enable --accept-routes on Linux clients.

That is how a tailnet reaches the hardware that will never run a Tailscale client: the IPMI board, the PoE switch management interface, the NAS that only speaks SMB on the LAN. Tailscale subnet router configuration fails in four predictable places, and almost always in this order: IP forwarding is off on the host, the routes are advertised but never approved, the Linux clients never got --accept-routes, and the policy file is far more permissive than anyone intended. Fix them in that order and the rest is detail.

How to set up a Tailscale subnet router

The examples use a Linux router host at 10.0.20.5 advertising two VLANs: 10.0.20.0/24 (services) and 10.0.40.0/24 (storage). Skipping any step produces the same symptom: traffic goes nowhere.

  1. Install Tailscale on the router host and join it to the tailnet. A brand-new tailnet should start with the Tailscale setup guide.

  2. Enable IP forwarding. Tailscale’s subnet router documentation gives the sysctl drop-in:

    echo 'net.ipv4.ip_forward = 1' | sudo tee -a /etc/sysctl.d/99-tailscale.conf
    echo 'net.ipv6.conf.all.forwarding = 1' | sudo tee -a /etc/sysctl.d/99-tailscale.conf
    sudo sysctl -p /etc/sysctl.d/99-tailscale.conf

    On a firewalld host (Fedora, RHEL, Rocky), the same page notes a known issue that may require firewall-cmd --permanent --add-masquerade.

  3. Advertise the routes as one comma-separated list:

    sudo tailscale set --advertise-routes=10.0.20.0/24,10.0.40.0/24

    tailscale set changes one preference without re-running the whole up flow, which matters when you are editing routes on a box you are reaching over the tunnel.

  4. Approve the routes in the admin console under Machines. Filter with property:subnet to find every advertising device, open Edit route settings, tick the prefixes and save. The quick setup page shows the same flow for macOS, Windows, tvOS and Android routers.

  5. Scope access in the policy file. Approval makes the route exist; grants decide who may use it (see the policy section below).

  6. Accept the routes on Linux clients with sudo tailscale set --accept-routes. Windows, macOS, iOS and Android accept them by default.

  7. Verify from a client: ping the router’s Tailscale address (tailscale ip -4 on the router prints it), then a LAN host such as the NAS at 10.0.40.10.

What tailscale up --accept-routes does

--accept-routes tells a Tailscale client to install the subnet routes other nodes advertise. Per the tailscale up reference, it defaults to on for Windows, iOS, Android and both macOS variants, and off everywhere else. A Linux client reaches the subnet router’s own 100.x address but nothing behind it until you run sudo tailscale set --accept-routes.

tailscale up --accept-routes and tailscale set --accept-routes end in the same state, but up expects every flag restated on each run, while set, per the CLI reference, changes only the flags you name. That makes set the safer choice on a node that already advertises routes or tags. Undo it with sudo tailscale set --accept-routes=false.

Accepted routes do not show up in a plain ip route on Linux. Since v0.99, Tailscale keeps its routes in a separate table, number 52, to prevent routing loops on subnet routers, per IP routes installed by Tailscale. Inspect them with ip route show table 52.

When accept-routes breaks the local LAN

Table 52 is consulted before the main table; the source comment quoted in tailscale/tailscale#6231 reads “VPN routes always beat non-VPN routes.” So a Linux machine that sits on 10.0.20.0/24 and accepts the route for 10.0.20.0/24 pushes its own LAN traffic, including replies to local SSH and SMB sessions, into tailscale0. If ip route get 10.0.20.20 names tailscale0, that is what happened. Fix it locally:

  • Leave --accept-routes off on hosts inside an advertised subnet. They reach that LAN directly anyway.
  • If the host needs other sites’ routes, add a rule ahead of Tailscale’s: sudo ip rule add to 10.0.20.0/24 lookup main priority 5269. It does not survive a reboot, so persist it in your network configuration or a systemd unit.

Approve routes from the policy file

Clicking approve in a browser does not survive a node rebuild. Tag the router and let the policy file approve it, using the autoApprovers syntax:

{
  "tagOwners": {
    "tag:subnet-router": ["autogroup:admin"]
  },
  "autoApprovers": {
    "routes": {
      "10.0.20.0/24": ["tag:subnet-router"],
      "10.0.40.0/24": ["tag:subnet-router"]
    }
  },
  "grants": [
    { "src": ["tag:laptop"], "dst": ["10.0.40.0/24"], "ip": ["tcp:445", "tcp:2049"] },
    { "src": ["autogroup:admin"], "dst": ["10.0.20.0/24"], "ip": ["*:*"] }
  ]
}

Bring the node up with sudo tailscale up --advertise-tags=tag:subnet-router --advertise-routes=10.0.20.0/24,10.0.40.0/24 and the routes approve themselves. Tagging has a second benefit: tagged devices have key expiry disabled by default, which is what the subnet router docs recommend for a server that should not need periodic re-authentication.

Note the split of responsibility, because it is where people get burned. Route approval decides whether the route exists in the tailnet. Grants decide who may use it and on which ports. An approved /24 under a permissive default policy means every node, including a phone with a stale key, reaches every host on that subnet. Anything exposed this way widens what an attacker who lands on one tailnet device can pivot to. More CIDR and host-alias patterns are in Tailscale ACL examples explained. Keep the router host patched against Tailscale’s security bulletins.

Which subnets to advertise

Advertise the smallest prefixes you can live with. 10.0.0.0/8 is legal RFC 1918 space and a terrible advertisement: it hands every tailnet node a route to everything you will ever number. Default routes (0.0.0.0/0, ::/0) belong to an exit node, not a subnet route. IPv6 prefixes go in the same comma-separated list on every platform except Apple TV, which routes IPv4 only.

In the example, the router host sits on an access port in VLAN 20 and the gateway does inter-VLAN routing:

VLANNameCIDRAdvertised to tailnetInter-VLAN policy
20services10.0.20.0/24yesdeny-all inbound, exceptions below
40storage10.0.40.0/24yes20 → 40 on 445/2049 only
50iot10.0.50.0/24nodeny-all, no tailnet exposure

The alternative is trunking 802.1Q subinterfaces into the host and letting it route. That gives per-VLAN control on the host, at the cost of putting a Linux box in the forwarding path for east-west traffic that never needed to leave the switch. On a 1 to 3 AP home network, let the gateway route.

--snat-subnet-routes=false and stateful filtering

--snat-subnet-routes=false stops a Linux subnet router from masquerading tailnet traffic, so LAN hosts see the real 100.x source address instead of the router’s LAN IP. The flag is Linux-only and defaults to true, and once it is off, LAN hosts need a return route for 100.64.0.0/10 via the subnet router.

With SNAT on, the NAS sees connections from 10.0.20.5 rather than from the tailnet address. That hides the real client from your logs and from any per-host ACL on the target. To turn it off:

# on the subnet router
sudo tailscale set --snat-subnet-routes=false
# on a host in 10.0.20.0/24 that does not use 10.0.20.5 as its gateway
sudo ip route add 100.64.0.0/10 via 10.0.20.5

In this layout the NAS sits on VLAN 40 and reaches the router through the gateway, so its return route belongs on the gateway: 100.64.0.0/10, next hop 10.0.20.5. The subnet router docs allow the route on each device, in the VPC, or pushed by DHCP; an ip route add alone does not survive a reboot. They also warn against disabling SNAT on a node that is also an exit node, because upstream devices can drop that traffic. Miss the return route and you get one-way traffic that looks exactly like a firewall drop.

--stateful-filtering is the other Linux-only switch. The CLI reference describes it as dropping inbound packets addressed to another node unless they belong to a tracked outbound connection, and states it defaults to disabled. It has not always: enabling it by default in the 1.66 series broke MagicDNS and other traffic for a lot of people, as documented in tailscale/tailscale#12087. If a subnet router works on one version and drops traffic after an upgrade, check this flag before rewriting your ACLs.

Tailscale site-to-site with two subnet routers

Tailscale site-to-site networking links two LANs by running a Linux subnet router in each, each advertising its own subnet with --snat-subnet-routes=false and --accept-routes. Hosts at each site then route the other site’s subnet and 100.64.0.0/10 via their local router. Both routers must run Linux, and the two subnets cannot use identical CIDRs.

Following the site-to-site guide, with site A as 10.0.20.0/24 (router 10.0.20.5) and site B as 172.16.100.0/24 (router 172.16.100.2):

  1. Enable forwarding on both routers as above, then bring each up with all three flags:

    # site A
    sudo tailscale up --advertise-routes=10.0.20.0/24 --snat-subnet-routes=false --accept-routes
    # site B
    sudo tailscale up --advertise-routes=172.16.100.0/24 --snat-subnet-routes=false --accept-routes
  2. Add the MSS clamp from the MTU section below on both routers.

  3. Approve both routes, or let autoApprovers do it, and disable key expiry on both routers.

  4. Grant each subnet access to the other, one rule per direction:

    "grants": [
      { "src": ["10.0.20.0/24"], "dst": ["172.16.100.0/24"], "ip": ["*:*"] },
      { "src": ["172.16.100.0/24"], "dst": ["10.0.20.0/24"], "ip": ["*:*"] }
    ]
  5. On hosts that do not use the subnet router as their default gateway, add static routes: at site A, 172.16.100.0/24 and 100.64.0.0/10 via 10.0.20.5; at site B, 10.0.20.0/24 and 100.64.0.0/10 via 172.16.100.2. In a cloud VPC, put the same routes in the provider’s route table.

The guide uses tailscale up because a fresh router should state its whole configuration in one line. On a router that is already running, tailscale set with the two new flags avoids the restate-every-flag warning.

A pfSense or OPNsense firewall looks like the natural Tailscale gateway for a site. Tailscale runs on OPNsense through the FreeBSD port or the os-tailscale plugin, which the OPNsense page lists as community-supported, and it can advertise routes like any subnet router. The no-SNAT recipe above still needs a Linux box, because --snat-subnet-routes is Linux-only.

Overlapping subnets and a second router

Two sites both numbered 192.168.1.0/24 cannot be advertised as-is. That is what 4via6 is for: it maps each overlapping IPv4 subnet into a unique IPv6 prefix keyed by a site ID from 0 to 65535.

tailscale debug via 7 10.1.1.0/24
# fd7a:115c:a1e0:b1a:0:7:a01:100/120
tailscale set --advertise-routes=fd7a:115c:a1e0:b1a:0:7:a01:100/120

With MagicDNS on, hosts answer to names of the form 10-1-1-16-via-7.

For redundancy, two or more routers can advertise the same prefix; the high availability guide puts no cap on how many. The default is active-passive failover on every plan: the router that joined the tailnet first carries the traffic, and route selection puts failover at about 15 seconds after a tailscale down on the primary, longer for other kinds of outage. The routers must advertise identical prefixes to fail over to each other.

Where prefixes differ, Tailscale picks the most specific route by longest prefix match. Two documented caveats: it does not fall back to a less specific route when the more specific one goes offline, and turning on --accept-routes for HA pairs sharing routes in the same region makes the standby accept its own advertised routes back from the primary.

MTU, MSS clamping, throughput

Tailscale runs a 1280-byte MTU, which the TCP troubleshooting page states outright. If ICMP is filtered on the path, 1500-byte LAN packets hit a PMTU black hole: the handshake completes, then full-size segments vanish. The site-to-site guide clamps it on each subnet router:

iptables -t mangle -A FORWARD -o tailscale0 -p tcp -m tcp \
  --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu

On Tailscale 1.54+ with kernel 6.2+, the performance guide recommends UDP GRO forwarding on the physical NIC of any subnet router or exit node:

NETDEV=$(ip -o route get 8.8.8.8 | cut -f 5 -d " ")
sudo ethtool -K $NETDEV rx-udp-gro-forwarding on rx-gro-list off

If throughput is still poor after that, the connection is probably relayed rather than direct. That is a different problem, covered in Tailscale throughput fixes.

How to check a subnet router is working

Run these from a client, not from the router host, which can reach the LAN regardless.

tailscale status | grep -i 'offers\|relay'
ip route show table 52
ip route get 10.0.40.10
tailscale ping --until-direct 10.0.20.5
ping -c 4 -M do -s 1252 10.0.40.10
mtr -rwzbc 50 10.0.40.10
iperf3 -c 10.0.40.10 -t 30 -P 4
dig +short nas.lan @10.0.20.53

ip route show table 52 and ip route get prove the client installed the route at all, and that it leaves through tailscale0. The -M do -s 1252 ping is the MTU check: 1252 bytes of payload plus 28 of headers is exactly 1280, so it passes while -s 1253 fails cleanly rather than hanging. tailscale ping --until-direct says whether the path is direct or a DERP relay, before you blame the subnet router for latency. dig against the internal resolver confirms split DNS, which is set on the admin console DNS page and is a separate step from route approval.

Nothing here stays correct on its own. Re-run the checks after a Tailscale upgrade, after a VLAN renumber, and after anyone touches the policy file.

Subnet router vs exit node: which Tailscale gateway to use

A subnet router is a gateway to specific private prefixes; an exit node is a gateway for all internet-bound traffic.

OptionWhat it carriesClient-side switchTypical use
Tailscale on each deviceTraffic to that device’s own 100.x addressNoneAnything that can run the client
Subnet routerApproved private prefixes, such as 10.0.40.0/24--accept-routes on Linux, default elsewherePrinters, IPMI, NAS, cloud VPCs, managed databases
Site-to-site pairBoth LANs’ prefixes, SNAT off--accept-routes on both Linux routersBranch offices, cloud to on-premises
Exit node0.0.0.0/0 and ::/0--exit-node=<name>Egress through a home or office IP

If the subnet router is also carrying general internet traffic, that is an exit node and a different set of flags, covered in Tailscale exit node setup.

FAQ

is a tailscale subnet router free?

Yes. Tailscale’s subnet router documentation, checked October 2026, says subnet routers are available on all plans, including the free one, and that devices reached through a subnet router do not count toward the plan’s device limit.

can a windows pc or mac be a tailscale subnet router?

Yes, with limits. On macOS, Tailscale enables IP forwarding automatically, though the App Store and Standalone apps need CLI integration first. On Windows, enable forwarding, then run tailscale set --advertise-routes=192.168.1.0/24 in PowerShell without sudo. --snat-subnet-routes and site-to-site stay Linux-only.

how do i remove a subnet route in tailscale?

Re-run the advertise command with only the prefixes you want to keep, such as sudo tailscale set --advertise-routes=10.0.20.0/24, or pass an empty value, --advertise-routes=, to stop advertising entirely. Then confirm the Machines page no longer lists the prefix, and drop it from autoApprovers and any grants so a rebuilt node cannot quietly bring it back.

does the subnet router itself need —accept-routes?

Not for a single router, which reaches its own LAN directly. Turn --accept-routes on only when the router must reach prefixes advertised by other routers, as in a site-to-site pair. Leave it off on high-availability routers sharing the same prefixes, or the standby sends its own traffic through the primary instead of out its local interface.

Sources

  1. Subnet routers
  2. tailscale up command
  3. Syntax reference for the tailnet policy file
  4. Site-to-site networking
  5. 4via6 subnet routers
  6. Performance best practices
  7. Troubleshoot TCP connection issues between two devices
  8. tailscale/tailscale issue 12087: v1.66.x breaks MagicDNS without --stateful-filtering=false
  9. RFC 1918: Address Allocation for Private Internets
  10. Configure a subnet router
  11. Tailscale CLI
  12. IP routes installed by Tailscale
  13. tailscale/tailscale issue 6231: accepted subnet routes should not have a higher priority than main table
  14. LibreELEC wiki: Tailscale
  15. Route selection
  16. Set up high availability
  17. Using OPNsense with Tailscale
#tailscale #subnet-router #wireguard #networking#vlan

Related