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.

Alex Novak 5 min read
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?#

console
$ 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: NOERROR means the name exists. NXDOMAIN means it doesn’t. SERVFAIL means 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.53 is systemd-resolved; the real upstream is in resolvectl 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?#

bash
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 +short

If 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:

console
$ dig example.com NS +short
ns1.dns-host.example.
ns2.dns-host.example.

Then ask each one directly, without recursion:

bash
dig @ns1.dns-host.example. app.example.com +norecurse
dig @ns2.dns-host.example. app.example.com +norecurse

Look 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:

compare SOA serialsbash
for ns in $(dig example.com NS +short); do
  echo "$ns $(dig @"$ns" example.com SOA +short | awk '{print $3}')"
done

The 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:

bash
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:

bash
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:

console
$ 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#

bash
dig -x 203.0.113.10 +short

Mostly 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#

CommandQuestion it answers
dig namewhat does my resolver think?
dig @1.1.1.1 name +shortwhat does the world think?
dig name NS +shortwho’s authoritative?
dig @ns name +norecursewhat does the source of truth say?
dig +trace nameis the delegation right?
dig @1.1.1.1 name +cdis 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.