Debugging DNS with dig, step by step
The handful of dig commands I actually use when a hostname doesn't resolve, resolves to the wrong thing, or resolves for some people and not others.
On this page
“It’s always DNS” is a joke because it’s usually true. After a migration last week, a customer-facing hostname resolved correctly for half the team and pointed at the old load balancer for the other half. Ten minutes with dig explained it. Here’s the sequence I go through, roughly in order.
1. What does my resolver say?#
$ dig app.example.com
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 41209
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; ANSWER SECTION:
app.example.com. 2713 IN A 198.51.100.20
;; Query time: 1 msec
;; SERVER: 127.0.0.53#53(127.0.0.53) (UDP)
Things I read every time:
- status:
NOERRORmeans the name exists.NXDOMAINmeans it doesn’t.SERVFAILmeans the resolver couldn’t get an answer at all, which is a very different problem. - The TTL (2713 here) is how many more seconds this cached answer will live. If it’s counting down from a large value, you’re looking at a cache, not at the current state of the zone.
- SERVER tells you who answered.
127.0.0.53is systemd-resolved; the real upstream is inresolvectl status. - Query time of 0–1 ms is almost always a cache hit.
For scripts and quick checks, dig +short app.example.com prints just the answer.
2. What do other resolvers say?#
dig @1.1.1.1 app.example.com +short
dig @8.8.8.8 app.example.com +short
dig @9.9.9.9 app.example.com +shortIf public resolvers disagree with each other or with your local one, it’s almost always caching: the record changed recently and some caches still hold the old value until the TTL runs out. That was exactly our case: the old record had a TTL of 86400, so some resolvers would keep the old address for up to a day.
3. What does the authoritative server say?#
To skip all caches, ask the servers that own the zone. Find them first:
$ dig example.com NS +short
ns1.dns-host.example.
ns2.dns-host.example.
Then ask each one directly, without recursion:
dig @ns1.dns-host.example. app.example.com +norecurse
dig @ns2.dns-host.example. app.example.com +norecurseLook for the aa (authoritative answer) flag in the header. If it’s missing, the server doesn’t consider itself authoritative for that zone, which points to a delegation problem.
If the two nameservers give different answers, check whether they serve the same version of the zone:
for ns in $(dig example.com NS +short); do
echo "$ns $(dig @"$ns" example.com SOA +short | awk '{print $3}')"
doneThe third field of the SOA record is the serial. Different serials mean a secondary hasn’t picked up the latest changes. Zone transfers failing quietly is more common than anyone admits.
4. Follow the delegation from the root#
When the authoritative servers look right but resolvers still get it wrong, trace the whole path:
dig +trace app.example.com+trace starts at the root servers and follows each referral: root, then com., then your zone’s nameservers. It shows which servers the parent zone delegates to.
5. SERVFAIL and DNSSEC#
SERVFAIL from a validating resolver often means DNSSEC validation failed: expired signatures, or a DS record at the parent that doesn’t match the zone’s keys after a provider change. There’s a one-flag test for that:
dig @1.1.1.1 app.example.com # status: SERVFAIL
dig @1.1.1.1 app.example.com +cd # status: NOERROR, with an answer+cd means “checking disabled”: the resolver returns the answer without validating it. If the name resolves with +cd and fails without it, the problem is DNSSEC, not your records. dig +dnssec shows the RRSIG records with their expiry dates, and online tools like DNSViz draw the whole chain of trust if you need a picture.
6. Negative answers are cached too#
If you query a name before it exists, resolvers cache the NXDOMAIN. How long is controlled by the SOA record:
$ dig example.com SOA +short
ns1.dns-host.example. hostmaster.example.com. 2026110301 7200 3600 1209600 300
The last number (300 here) is the negative caching TTL from RFC 2308. Practically: create the record first, then tell people to try it. If someone tested too early, they may have to wait up to that many seconds.
7. Reverse lookups#
dig -x 203.0.113.10 +shortMostly relevant for mail servers, where a missing or mismatched PTR record gets your messages rejected, and for making logs readable. PTR records are managed by whoever owns the IP range, usually your hosting provider, not in your own zone.
The short version#
| Command | Question it answers |
|---|---|
dig name | what does my resolver think? |
dig @1.1.1.1 name +short | what does the world think? |
dig name NS +short | who’s authoritative? |
dig @ns name +norecurse | what does the source of truth say? |
dig +trace name | is the delegation right? |
dig @1.1.1.1 name +cd | is it DNSSEC? |
Six commands, and in my experience they cover the large majority of “DNS is broken” tickets. The rest are usually /etc/hosts, which is worth checking before any of the above. I say this as someone who once spent an hour on a stale hosts entry.