A resolved name does not prove a working LAN route
MagicDNS assigns readable names to tailnet devices. Resolving a name to a Tailscale IP and reaching that node does not prove that a route to the private LAN behind it was injected. Conversely, a subnet route can work while a private LAN hostname fails because the client cannot reach the private DNS server.
The key is to avoid changing DNS and routes together. First record which address the name resolves to and whether it identifies a Tailscale node or a LAN host. Then verify route-injection conditions separately from packet authorization.
Classify the symptom by address type
| Target | Success condition | Failure layer |
|---|---|---|
| MagicDNS device name | A tailnet node name resolves to a Tailscale IP | MagicDNS and client DNS acceptance |
| Tailscale IP | The node is directly reachable | Node online state and grant |
| LAN IP | A subnet route is installed and allowed | Advertisement, approval, acceptance, or access control |
| Private DNS name | The nameserver is routed and permits the query | Split DNS and the DNS-server route |
tailscale status
tailscale status --json > tailscale-status.json
tailscale ip -4
getent hosts <MAGICDNS_NAME>
tailscale ping <SUBNET_ROUTER>
ip route get <LAN_IP>
On a non-Linux client, replace the final command with that operating system’s route inspection tool. tailscale-status.json contains device and tailnet information, so redact identifiers before sharing it publicly.
Read and record the original values before changing anything
This is a read-only Linux client query. RouteAll represents route acceptance and CorpDNS represents DNS acceptance. Stop if either is unknown or the command fails. Record the returned true/false values in the ORIGINAL_ACCEPT_ROUTES and ORIGINAL_ACCEPT_DNS shell variables respectively, then run only the necessary test in the same shell. Keep a separate management connection available and do not change DNS and routes together.
tailscale version
tailscale debug prefs | python3 -c 'import json,sys; p=json.load(sys.stdin); keys=("RouteAll","CorpDNS"); ok=all(type(p.get(k)) is bool for k in keys); print(json.dumps({k:p.get(k,"unknown") for k in keys})); sys.exit(0 if ok else 1)'
Verify route-injection conditions in order
Tailscale documents that a subnet router must advertise the route, an administrator must approve it, the control plane must distribute it, and the client must accept it. Windows, macOS, Android, iOS, and tvOS accept routes by default, while Linux requires an explicit setting.
sudo tailscale set --accept-routes=true
tailscale status --json > tailscale-status-after.json
ip route get <LAN_IP>
A visible route and permitted traffic are different conditions. Grants or ACLs authorize packets but do not create routes. A route can also be installed while access control denies the destination and port.
Do not confuse MagicDNS with private DNS
MagicDNS registers tailnet device names; it is not a store for arbitrary LAN records. A NAS or printer without a Tailscale client needs a private nameserver that owns the record and a split-DNS configuration. When that nameserver is on the LAN, the client also needs a subnet route to its address.
Check whether the client accepts Tailscale DNS before changing it. Disabling DNS acceptance is an isolation test for name resolution, not a route repair.
sudo tailscale set --accept-dns=false
getent hosts <MAGICDNS_NAME>
# Restore the recorded values using the guarded rollback below.
Interpret the combined results
- MagicDNS and the Tailscale IP work, but the LAN IP fails: investigate subnet routing and access control, not DNS.
- The LAN IP works, but a private name fails: routing works. Inspect the split-DNS nameserver, search domain, and client DNS acceptance.
- The router’s Tailscale IP fails: restore the router node’s online state and basic tailnet permission before looking behind it.
- Only Linux clients fail to reach the LAN: compare route acceptance and the real route table. Do not assume every operating system uses the same default.
- The route exists, but one port fails: separate the grant, destination listen address, and LAN firewall.
Rollback and verification
Do not hard-code false/true as rollback defaults. Restore the two values recorded before the test, then query them again. Run the following in a Bash script; it exits if the original values are missing. Changes to the router advertisement affect other clients and are separate from this client-only test.
# Set these to the booleans recorded BEFORE the test in this shell.
# Empty/unknown values stop the command; do not guess defaults.
case "${ORIGINAL_ACCEPT_ROUTES-}:${ORIGINAL_ACCEPT_DNS-}" in
true:true|true:false|false:true|false:false) ;;
*) printf '%s\n' 'STOP: original preferences are required' >&2; exit 1 ;;
esac
sudo tailscale set --accept-routes="$ORIGINAL_ACCEPT_ROUTES" --accept-dns="$ORIGINAL_ACCEPT_DNS"
tailscale version
tailscale debug prefs | python3 -c 'import json,sys; p=json.load(sys.stdin); keys=("RouteAll","CorpDNS"); ok=all(type(p.get(k)) is bool for k in keys); print(json.dumps({k:p.get(k,"unknown") for k in keys})); sys.exit(0 if ok else 1)'
Final verification should preserve three results: name resolution, route selection, and connection to the real service port. A successful ping alone is not an application recovery.
Related guides
Official references
- Tailscale MagicDNS – tailnet device names and client DNS acceptance
- DNS in Tailscale – global DNS, split DNS, private nameservers, and exit nodes
- Tailscale subnet routers – route advertisement, approval, and Linux client acceptance
- Tailscale route injection – independent route and access-control conditions
- Tailscale client preferences – accept-dns and accept-routes settings
Earlier verification notes preserved from this article
Verification level: official documentation checked
The boundaries among MagicDNS, split DNS, subnet routes, and access control were checked against Tailscale documentation. No specific LAN route or firewall was reproduced, so the actual packet-drop location remains an environment-specific verification.