MagicDNS Resolves but the LAN Is Unreachable: Check Route Injection

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

TargetSuccess conditionFailure layer
MagicDNS device nameA tailnet node name resolves to a Tailscale IPMagicDNS and client DNS acceptance
Tailscale IPThe node is directly reachableNode online state and grant
LAN IPA subnet route is installed and allowedAdvertisement, approval, acceptance, or access control
Private DNS nameThe nameserver is routed and permits the querySplit 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.

Official references

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.

Writing and correction policy · Report an error