DEV Community

Cover image for How to Read dig Output When DNS Looks Wrong
Pro Gineous
Pro Gineous

Posted on Originally published at blog.progineous.com

How to Read dig Output When DNS Looks Wrong

You moved a site to a new server an hour ago and updated the A record. Your phone loads the new site, your laptop still shows the old one, and a teammate gets "server not found." Three devices, three different answers, and the DNS panel insists everything is correct.

This is where the dig command earns its place. A browser hides DNS behind layers of caching; dig asks one specific server one specific question and prints exactly what came back. Once you can read that output, "DNS is broken" turns into a short list of facts: who answered, what they said, and how long they will keep saying it.

dig ships with macOS and most Linux distributions (on Debian and Ubuntu, install dnsutils). The examples use example.com; in our scenario the old server is 203.0.113.10 and the new one is 203.0.113.20.

Start with one plain query

dig example.com
Enter fullscreen mode Exit fullscreen mode
; <<>> DiG 9.18.30-0ubuntu0.24.04.2-Ubuntu <<>> example.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 48211
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 65494
;; QUESTION SECTION:
;example.com.                   IN      A

;; ANSWER SECTION:
example.com.            2917    IN      A       203.0.113.10

;; Query time: 3 msec
;; SERVER: 127.0.0.53#53(127.0.0.53) (UDP)
;; WHEN: Sat Oct 17 10:42:18 EEST 2026
;; MSG SIZE  rcvd: 56
Enter fullscreen mode Exit fullscreen mode

The screenshot adds a second query for a name that doesn't exist, so you can see NXDOMAIN next to a normal answer.

Terminal screenshot of the dig command: dig example.com returning status NOERROR with an A record of 203.0.113.10 and 2917 seconds of TTL left, followed by dig shop.example.com returning NXDOMAIN with the SOA record in the AUTHORITY section

It looks noisy, but it's only a few blocks.

The header: status and flags

The ->>HEADER<<- line holds the most important word in the output: status. Check it before looking at any IP. NOERROR means the server answered without a problem, not that the answer is the one you wanted.

The flags line tells you what kind of server you were talking to:

  • qr: this is a response. Always there.
  • rd: you asked for recursion. dig does this by default.
  • ra: the server offers recursion, so you're talking to a resolver, not the source of the record.
  • aa: authoritative answer. The server holds the zone itself. You only see this when you query the domain's own nameservers.

QUESTION and ANSWER

QUESTION echoes what you asked: name, class IN, type A. A typo or a forgotten MX shows up here.

ANSWER holds the records in five columns: name, TTL, class, type, value. Here the resolver says example.com points to 203.0.113.10, the old server.

TTL is a countdown

The second column is the TTL in seconds. On a resolver it's the time left before that cached copy expires, not the number you typed into your DNS panel. Run the query twice and you'll watch it drop. Above, 2917 seconds remain, so this resolver will hand out the old IP for roughly another 48 minutes, whatever you change in the meantime. On the authoritative server, the TTL is always the full configured value.

AUTHORITY, ADDITIONAL and the footer

AUTHORITY mostly shows up when there's no answer. On an NXDOMAIN or an empty response you'll find the zone's SOA record there, and its last number caps how long the "doesn't exist" result gets cached. When you query a TLD server, AUTHORITY is where the delegation (the NS records) lives. ADDITIONAL carries helpers such as nameserver IPs.

In the footer, the SERVER line matters. 127.0.0.53 is systemd-resolved on your own machine, which means you're looking at your local cache, not at the internet.

What the status codes are telling you

Status What it means Where to look next
NOERROR with answers The name exists and has records of that type Check the value and the TTL
NOERROR, ANSWER: 0 The name exists, but not with that record type (often called NODATA) Wrong type, or the record was never created
NXDOMAIN The name doesn't exist in the zone that answered A typo, a missing record, or a resolver still reading the old zone
SERVFAIL The resolver couldn't get a usable answer Broken delegation, unreachable nameservers, or a DNSSEC failure
REFUSED The server won't answer this query You asked a server that isn't authoritative for the zone and won't recurse for you

On SERVFAIL, retry with +cd (checking disabled). If an answer appears, DNSSEC validation is failing, often because a domain moved to new nameservers while an old DS record still sits at the registrar. And REFUSED from a nameserver you expected to be authoritative usually means the zone was never created, or never activated, on that provider.

Ask a specific resolver, then ask the source

By default dig uses your system's resolver. Point it elsewhere with @:

dig example.com @1.1.1.1
dig example.com @8.8.8.8
Enter fullscreen mode Exit fullscreen mode

Public resolvers are still caches. To see what the zone really says, find its authoritative nameservers and query one directly:

dig +short NS example.com
dig example.com @ns1.example.net +norecurse
Enter fullscreen mode Exit fullscreen mode
; <<>> DiG 9.18.30-0ubuntu0.24.04.2-Ubuntu <<>> example.com @ns1.example.net +norecurse
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 61027
;; flags: qr aa; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;example.com.                   IN      A

;; ANSWER SECTION:
example.com.            3600    IN      A       203.0.113.10
Enter fullscreen mode Exit fullscreen mode

+short strips everything except the values. In the authoritative reply, look for the aa flag and the full TTL. If the authoritative server already returns the new IP and a resolver still returns the old one, nothing is broken; you're waiting for cached copies to expire. If the authoritative server returns the old IP, like the one above, your change never reached the zone that's actually live. That leads to the next check.

Follow the chain with +trace

dig +trace +nodnssec example.com
Enter fullscreen mode Exit fullscreen mode

+trace skips your resolver's cache and walks the delegation itself. It asks a root server who handles .com, asks a .com server who handles example.com, then asks that nameserver for the record. The +nodnssec part just hides the signature records so the output stays readable.

Flat diagram of DNS resolution: a laptop asks a recursive resolver, which queries a root server, the .com TLD server and then the authoritative nameserver ns1.example.net for example.com

Here are the last two hops, trimmed:

example.com.            172800  IN      NS      ns1.example.net.
example.com.            172800  IN      NS      ns2.example.net.
;; Received 87 bytes from 192.5.6.30#53(a.gtld-servers.net) in 24 ms

example.com.            3600    IN      A       203.0.113.10
;; Received 56 bytes from 198.51.100.53#53(ns1.example.net) in 31 ms
Enter fullscreen mode Exit fullscreen mode

Each Received ... from line names the server that sent the block above it. The hop from a.gtld-servers.net is the delegation your registrar published. The final hop is what the zone itself says.

When the registrar and the zone disagree

This is the classic "I changed the record and nothing happened." You moved DNS to a new provider and created the records there, but the registrar still delegates to the old nameservers. The new zone is perfect, and nobody is reading it.

Ask a .com TLD server directly what the delegation looks like:

dig NS example.com @a.gtld-servers.net
Enter fullscreen mode Exit fullscreen mode

The NS records come back in the AUTHORITY section, not ANSWER, because the TLD server is referring you elsewhere rather than answering itself. That's also why +short prints nothing here. Then compare with the zone you've been editing and a public resolver:

dig +short NS example.com @ns1.example.org
dig +short example.com @ns1.example.org
dig +short example.com @1.1.1.1
Enter fullscreen mode Exit fullscreen mode

Terminal screenshot comparing dig NS example.com at a.gtld-servers.net, which still delegates to ns1.example.net, with the new nameserver ns1.example.org returning 203.0.113.20 while 1.1.1.1 still returns 203.0.113.10

Here the TLD still points to ns1.example.net and ns2.example.net, while the new provider's zone lists ns1.example.org and already holds the new IP. Resolvers follow the delegation, so they keep reading the old zone. The fix lives at the registrar: update the nameservers there, and remove any stale DS record if DNSSEC was enabled. Delegation records at the TLD usually carry a 172800-second TTL (two days), so resolvers that cached the old NS set can take a while to catch up.

Quick checks for MX, TXT and CNAME

Add the record type, and +short makes each check one line:

$ dig +short MX example.com
10 mail.example.com.
20 mail2.example.com.
$ dig +short TXT example.com
"v=spf1 ip4:198.51.100.25 include:_spf.example.net -all"
$ dig +noall +answer www.example.com
www.example.com.        3600    IN      CNAME   example.com.
example.com.            2917    IN      A       203.0.113.10
Enter fullscreen mode Exit fullscreen mode

Worth checking while you're there:

  • MX records must point to hostnames, never to IP addresses or CNAMEs. The lower number has priority.
  • A domain should publish exactly one v=spf1 TXT record. Two of them is an error, and receivers treat it as an SPF failure.
  • DKIM and DMARC live on their own names: dig +short TXT selector1._domainkey.example.com and dig +short TXT _dmarc.example.com.
  • +noall +answer shows a whole CNAME chain in one go. A name that has a CNAME can't hold any other records, which is why the bare domain usually can't be a CNAME.

Clear the cache on your own machine

Once the authoritative servers and public resolvers agree, your own computer may still hold the old answer.

On Linux with systemd-resolved:

sudo resolvectl flush-caches
resolvectl statistics    # "Current Cache Size" drops to 0
Enter fullscreen mode Exit fullscreen mode

On macOS:

sudo dscacheutil -flushcache
sudo killall -HUP mDNSResponder
Enter fullscreen mode Exit fullscreen mode

On Windows it's ipconfig /flushdns. Browsers keep their own DNS cache, so restart the browser too. Public resolvers can't be flushed from your machine, though the big public DNS services offer a purge page for single names.

A short troubleshooting checklist

  1. Run dig name type and read status before anything else.
  2. Check the SERVER line so you know whose cache you're looking at.
  3. Find the nameservers with dig NS, query one with @, and look for the aa flag.
  4. Compare that with the TLD delegation (dig NS name @a.gtld-servers.net or +trace). They must match.
  5. Ask @1.1.1.1 and @8.8.8.8 and note the remaining TTL. That's how long you'll wait.
  6. On SERVFAIL, retry with +cd to rule DNSSEC in or out.
  7. Flush local caches only after the authoritative answer is correct.

Most DNS problems aren't mysterious. They're a mismatch between two layers (registrar and zone, zone and cache, cache and browser), and the dig command shows you exactly which two. If your domains and DNS are managed at Pro Gineous, our support team is happy to walk through these checks with you whenever something looks off.

Every domain registered through our domain search and registration page comes with DNS you can manage yourself, so you can check your records with dig as you change them.

Related reading

Originally published on the Pro Gineous blog.

Top comments (0)