Approval in the admin console looks like the final step, and it is not. The application times out first, and only afterwards do you find the router online and the route approved. Approval is a distribution step, not a permission and not a forwarding guarantee. Four stages have to line up before a packet reaches a host behind a subnet router, and each one can succeed while the next fails. Work them in order and stop at the first stage that does not hold.

Four stages, one failure

The router advertises the prefix, the tailnet approves that advertisement, the client accepts the route it received, and the access policy allows the specific connection. Those are four separate decisions made by three different components. An approved route therefore says only that the network is now part of the tailnet's routing table.

The fourth stage is the one most often skipped, because the first three produce visible state. A grant that covers the router's own tailnet address does not automatically cover a private subnet behind it. Those are different destinations, and policy is evaluated per destination.

On Linux the third stage also has a default that surprises people: accepting subnet routes is opt-in. Several GUI clients accept advertised routes by default, so a mixed fleet can show one failing machine while the rest work.

Confirm the preference on the failing machine rather than assuming a platform. The route was never the missing piece on the working machines, so let them prove that the tailnet side is healthy. Tailscale's subnet router documentation covers approval, client route acceptance and the policy stage.

Check the route where the client runs

Start on the client that fails, not on the router. Confirm the route is present and that the client preference allows accepting routes at all.

tailscale status
tailscale debug prefs | grep -i route
ip route get 10.20.0.42

The first command shows the peer list and whether the subnet router is reachable, the second prints the client's preferences including the route-acceptance flag, and the third asks the kernel which interface it would use for a real host on the subnet. If that last command names the physical interface instead of the Tailscale interface, the route never made it into the table. A missing route is a client-side problem, not an approval problem. Fix it before you look at policy.

To accept routes on Linux, reconnect with the flag rather than assuming it is set: tailscale up --accept-routes. A client that was joined with different flags keeps them until changed, so check the current preference before adding another one. Re-running the first command after the change should show the route in the table.

Prove forwarding and the return path

Approval does not configure the router to forward packets, and it does not arrange the reply. Both are host-level concerns on the router's LAN side. Test them on the router, not on the client.

sysctl net.ipv4.ip_forward
iptables -t nat -L POSTROUTING -n -v
tcpdump -ni any host 10.20.0.42 and port 443

The first command must report 1, or the kernel will drop the forwarded packet. The second shows whether subnet-route masquerading is rewriting the source, which is the default behaviour and usually what you want. The third shows whether the request arrives at the destination and whether anything comes back; a request with no reply is a return-path problem, not an approval problem. Read the three results together before changing any of them.

If masquerading has been disabled, the destination network needs a route back to the tailnet's addresses, and every hop in between needs to know it. Turning masquerading back on is the quicker fix for a single service, at the cost of hiding the original client address from the destination. That trade matters when the destination logs source addresses for auditing, so decide it deliberately instead of flipping the flag during an incident. Document which choice you made, because the next person will see only one of the two behaviours.

Read the access rule for the resource

With the route present and forwarding proven, the remaining suspect is policy. Read the grant that is supposed to allow the connection and check its destination, not just its source. The client's own identity has to match the policy selector as well, so capture it from the same machine that fails.

tailscale status --json | head -40
tailscale ping <router-name-or-tailnet-ip>
ping -c 3 10.20.0.42

The status output identifies the client's own addresses and the peers it can see, which is what a policy source selector has to match. tailscale ping proves the tailnet path to the subnet router itself; it resolves peer names and tailnet addresses, not hosts inside an advertised subnet. The third command sends a normal ICMP echo through the subnet route, so its result describes the destination rather than the router. A tailnet-level denial usually comes back as a refusal, while a packet that leaves and never returns times out.

A failing name is a different question from a failing address; resolve the host directly before blaming the route. Write the grant against the actual resource requirement rather than the whole subnet. Opening an entire /24 so that one application works is how a narrow request becomes a broad permission that nobody later dares to remove.

If the goal was internet egress rather than a private subnet, the correct component is an exit node, not a subnet router, and the policy and routing differ accordingly. Check that decision before widening a grant. An exit node changes the default route; a subnet router only advertises a prefix.

Winndannde jowitiiɗe heen