dns-troubleshooting
Skill0 starsChanged 2 months agoApache-2.0View on GitHub
Diagnose DNS failures - NXDOMAIN, SERVFAIL, intermittent resolution errors, stale records after a change, CoreDNS problems in Kubernetes, ndots and search domain surprises, negative caching, and TTL-driven propagation delays. Use whenever a name does not resolve, resolves to the wrong address, or resolution is slow or intermittent.
In this repository26 other files it gives its agents.
- AGENTS.mdAGENTS.md
- aws-production-debuggingSkill
- github-actions-debuggingSkill
- postgresql-troubleshootingSkill
- container-image-reviewSkill
- docker-debuggingSkill
- incident-triageSkill
- postmortem-analysisSkill
- production-debuggingSkill
- kubernetes-networkingSkill
- kubernetes-production-readinessSkill
- kubernetes-resource-debuggingSkill
- kubernetes-troubleshootingSkill
- linux-network-debuggingSkill
- linux-performanceSkill
- linux-troubleshootingSkill
- http-debuggingSkill
- tls-debuggingSkill
- observability-debuggingSkill
- prometheus-troubleshootingSkill
- slo-designSkill
- secrets-management-reviewSkill
- technology-jobSkill
- terraform-reviewSkill
- terraform-safe-changesSkill
- terraform-troubleshootingSkill
The file
We split this skill into the rules it gives an agent
4 rules. About 0 are common, 3 are distinctive, and 1 is very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈3
Distinctive · estimate
This author's own choices. Worth a look.
1
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 242
Keep the previous ConfigMap to hand, and change one directive at a time.
- Distinctive: line 277
Verify from three positions, because each has its own cache:
- Distinctive: line 296
Bad: Create the record, then test it repeatedly until it works.
- Very specific: line 300
Bad: Add an /etc/hosts entry to work around it.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
DNS Troubleshooting
Resolve the name yourself, at each layer, and find the first one that gives a different answer.
Purpose
Use this when:
no such host,NXDOMAIN,SERVFAIL, orTemporary failure in name resolution.- A name resolves to an old address after a DNS change.
- Resolution is intermittent or slow.
- A Kubernetes pod cannot resolve a Service name, or resolution takes hundreds of milliseconds.
Do not use this when:
- The name resolves and the connection fails — that is TCP; use
linux-network-debuggingorkubernetes-networking. - The failure is a TLS certificate name mismatch — use
tls-debugging.
What this skill assumes: dig or drill. nslookup and ping are poor DNS tools — they hide
the response code, which is the most diagnostic field in the answer.
Operating Procedure
- Get the exact error.
NXDOMAIN,SERVFAILand a timeout mean three different things. - Resolve from the failing context, not from your laptop. A container, a host and a laptop have different resolvers, different search domains, and different caches.
- Walk the chain: local cache → configured resolver → authoritative server. Find the first layer that disagrees.
- Check the record at the authority. That is ground truth; everything else is a cache.
- Account for TTL. Most "DNS is broken after a change" reports are a cache that has not expired.
- Fix at the layer that is wrong, and verify from the original failing context.
Which resolver answered is Observed only if you saw it in the dig output. A record you
believe is correct because you set it is an Assumption until you query the authority.
Investigation
Step 1 - resolve, with the response code visible
dig +noall +comments +answer <name>
dig <name> A
dig <name> AAAAThe response code in the ;; ->>HEADER<<- line is the whole diagnosis:
| Code | Meaning | Implication |
NOERROR with an answer | Resolved | DNS is working; the problem is elsewhere |
NOERROR with no answer | The name exists, but not for this record type | Often an A query against an AAAA-only name, or vice versa |
NXDOMAIN | The name definitively does not exist | Typo, wrong search domain, or the record was never created |
SERVFAIL | The resolver failed | Upstream unreachable, DNSSEC validation failure, or an authority timing out |
REFUSED | The server declined | Access control; you are querying a resolver that does not serve you |
| Timeout | No response | The resolver is unreachable — a network problem, not a DNS problem |
SERVFAIL versus NXDOMAIN is the key split: NXDOMAIN is a definitive answer that the name does
not exist, SERVFAIL means the resolver could not determine anything. Treating a SERVFAIL as a
missing record sends you to create a record that already exists.
Step 2 - which resolver, and what is configured
cat /etc/resolv.conf
dig @<resolver-ip> <name>
resolvectl status 2>/dev/null | head -30Query each configured resolver directly. If one answers correctly and another does not, you have found the fault without going further — and an intermittent failure with two resolvers configured is almost always one of them being wrong.
/etc/resolv.conf also holds the two settings that cause the most confusion:
search default.svc.cluster.local svc.cluster.local cluster.local
options ndots:5ndots:5 means any name with fewer than 5 dots is first tried with each search domain appended.
So api.example.com (2 dots) is queried as api.example.com.default.svc.cluster.local, then
...svc.cluster.local, then ...cluster.local, and only then as itself. That is four queries for
every external lookup — the standard explanation for slow DNS in Kubernetes and for inflated
CoreDNS load.
A trailing dot makes a name fully qualified and skips the search list entirely:
api.example.com. resolves in one query.
Step 3 - the chain from the authority down
dig +trace <name>
dig SOA <domain>
dig @<authoritative-ns> <name>
dig @8.8.8.8 <name>+trace walks from the root servers down, showing which server delegates to which. It bypasses
your resolver's cache and shows where the chain breaks.
Then compare the authority's answer against your resolver's:
| Authority | Your resolver | Meaning |
| Correct | Correct | DNS is fine; look elsewhere |
| Correct | Old value | Cache; wait out the TTL or flush |
| Correct | NXDOMAIN | Negative caching — see below |
| Wrong | Wrong | The record itself is wrong |
| NXDOMAIN | Anything | The record does not exist at the authority |
Step 4 - TTL and negative caching
dig <name> | grep -A2 'ANSWER SECTION'
dig +nocmd +noall +answer <name>
watch -n5 'dig +noall +answer <name>'The TTL is the second-to-last field in the answer and counts down as the cache ages. A TTL of 300 means up to five more minutes of the old value.
Negative caching is the part that surprises people. When a name does not exist, the NXDOMAIN is
cached too — for the duration in the SOA minimum field, not the record's TTL:
dig SOA <domain> | grep -A1 'ANSWER SECTION'The last number in the SOA record is the negative TTL. If it is 3600 and someone queried the name
before it was created, that NXDOMAIN is cached for an hour. Querying a record before you create
it is a genuinely common and very confusing self-inflicted outage.
Lower TTLs before a planned change, not during it — lowering a TTL is itself subject to the old TTL.
Step 5 - Kubernetes specifics
kubectl exec -it <pod> -- cat /etc/resolv.conf
kubectl exec -it <pod> -- nslookup kubernetes.default
kubectl get pods -n kube-system -l k8s-app=kube-dns
kubectl logs -n kube-system -l k8s-app=kube-dns --tail=50
kubectl top pods -n kube-system -l k8s-app=kube-dnsCommon causes specific to clusters:
| Symptom | Cause |
| Everything fails to resolve in one namespace | A default-deny NetworkPolicy with no UDP/TCP 53 egress rule |
| Intermittent timeouts, ~5s latency spikes | The classic conntrack race on parallel A/AAAA UDP lookups; mitigate with single-request-reopen or node-local DNS cache |
| CoreDNS at its CPU limit | ndots:5 amplification, or a client retry loop |
| External names fail, internal work | CoreDNS upstream forwarding is broken |
| Slow external resolution | Search-domain amplification; use fully qualified names with a trailing dot |
CoreDNS being throttled or OOMKilled is a cluster-wide outage that presents as every application failing simultaneously with unrelated-looking errors. Check its resources early when many services fail at once.
Decision Tree
Name does not resolve
├── Timeout (no response at all)
│ ├── Resolver unreachable → network, not DNS → linux-network-debugging
│ └── In Kubernetes → NetworkPolicy blocking port 53, or CoreDNS down
├── SERVFAIL
│ ├── dig +trace fails at a delegation → broken NS records
│ ├── DNSSEC validation failure → check with +cd (checking disabled)
│ └── Upstream authority timing out → the authoritative server
├── NXDOMAIN
│ ├── Authority also says NXDOMAIN → the record does not exist
│ ├── Authority has it, resolver does not → negative cache; check SOA minimum
│ └── Wrong name assembled → search domains and ndots
├── NOERROR but no answer section
│ └── Wrong record type (A vs AAAA vs CNAME)
├── Resolves to the wrong address
│ ├── Authority is correct → cache; wait out the TTL
│ └── Authority is wrong → fix the record
└── Slow or intermittent
├── ndots amplification → use FQDNs with a trailing dot
├── One of several resolvers is bad → query each directly
└── 5s spikes in Kubernetes → the UDP conntrack raceCommon Causes
| Cause | Signal | Fix | Risk |
| Negative cache after creating a record | Authority correct, resolver NXDOMAIN | Wait out the SOA minimum | read-only |
| TTL not lowered before a migration | Old address served after the cutover | Lower TTL days in advance | mutating |
ndots:5 amplification | 3-4 extra queries per external name | Trailing dot, or ndots:1 in dnsConfig | mutating |
| NetworkPolicy blocks DNS | Every name fails in one namespace | Allow UDP+TCP 53 egress to kube-dns | mutating |
| CoreDNS saturated | High latency, throttling, restarts | Scale CoreDNS; add a node-local cache | mutating |
| Split-horizon confusion | Different answers internally and externally | Query both views explicitly | read-only |
| CNAME at the zone apex | SERVFAIL or a rejected record | Use ALIAS/ANAME, per your provider | mutating |
Stale /etc/hosts entry | One host resolves differently from all others | Remove the entry | mutating |
Safety
Read-only
dig, drill, host, resolvectl status, reading /etc/resolv.conf, and reading CoreDNS
configuration and logs. DNS queries are read operations; dig +trace generates a few more of them
than usual and is still harmless.
Potentially disruptive
- Changing a DNS record — takes effect for new lookups and then propagates for the length of the old TTL. This is one of the few changes that is not instantly reversible: reverting is itself subject to the TTL you just served. Lower the TTL well in advance of any planned change.
- Flushing a resolver cache (
resolvectl flush-caches, restarting a caching resolver) — causes a burst of upstream queries. Safe on one host, potentially a thundering herd on a fleet. - Restarting CoreDNS — every pod in the cluster loses resolution for the restart window. Kubernetes runs multiple replicas for this reason; verify more than one is ready before restarting any of them.
- Editing the CoreDNS ConfigMap — cluster-wide. A syntax error breaks DNS for everything. Keep the previous ConfigMap to hand, and change one directive at a time.
- Changing
ndotsordnsConfigon a workload — requires a pod restart and can change which names resolve at all if code relies on search-domain behaviour.
Destructive
- Deleting a DNS record or a zone — removes name resolution for everything that depends on it, and the negative caching means the outage outlives the mistake by the SOA minimum. Required first: confirm nothing resolves the name (query logs, not assumption), export the zone, and get explicit confirmation. Deleting a zone is effectively irreversible within any useful timeframe.
- Deleting the CoreDNS Deployment or its ConfigMap — a full cluster outage: every pod loses resolution and most applications fail within seconds. Required first: a saved copy of the ConfigMap and a tested restore path.
DNS failures look total and tempt a "delete and recreate" response. That instinct is wrong here specifically because of caching: recreating a record does not undo the negative cache that the deletion populated.
Verification
Before: api.internal resolves to 10.0.4.12 (decommissioned); 100% connection timeouts
After: api.internal resolves to 10.0.9.30 from all 3 resolvers and from inside the pod;
0 timeouts over 200 requestsdig +short <name>
for r in $(awk '/^nameserver/{print $2}' /etc/resolv.conf); do
echo "$r -> $(dig +short @"$r" <name>)"
done
kubectl exec -it <pod> -- getent hosts <name>Verify from three positions, because each has its own cache:
- The authority — the record is right at the source.
- Each configured resolver — no stale cache, and all of them agree.
- The original failing context — the pod, host or container that reported the problem.
Verifying only from your laptop proves nothing about the pod, and is the most common false confirmation in DNS work.
Anti-Patterns
Bad: ping <name> to test DNS.
Better: ping hides the response code and may use a different resolution path (nsswitch,
/etc/hosts, mDNS). dig shows you NXDOMAIN versus SERVFAIL, which is the entire diagnosis.
Bad: Change the record and wait for it to propagate. Better: DNS does not propagate; caches expire. The wait is exactly the old TTL, and it started when each resolver last queried. Lower the TTL before the change.
Bad: Create the record, then test it repeatedly until it works. Better: Querying it before it exists populates the negative cache for the SOA minimum. Check the authority directly instead of hammering the resolver.
Bad: Add an /etc/hosts entry to work around it.
Better: That fixes one host and creates a permanent, invisible discrepancy that outlives
everyone's memory of why it is there. It is a legitimate emergency mitigation — with a ticket to
remove it.
Bad: Restart CoreDNS when DNS misbehaves. Better: That is a cluster-wide outage window. Check whether CoreDNS is actually at fault first: its own logs, CPU, and whether the failure is limited to one namespace.
Bad: Set ndots:1 everywhere to make DNS faster.
Better: It does reduce query amplification, but it breaks any code relying on short-name
resolution of in-cluster Services. Use fully qualified names with a trailing dot instead.
Composition
kubernetes-networking— Services, NetworkPolicy, and the rest of the cluster network path.linux-network-debugging— when the resolver is unreachable rather than wrong.http-debugging— once names resolve and the problem is at the protocol layer.tls-debugging— certificate name mismatches, which look like DNS problems and are not.
References
- RFC 2308: Negative Caching of DNS Queries — the source of SOA-minimum negative caching behaviour.
- Kubernetes: DNS for Services and Pods
— search domains,
ndots, anddnsConfig.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

