Skip to content

Troubleshoot

Symptoms

SymptomLook atTypical cause
Tailnet ApiReachable=False/Unreachablecondition messageapiUrl wrong, NetworkPolicy blocks the operator
Tailnet ApiReachable=False/UnhealthyHeadscale /health, its databaseHeadscale answers but cannot reach its database (it then returns 401 on every API call)
Tailnet ApiKeyValid=False/RejectedHeadscale apikeys listworking key expired or deleted on the server; Error = rotation failed (bootstrap key expired)
HeadscaleCompatible=False/Incompatiblecondition messagemissing policy/check endpoint (requires ≥ 0.29), or file policy mode
PolicyMode=False/File, PolicyApplied=False/PolicyModeFileHeadscale policy.modeswitch to database mode using Migrating from file mode ; writes are skipped until mode detection changes
Preflight Unknown/ProbeFailedAPI/key conditions and Headscale logsfailed requests or invalid response shape; not proof of file mode
SplitDNS=Unknown/CannotVerifycondition message and Headscale DNS configmanually configure the named split zone; external Headscale has no DNS API
UsersUnique=False/Duplicate, PolicyApplied=False/PolicyRejectedheadscale users listduplicate user names (typically OIDC plus a CLI-created user); destroy the surplus user, the next reconcile clears both
Egress TagKnown=False/Unknownkubectl describe tailnetegresstag missing in tagOwners (file mode)
Egress TagKnown=False/PolicyPendingkubectl describe tailnet → PolicyAppliedpolicy not applied yet / rejected
status.targets[].reason=NotAdvertisedrouter --advertise-routesroute not advertised
… NotApprovedheadscale nodes list-routes, kubectl describe tailnet eventsroute not approved; with operator-owned policy it is re-approved on the next policy reconcile — persists → autoApprovers entry missing or router tag differs
… NoGrantpolicy grantsno grant tag → /32:port (file mode)
TargetsReachable=Falseagent log dial …: context deadline exceededsite firewall, target down
Alias does not resolve in a podkubectl -n tailnet-system get cm tailnet-dns-hosts, resolver logzone not forwarded, ConfigMap not mounted, Service not created yet
Ingress PolicyGranted=False/PolicyDeniedcondition messageallowFrom entry not covered by ingressSourceAllowlist
PolicyRendered=False/PolicyEmptybase ConfigMapbase has no tagOwners or no acls/grants — refused to avoid a lock-out
PolicyApplied=False/ApplyFailedcondition message (read: …; write: …)Headscale still in file mode, empty policy table, API unreachable
Event PolicyDriftOverwrittenkubectl get events -A --field-selector reason=PolicyDriftOverwrittensomeone ran headscale policy set

Diagnosis cheat sheet

QuestionCommandHealthy output
Is the control server reachable?kubectl describe tailnet corpApiReachable=True, ApiKeyValid=True
Does preflight pass?kubectl get tailnet corp, kubectl describe tailnet corpCOMPAT=Compatible, PolicyMode=True/Database; DNS=CannotVerify is a manual split-DNS configuration hint
Why is an egress not ready?kubectl -n tailnet-system get tailnetegress X -o jsonpath='{range .status.targets[*]}{.name} {.present} {.reachable} {.reason}{"\n"}{end}'true true per target, empty reason
Is the route on the router?kubectl -n tailnet-system exec deploy/headscale -- headscale nodes list-routesthe target’s /32 or a covering subnet under the router’s approved routes
Does the agent reach the host?kubectl -n tailnet-system logs deploy/X | grep targetno dial …: context deadline exceeded
What does the agent probe say?wget -qO- http://<agent-pod-ip>:9080/targets from any pod"reachable":true per target
Does an alias resolve?kubectl -n <tenant> exec deploy/app -- getent hosts api.corp.examplethe Service ClusterIP
Is the hosts file current?kubectl -n tailnet-system get cm tailnet-dns-hosts -o jsonpath='{.data.hosts}'one line per alias / ingress name
Resolver errors?kubectl -n <resolver-ns> logs <coredns-pod> | grep -i errornone; i/o timeout = upstream unreachable
What did the policy render?kubectl get tailnet corp -o jsonpath='{.status.policy}', ConfigMap headscale-policy-renderedhashes equal, PolicyApplied=True
Who is on the tailnet?kubectl -n tailnet-system exec deploy/headscale -- headscale nodes listegress/ingress nodes online with their tags

Known CNI behavior: ICMP errors

Kubernetes NetworkPolicy specifies TCP, UDP and SCTP; ICMP handling is up to the CNI, and some implementations drop node-generated ICMP errors (unreachable, fragmentation-needed) to policy-selected pods. A timeout on an agent is therefore not proof of reachability, and path-MTU discovery may not work; Tailnet.spec.proxy.mtu (default 1200) sidesteps the latter.

Source: docs/how-to/troubleshooting.md at v0.18.0 — found a mistake? Tell us.