Skip to main content
Linux command: dig
On this page
# Topics

Linux command: dig

21 August 2026

Sooner or later every website problem turns out to be a DNS problem. The site loads for you but not for the customer, the new server is live but the old one keeps answering, the mail stops arriving after a migration. In all of those cases you need a way to ask the domain name system exactly what it thinks, without a browser, a cache, or a control panel in the way. That tool is dig.

1. The Basics

The job of dig is narrow and precise: it sends one DNS question to one name server and prints the complete reply, exactly as it came back over the network. Nothing is summarised, nothing is hidden, and nothing is interpreted for you.

That narrowness is the point. A browser tells you "this site cannot be reached", which could mean anything. dig tells you that the name server answered NXDOMAIN, or that it answered with the IP address of your old hosting provider, or that it did not answer at all. Those are three different problems with three different fixes.

Two properties make dig the tool of choice for DNS work. First, it is read-only. It asks questions; it never changes a zone, a record, or a cache. You can run it against your own servers or somebody else's without any risk. Second, it is honest. It shows the response codes, the flags, and the timings that other tools quietly swallow.

A question, a server, and the raw answer. Everything else in this article is a variation on those three things.

The right mental model: dig does not tell you "how my computer resolves this name". It tells you "what this particular server replies to this particular question, right now". Those are very different statements, and mixing them up causes most DNS confusion.

1.1 Getting dig Installed

On many distributions dig is not installed by default any more, because it lives in the same package as the other BIND client tools:

$ sudo apt install bind9-dnsutils     # Debian, Ubuntu
$ sudo dnf install bind-utils          # Fedora, RHEL, Rocky, AlmaLinux
$ sudo pacman -S bind                  # Arch Linux

The same package gives you nslookup and delv, both mentioned later in this article. On Debian and Ubuntu, host sits in a separate bind9-host package. Check your version with dig -v; every example here was run against the version below, and the behaviour has been stable for years.

$ dig -v
DiG 9.18.39-0ubuntu0.24.04.6-Ubuntu
Back to top

2. Where the Name Comes From

For decades the manual page gave dig an expansion: domain information groper.

dig  =  Domain Information Groper

It works on two levels. Literally, the program gropes around in the domain name system for information. And as a verb, you dig for something buried, which is a fair description of chasing a stale record through three layers of caching.

The expansion was printed in the manual page for decades. It was quietly removed in 2017, so on a modern system man dig simply calls it a "DNS lookup utility". The name is now just a name, which is what happens to most good acronyms eventually.

Back to top

3. A Short History

To understand why dig behaves the way it does, it helps to know that it was built by the people who wrote the DNS server software itself. It is not a third-party wrapper; it is the reference implementation's own debugging tool.

YearMilestone
1983 Paul Mockapetris describes the domain name system in RFC 882 and RFC 883
1986 nslookup, a Berkeley class project by Andrew Cherenson, ships in 4.3-Tahoe BSD
1987 RFC 1034 and RFC 1035 replace the originals and still define DNS today
Late 1980s Steve Hotz writes the first dig
1990 dig ships as part of BIND 4
2000 BIND 9 arrives with a dig rewritten by Michael Sawyer; this is the version you run today
2004 ISC reverses its plan to retire nslookup, in BIND 9.3
2017 The "domain information groper" expansion disappears from the manual page
Today Maintained by the Internet Systems Consortium; speaks DNS over UDP, TCP, TLS, and HTTPS

3.1 The Family of Lookup Tools

You will meet several commands that look like they do the same thing. They do not, and knowing which is which saves time.

CommandWhat it is for
dig Full DNS diagnostics. Shows the entire reply, every flag, and every timing.
host A short, friendly answer in one line. Good for a quick check, poor for debugging.
nslookup The oldest of the three, with an interactive mode. Still supported, but its output hides detail.
delv Like dig, but it actually validates DNSSEC signatures instead of only displaying them.
getent hosts Asks the operating system, not DNS. This is what your other programs really use.
resolvectl On systemd systems: which upstream servers are configured, and what is in the local cache.
kdig, drill Alternatives from Knot DNS and ldns. Same idea, slightly different output and options.

A word about nslookup. For a while the BIND developers intended to remove it, and a generation of tutorials still repeats "nslookup is deprecated". That decision was reversed in 2004. It is fully supported. The honest reason to prefer dig is not that nslookup is dying, but that dig shows you the response code and the flags, and nslookup does not.

Back to top

4. Simple Use Cases

4.1 The Simplest Possible Query

Type dig followed by a domain name. In the examples below, the line starting with $ is what you type; the rest is what the system prints back.

$ dig petermartin.nl

; <<>> DiG 9.18.39-0ubuntu0.24.04.6-Ubuntu <<>> petermartin.nl
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 89
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

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

;; ANSWER SECTION:
petermartin.nl.         349     IN      A       23.88.98.40

;; Query time: 0 msec
;; SERVER: 127.0.0.53#53(127.0.0.53) (UDP)
;; WHEN: Fri Aug 21 11:58:20 CEST 2026
;; MSG SIZE  rcvd: 59

That is a lot of output for one IP address, and beginners often reach straight for +short to make it go away. Resist that for a moment. Almost every field in this block answers a question you will eventually need to ask.

Two defaults are worth naming right away. When you give no record type, dig asks for an A record. When you name no server, it uses the first usable server in /etc/resolv.conf.

4.2 Reading the Output, Block by Block

The reply is always printed in the same order. Learn the blocks once and you can read any dig output at a glance.

BlockWhat it tells you
; <<>> DiG ... <<>> Your dig version and the exact command you ran. Useful when you paste output into a ticket.
HEADER The status: field. This is the single most important word in the whole output.
flags: Which bits the server set in its reply, plus how many records are in each section.
OPT PSEUDOSECTION EDNS details: the largest UDP reply both sides agreed on, and DNSSEC or cookie options.
QUESTION SECTION The question as the server understood it. Check here first when the answer surprises you.
ANSWER SECTION The records you asked for. This is what people mean by "the answer".
AUTHORITY SECTION Which name servers are responsible for the zone. Often shown when there is no answer.
ADDITIONAL SECTION Extra records the server sent unasked, usually the addresses of those name servers.
Query time How long the reply took. 0 msec almost always means a local cache hit.
SERVER Which server actually answered. Read this line every single time.
WHEN, MSG SIZE Timestamp and reply size in bytes. The size matters when packets get too big for UDP.

The SERVER line deserves special attention. In the example above it says 127.0.0.53, which is the local stub resolver on a systemd system, not a public DNS server. Half of all "my DNS change did not work" reports come from reading an answer that a local cache produced.

4.3 Reading a Single Record

Every record in an answer is one line with five fields, always in the same order. Once you can name them, any DNS output becomes readable:

petermartin.nl.         349     IN      A       23.88.98.40
|                       |       |       |       |
name                    TTL     class   type    data
FieldExampleMeaning
Name petermartin.nl. The name this record belongs to, written in full
TTL 349 Seconds this record may be cached. Section 5.6 explains why it changes
Class IN Short for Internet. In practice always IN
Type A The kind of record. Section 5.2 lists the ones you meet
Data 23.88.98.40 The value itself. Its shape depends on the type

The class field is a leftover from the 1980s. DNS was designed to carry other networks too, and dig still accepts -c CH for Chaosnet and -c HS for Hesiod. You will spend your career reading IN, but knowing what that column is stops it from being noise.

Now look at the trailing dot in petermartin.nl., which is easy to dismiss as a typo. It is not. DNS names are a hierarchy that ends at the root, and the root is written as an empty label after a final dot. A name with the dot is fully qualified: it means exactly this name and nothing else. A name without one may still get a search domain appended by whatever is resolving it. dig always prints the dot, because it always prints the complete name.

4.4 The Status Line: Four Words Worth Knowing

The status: field in the header is the server's verdict. Four values cover almost everything you will see.

StatusMeaningWhat to do
NOERROR The query worked. Note that this does not promise an answer section. Check the ANSWER: count. NOERROR with ANSWER: 0 means the name exists but has no record of that type.
NXDOMAIN The name does not exist at all. Check for a typo, a missing record, or a domain that has expired.
SERVFAIL The server tried and failed. Very often a DNSSEC validation failure. Ask a different resolver, then ask the authoritative server directly to find where it breaks.
REFUSED The server can answer but will not, for you. You are asking a server that does not serve this zone, or does not offer recursion to your IP.

The distinction in that first row catches people constantly. Compare these two replies. The first asks a healthy domain for a record type it does not have: the status is NOERROR, and the answer count is zero.

$ dig petermartin.nl SRV
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 13851
;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 2, ADDITIONAL: 7

;; AUTHORITY SECTION:
petermartin.nl.         85897   IN      NS      chelsea.ns.cloudflare.com.

The second asks for a name that simply is not there. Now the status itself changes:

$ dig this-does-not-exist-pm.nl
;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 32259
;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1

Both produce an empty answer section, and +short prints nothing in either case. Only the status word tells you whether the domain is misconfigured or does not exist, and those need very different fixes.

One modern caveat spoils the neat rule, and it is worth knowing before it confuses you. Some large DNS providers no longer return NXDOMAIN at all on signed zones. Watch the same missing name answered two ways:

$ dig @elliott.ns.cloudflare.com nosuch.example.com | grep status
;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 48273

$ dig @elliott.ns.cloudflare.com +dnssec nosuch.example.com | grep status
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 55148

Adding +dnssec turned a missing name into NOERROR. This is compact denial of existence, nicknamed "black lies". Proving that a name does not exist requires signed records, and the honest way of doing that lets anyone walk the whole zone name by name. So instead the server synthesises a record on the spot that says "this name exists, but has no data", which is cheaper to sign and gives nothing away.

Because public resolvers request DNSSEC on every query, this is what you normally see:

$ dig @1.1.1.1 nosuch.example.com | grep status        # Cloudflare-hosted zone
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 14782

$ dig @1.1.1.1 nosuch-x.debian.org | grep status      # a zone that does not do this
;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 24703

So on a large share of real domains, a name that truly does not exist reports NOERROR with zero answers. Do not read that as "the record type is missing" without checking. Ask the authoritative server without +dnssec, as in the first example above, and the plain NXDOMAIN comes back. dig even carries an option for the mechanism, +coflag, which sets the compact-denial-of-existence flag in the query.

4.5 The Flags Line

The flags are two-letter abbreviations of bits in the DNS message header. You do not need to know all of them, but these six come up in daily work.

FlagStands forWhy you care
qr Query response This is a reply, not a question. Always present in output.
rd Recursion desired You asked the server to do the work of chasing the answer.
ra Recursion available The server is willing to do that for you. Missing on authoritative-only servers.
aa Authoritative answer The answer comes from the source, not from a cache. This is the flag you want when you verify a change.
ad Authentic data The resolver validated the DNSSEC signatures and they were good.
cd Checking disabled You asked the resolver to skip DNSSEC validation. Useful to prove a SERVFAIL is DNSSEC related.

The practical rule: if aa is missing, you are reading a cached copy. Whatever it says may be minutes or hours out of date.

4.6 Cutting the Noise: +short

Once you understand the full output, +short is the flag you will type most. It prints the answer data and nothing else:

$ dig +short petermartin.nl
23.88.98.40

$ dig +short example.com
172.66.147.243
104.20.23.154

That second result is a useful reminder in itself: a name can have several addresses, and the order changes between queries. There is no such thing as "the" IP address of a busy site.

+short is perfect for scripts and for quick confirmation. It is the wrong tool the moment something is broken, because it throws away the status, the flags, and the server line, which is exactly the information a broken lookup depends on.

4.7 Asking for a Specific Record Type

Add the record type after the name. Case does not matter, and dig is relaxed about the order of its arguments:

$ dig +short petermartin.nl MX
0 fallback.axc.eu.

$ dig +short petermartin.nl NS
chelsea.ns.cloudflare.com.
finley.ns.cloudflare.com.

$ dig mx joomla.org +short        # type first also works
5 alt2.aspmx.l.google.com.
1 aspmx.l.google.com.
10 alt3.aspmx.l.google.com.
10 alt4.aspmx.l.google.com.
5 alt1.aspmx.l.google.com.

Look closely at that MX list. The numbers are priorities, where lower means preferred, and the lines are clearly not sorted. dig prints records in the order the server sent them, and servers deliberately shuffle them. Sorting is the mail server's job, not the display's, so do not read the first line as "the main mail server".

If you prefer to be explicit, and in scripts you should be, use -t for the type and -q for the name. This removes all ambiguity, which matters for names that collide with type or class keywords:

$ dig -t MX -q petermartin.nl +short
0 fallback.axc.eu.

4.8 Choosing Which Server Answers: @

Put @ followed by a server in front of the name, and dig asks that server instead of the one in /etc/resolv.conf. This single character is what turns dig from a lookup tool into a diagnostic tool:

$ dig @1.1.1.1 +short petermartin.nl      # Cloudflare's public resolver
$ dig @8.8.8.8 +short petermartin.nl      # Google's public resolver
$ dig @9.9.9.9 +short petermartin.nl      # Quad9
$ dig @192.168.1.1 +short petermartin.nl  # your own router
$ dig @chelsea.ns.cloudflare.com +short petermartin.nl   # the authoritative source

Comparing two of those answers tells you far more than any single lookup. If the authoritative server says one thing and a public resolver says another, you are simply waiting for a cache to expire. If they agree and your own machine disagrees, the problem is on your side of the wire.

Ninety percent of practical DNS troubleshooting is one question asked twice: once to a cache, and once to the authoritative name server. The difference between the two answers is the problem.

Back to top

5. Moderate Use Cases

5.1 The Display Flags: +noall +answer

Between the wall of full output and the bare data of +short sits the combination most administrators settle on. It works by switching every display section off, then switching one back on:

$ dig +noall +answer petermartin.nl
petermartin.nl.         349     IN      A       23.88.98.40

The mechanism is worth understanding, because it generalises. Each section of the output has its own switch, and +all or +noall sets them all at once:

OptionControls
+[no]cmd The version and command banner at the top
+[no]comments The header line, the flags line, and the section titles
+[no]question The question section
+[no]answer The answer section
+[no]authority The authority section
+[no]additional The additional section
+[no]stats The query time, server, timestamp, and message size
+[no]all All of the above at once

So +noall +answer means "show me nothing, except the answer, with the record fields intact". Add anything else you want back:

$ dig +noall +answer +authority petermartin.nl MX     # answer plus who serves the zone
$ dig +noall +comments example.com                    # only the header and flags
$ dig +noall +answer +stats petermartin.nl            # the answer plus which server gave it

Keep +short for scripts that need one value, and +noall +answer for everything you will read with your own eyes.

5.2 The Record Types You Actually Meet

DNS has dozens of record types. In website and mail administration the same handful comes up again and again.

TypePurpose
A An IPv4 address for a name
AAAA An IPv6 address. Named for being four times the size of an A record
CNAME An alias: "look up this other name instead"
MX Mail exchanger: where email for the domain should be delivered
TXT Free text. In practice: SPF, DKIM, DMARC, and site verification tokens
NS The authoritative name servers for the zone
SOA Start of authority: the serial number and the timers of the zone
CAA Which certificate authorities may issue certificates for this domain
SRV A service on a host and port, used by SIP, XMPP, and mail submission
PTR The reverse: an address mapped back to a name
DS, DNSKEY, RRSIG The DNSSEC records: delegation signer, public key, and signature

Here is a real domain examined type by type. Notice how much you learn about a hosting setup from four commands:

$ dig +noall +answer petermartin.nl A
petermartin.nl.         349     IN      A       23.88.98.40

$ dig +noall +answer petermartin.nl AAAA
petermartin.nl.         589     IN      AAAA    2a01:4f8:c0c:6848::1

$ dig +noall +answer petermartin.nl NS
petermartin.nl.         7052    IN      NS      chelsea.ns.cloudflare.com.
petermartin.nl.         7052    IN      NS      finley.ns.cloudflare.com.

$ dig +noall +answer www.petermartin.nl
www.petermartin.nl.     349     IN      CNAME   petermartin.nl.
petermartin.nl.         349     IN      A       23.88.98.40

That last one shows an important behaviour. You asked for an A record for www, and the server gave you two records: the alias, and then the address it points at. dig shows the whole chain, which is how you spot a CNAME pointing at a name that no longer exists.

A TXT lookup is the fastest way to check mail authentication. SPF lives on the domain itself, DMARC on the _dmarc subdomain, and DKIM on a selector you choose:

$ dig +short petermartin.nl TXT
"google-site-verification=Cb25YjPnzwZBEfXrkDteEOOkHC_1xv9XCXhTeCOBPzg"

$ dig +short _dmarc.example.com TXT      # the DMARC policy
$ dig +short mail._domainkey.example.com TXT     # a DKIM public key

And CAA tells you which certificate authorities are allowed to issue for the domain, which explains a surprising number of failed certificate renewals:

$ dig +short petermartin.nl CAA
0 issue "letsencrypt.org"
0 issue "digicert.com; cansignhttpexchanges=yes"
0 issuewild "letsencrypt.org"

5.3 Records Travel in Sets

DNS does not really deal in individual lines. Every record that shares the same name, class, and type forms one unit called a resource record set, or RRset. The two addresses of example.com are not two answers; they are one RRset with two members:

$ dig +noall +answer example.com
example.com.            67      IN      A       172.66.147.243     } one RRset,
example.com.            67      IN      A       104.20.23.154      } two records

This is not a detail for protocol pedants. Three things you will meet follow directly from it:

  • The whole set shares one TTL. You cannot expire one address sooner than the other, because a cache stores and discards the set as a whole.
  • DNSSEC signs the set, not the record. That is why a zone with two A records still shows only one RRSIG A alongside them. Change any member and the signature over the entire set is recomputed.
  • Order carries no meaning. A server may return the members in any order, and many rotate them deliberately to spread load. This is the same effect as the unsorted MX list above, and the reason to be wary of scripts that take the first line and treat it as "the" answer.

The rule also explains a restriction people run into when configuring a zone. A CNAME may not sit alongside other records at the same name, because that would put two different types where the protocol expects one alias to stand for everything. Section 9.2 returns to this as the zone-apex trap.

5.4 Reverse Lookups With -x

A reverse lookup turns an IP address back into a name. Done by hand this means reversing the octets and appending in-addr.arpa, which nobody wants to do twice. The -x flag builds that name for you and sets the type to PTR:

$ dig -x 8.8.8.8 +short
dns.google.

$ dig -x 8.8.8.8 +noall +question
;8.8.8.8.in-addr.arpa.                  IN      PTR

The question section shows exactly what -x constructed. IPv6 addresses work the same way, expanded one hex digit at a time under ip6.arpa.

Reverse DNS matters most for mail servers. Many receiving systems check that the sending IP has a PTR record and that the name it gives resolves back to the same address. A missing PTR is a common reason for mail landing in spam.

5.5 Several Questions in One Command

dig accepts multiple lookups on one line. It runs them in order and prints each reply:

$ dig +noall +answer petermartin.nl A petermartin.nl MX
petermartin.nl.         497     IN      A       23.88.98.40
petermartin.nl.         600     IN      MX      0 fallback.axc.eu.

This introduces a subtlety in how options are applied. Options given before the first name are global and apply to every lookup. Options given after a name apply only to that lookup. That lets you compare two servers in a single command:

$ dig +short petermartin.nl @1.1.1.1 petermartin.nl @chelsea.ns.cloudflare.com
23.88.98.40
23.88.98.40

When the two lines differ, a cache is holding an old value.

5.6 The TTL Column, and Why the Number Keeps Changing

The second column of every record is the TTL, the time to live, in seconds. It is the answer's expiry date: how much longer a resolver may keep this record in its cache before asking again.

Run the same query twice and watch:

$ dig +noall +answer petermartin.nl
petermartin.nl.         408     IN      A       23.88.98.40

$ dig +noall +answer petermartin.nl     # three seconds later
petermartin.nl.         405     IN      A       23.88.98.40

The number counts down. That is the clearest possible proof that you are reading a cached answer: the cache is telling you how much of its life is left. Ask the authoritative server instead and you get the zone's configured value, which stays the same every time.

This countdown is also the honest answer to "how long does DNS propagation take". There is no propagation. There is only a set of caches, each holding a record until its own TTL runs out.

Which is why, when I know a DNS change is coming, I lower the TTL a day beforehand and leave it alone. It feels like doing nothing, and that is the point: by the time I make the real change, every cache that mattered has already thrown away its long-lived copy and replaced it with one that expires in five minutes. The switchover then takes minutes instead of a day, and I have watched it happen rather than hoped it did.

The order matters more than the number. Lowering the TTL at the same moment you change the record achieves nothing, because the caches holding the old answer were handed the old TTL and will keep it for the full term regardless. The new short value only reaches them when that term runs out.

So the wait is set by the value you are replacing, not by the one you are moving to. Check what you are actually up against before you plan the day:

$ dig @chelsea.ns.cloudflare.com petermartin.nl +noall +answer
petermartin.nl.         300     IN      A       23.88.98.40

Ask the authoritative server, not a cache, or you will read a number that is already counting down. A zone on 3600 needs an hour before the low TTL is everywhere, and a day is a comfortable margin. A zone still sitting on the traditional 86400 needs a full day, so "the day before" means exactly that, and leaving an extra few hours costs nothing.

Two separate things decide how quickly a change goes live, and it helps to keep them apart. The first is how fast your DNS provider publishes the edit across its own name servers. The second is how long every cache in the world may hold the previous answer, which is the TTL. You control the second one; the provider controls the first.

That first half is why I host most of the domains I look after on Cloudflare. An edit is live on their authoritative servers within seconds rather than waiting for a zone transfer cycle, and every one of those servers answers from the same anycast address, so there is no window where one name server has the change and another does not. Their default TTL is short too: the 300 seconds in the check above is what every Cloudflare-hosted domain in this article answers with.

Five minutes is a comfortable default, and it quietly removes the planning problem from the previous paragraphs. A zone that already sits on 300 has nothing to lower a day in advance, so an unplanned change, the kind you make because something is broken rather than because it was scheduled, still clears in the time it takes to make coffee. Any provider with fast publishing and a sane default TTL does this job; the point is to know which of the two you are waiting on when a change seems slow. Section 6.4 shows how to confirm the first half for yourself, by asking every authoritative server for its serial number.

If seconds are hard to read, ask for units:

$ dig +ttlunits +noall +answer petermartin.nl
petermartin.nl.         8m17s   IN      A       23.88.98.40

5.7 So What Should the TTL Actually Be?

This is the question I get asked more than any other about DNS, and most answers are folklore. dig lets you replace the folklore with measurements. Ask a handful of well-run domains what they publish, straight from their own authoritative servers:

$ ns=$(dig +short google.com NS | head -1)
$ dig @$ns google.com A +noall +answer
google.com.             300     IN      A       192.178.25.206

The address you get back will differ from mine, because Google answers with whatever is nearest. The second column will not: that is a published setting, and it is the one we are after.

Repeat that for a few domains and a clear pattern appears. These are real values, measured at the source:

DomainANSMXTXT
google.com 300 345600 300 300
wikipedia.org 180 172800 300 600
github.com 60 3600 300 300
nu.nl 20 3600 3600 900
joomla.org 300 86400 300 300

Two things jump out. Nobody runs a long TTL on an address record any more: the whole column sits between 20 seconds and five minutes. And NS records are the opposite, measured in days, with Google on four of them. So there is no single best TTL. The number depends on what that particular record needs to do.

A high TTL buys resilience. This is the benefit people forget. If your authoritative name servers go down, every resolver holding a cached copy keeps answering until it expires, so a long TTL is an outage buffer. It also cuts query volume, which matters if your provider bills per query, and saves a new visitor one recursive lookup.

A low TTL buys control. You can move a site, fail over to a standby, or undo a mistake and have the internet follow within minutes. It is what makes health-checked failover and geographic balancing work at all, and it limits the damage when you get something wrong.

The trade-off has shifted over the years. DNS is cheap and served from anycast networks now, so the query cost of a low TTL is close to irrelevant for an ordinary site. What you are really giving up is the outage buffer. RFC 8767 from 2020 softens even that, by letting resolvers serve expired records when the source is unreachable, though not every resolver does.

Here is what I set, and why:

RecordTTLReasoning
A, AAAA 300 Five minutes. Agile enough for an unplanned move, and the same value most of the table above uses
NS 86400 Keep long. They almost never change, and this is where the outage buffer genuinely pays
MX 3600 Sending servers retry for hours, so a short TTL buys little
TXT (SPF, DKIM, DMARC) 3600 Changes are planned, never urgent
CAA 3600 Read at certificate issue time, not on every visit
SOA minimum 300 to 900 The negative TTL. Keep it low or a cached "no" blocks a record you just created, as section 5.10 explains
During a migration 300, or 60 Set it a day ahead, move, then put it back

Two exceptions are worth naming. If your records point at a proxy or CDN, the TTL barely matters, because the address you publish stays the same and the routing decisions happen above DNS. And if you run failover that depends on DNS, you need 60 seconds or less, accepting that clients and intermediate resolvers will not all honour it.

Finally, some perspective on the advice you will find elsewhere. RFC 1912, from 1996, recommends a TTL of one to five days, and describes the lower-it-beforehand routine almost word for word: turn the value down, wait out the previous one, make the change, then turn it back up. The procedure is thirty years old and still exactly right. The numbers are not, at least for address records, and the table above is the evidence. When a piece of DNS advice quotes a value, check what the internet is actually doing before you follow it.

5.8 Watching the Countdown With watch

This is the habit I rely on most after changing a record, and it turns waiting into something you can actually see. The watch command re-runs any command at a fixed interval and redraws the screen, so pointing it at dig gives you a live view of the cache emptying:

$ watch -n 5 dig +noall +answer petermartin.nl

The -n 5 sets the interval to five seconds. Add -d (short for differences) and watch highlights whatever changed since the previous frame, which makes the moving TTL jump out of the line.

Here is what the frames look like, printed one after another so you can see the whole event on the page:

12:34:18  petermartin.nl.       11    IN    A    23.88.98.40
12:34:23  petermartin.nl.        6    IN    A    23.88.98.40
12:34:28  petermartin.nl.        1    IN    A    23.88.98.40
12:34:33  petermartin.nl.      600    IN    A    23.88.98.40     <- expired, fetched again
12:34:38  petermartin.nl.      594    IN    A    23.88.98.40

That jump back up is the moment the cached copy expired and the resolver went and asked again. If you have just pointed a domain at a new server, that is the exact frame where the old IP address is replaced by the new one. Instead of reloading a browser and wondering, you sit and watch the number fall to zero, and you know precisely when to check.

Notice one detail in those frames, because it is the kind of thing that costs people an afternoon. The record came back with 600, but the zone is published with a TTL of 300:

$ dig @chelsea.ns.cloudflare.com petermartin.nl +noall +answer   # the source
petermartin.nl.         300     IN      A       23.88.98.40

$ dig @1.1.1.1 petermartin.nl +noall +answer                     # a public resolver
petermartin.nl.         300     IN      A       23.88.98.40

$ dig +noall +answer petermartin.nl                              # my own router
petermartin.nl.         556     IN      A       23.88.98.40

The router on my own desk hands out answers with a longer life than the zone asked for. Consumer routers and some ISP resolvers do this, holding records past their stated TTL to save themselves work. Your carefully lowered TTL is a request, not a guarantee, and the last box between you and the internet may quietly ignore it. Section 7.5 covers the trick I use to get around exactly this.

One courtesy: pick a sensible interval. watch defaults to two seconds, and pointing that at somebody else's authoritative name servers for an hour is not polite. Five or ten seconds tells you everything you need.

5.9 Making an SOA Record Readable: +multiline

The SOA record packs two names and five numbers onto one line, and nobody remembers the order:

$ dig +noall +answer petermartin.nl SOA
petermartin.nl.  1800  IN  SOA  chelsea.ns.cloudflare.com. dns.cloudflare.com. 2411557357 10000 2400 604800 1800

The +multiline option expands records into a commented, indented form. For SOA it is transformative:

$ dig +multiline +noall +answer petermartin.nl SOA
petermartin.nl.         1789 IN SOA chelsea.ns.cloudflare.com. dns.cloudflare.com. (
                                2411557357 ; serial
                                10000      ; refresh (2 hours 46 minutes 40 seconds)
                                2400       ; retry (40 minutes)
                                604800     ; expire (1 week)
                                1800       ; minimum (30 minutes)
                                )

The serial is the one to remember. Every time a zone changes, the serial must go up, and secondary name servers use it to decide whether to pull a fresh copy. If you changed a record and the secondaries still serve the old data, compare serials across servers first. Section 6.4 shows how to do that in one command.

The second field, dns.cloudflare.com., looks like a hostname but is an email address with the first dot standing in for the @. It is a piece of 1980s syntax that trips up everyone exactly once.

5.10 "It Does Not Exist" Gets Cached Too

The last SOA field, minimum, is the one worth a paragraph of its own, because its name is misleading. It once meant a minimum TTL for the zone. Since RFC 2308 in 1998 it means something else entirely: it is the negative caching TTL, the time a resolver may remember that something does not exist.

You can watch this happen. A negative answer has no answer section, so the server puts the zone's SOA in the authority section instead, and that record's TTL is how long the "no" is good for:

$ dig @1.1.1.1 nosuch.example.com +noall +comments +authority
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 13792
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 1

;; AUTHORITY SECTION:
example.com.  1800  IN  SOA  elliott.ns.cloudflare.com. dns.cloudflare.com. 2411783310 10000 2400 604800 1800

That trailing 1800 is the minimum field, so this "no" may be cached for thirty minutes. The SOA appearing in the authority section is itself the signal: whenever you see it there with an empty answer, you are looking at a negative answer and being told how long it will stick.

The practical consequence catches people out constantly, and it is the mirror image of the TTL problem in the previous section. You add a record that was missing, you query it, and it is still missing. Nothing is broken: a resolver asked before you made the change, cached the "no", and will not ask again until the negative TTL runs out. The proof takes one command, because the authoritative server has no cache to consult:

$ dig @chelsea.ns.cloudflare.com newrecord.petermartin.nl +short   # the source
$ dig @1.1.1.1 newrecord.petermartin.nl +short                     # a cache

If the first answers and the second does not, you are waiting out a cached negative, not fixing a record. This is also why creating the DNS record before anything asks for it is worth the small effort: nothing can cache a "no" for a name nobody has queried yet.

Back to top

6. Advanced Use Cases

6.1 Following the Delegation: +trace

Normally you ask one resolver and it does all the work. With +trace, dig does the work itself: it starts at the root servers and walks down the delegation chain, printing each step. It is the single most instructive thing you can run against a domain.

$ dig +trace petermartin.nl

.                       39987   IN      NS      b.root-servers.net.
.                       39987   IN      NS      f.root-servers.net.
;; Received 239 bytes from 127.0.0.53#53(127.0.0.53) in 14 ms

nl.                     172800  IN      NS      ns1.dns.nl.
nl.                     172800  IN      NS      ns3.dns.nl.
nl.                     172800  IN      NS      ns4.dns.nl.
nl.                     86400   IN      DS      17153 13 2 C5DFDDC91E75...
;; Received 596 bytes from 170.247.170.2#53(b.root-servers.net) in 15 ms

petermartin.nl.         3600    IN      NS      chelsea.ns.cloudflare.com.
petermartin.nl.         3600    IN      NS      finley.ns.cloudflare.com.
;; Received 275 bytes from 185.159.199.200#53(ns4.dns.nl) in 10 ms

petermartin.nl.         300     IN      A       23.88.98.40
;; Received 169 bytes from 162.159.38.177#53(chelsea.ns.cloudflare.com) in 6 ms

Read it as a story in four steps. A root server hands off to the servers for .nl. The .nl registry hands off to Cloudflare. Cloudflare answers with the address. Each ;; Received line names the server that produced the block above it.

This is where delegation problems become obvious. If the NS records at the registry point at name servers that do not host the zone, the trace stops there, and no amount of editing records at your new provider will help.

Two details are easy to miss. +trace turns on +dnssec automatically, which is why DS and RRSIG records appear. And the very first step still uses your own resolver to find the root servers; everything after that is dig talking to the authoritative servers directly.

6.2 Glue Records and the Two Sets of NS Records

The trace above skipped past something. When the .nl servers named Cloudflare's name servers, how does a resolver find those? Usually by looking them up, which is fine when they live in a different zone. But consider a domain whose name servers sit inside itself:

google.com.  NS  ns1.google.com.

To reach ns1.google.com you must ask the servers for google.com, and to find those you must reach ns1.google.com. That is a circle, and DNS breaks it with glue records: address records the parent zone hands out along with the delegation, purely so the chain can continue. Ask a .com server and you can see them arrive:

$ dig @a.gtld-servers.net google.com NS +noall +authority +additional
google.com.             172800  IN      NS      ns2.google.com.
google.com.             172800  IN      NS      ns1.google.com.
google.com.             172800  IN      NS      ns3.google.com.
google.com.             172800  IN      NS      ns4.google.com.
ns2.google.com.         172800  IN      A       216.239.34.10
ns1.google.com.         172800  IN      A       216.239.32.10
ns3.google.com.         172800  IN      A       216.239.36.10
ns4.google.com.         172800  IN      A       216.239.38.10

The NS records are the delegation; the A records underneath are the glue. The real reply carries an AAAA record for each server too, left out here for space. Now compare a domain whose name servers live elsewhere. There is no circle to break, so the parent sends no glue at all:

$ dig @ns1.dns.nl petermartin.nl NS +noall +authority +additional
petermartin.nl.         3600    IN      NS      finley.ns.cloudflare.com.
petermartin.nl.         3600    IN      NS      chelsea.ns.cloudflare.com.

Both replies are correct. Glue is not a quality signal; it appears only when it is needed. Stale glue, however, is a genuine and nasty failure: if you renumber a name server that lives inside its own zone and update the record without telling the registry, the parent keeps handing out the old address and part of the internet keeps arriving at a server that is gone.

This leads to the check that solves most delegation mysteries. There are two sets of NS records, one held by the parent zone and one inside the zone itself, and nothing forces them to agree. Ask each side separately:

$ dig @ns1.dns.nl petermartin.nl NS +noall +authority     # what the parent says
petermartin.nl.         3600    IN      NS      finley.ns.cloudflare.com.
petermartin.nl.         3600    IN      NS      chelsea.ns.cloudflare.com.

$ dig @chelsea.ns.cloudflare.com petermartin.nl NS +short   # what the zone says
chelsea.ns.cloudflare.com.
finley.ns.cloudflare.com.

Read them as sets, not lists: the order differs between the two replies and means nothing, exactly as section 5.3 described.

These two agree, which is what you want. When they do not, the parent's list wins for the rest of the world, because that is the one resolvers follow. A zone can therefore be perfectly configured and still be unreachable for half its visitors, and no amount of editing records at the DNS provider will change it. The fix lives at the registrar.

A domain has two answers to "which name servers?": the parent's and its own. Every confusing delegation problem is somewhere in the gap between them, and dig is how you see both.

6.3 Asking the Authoritative Server Directly

This is the verification workflow that matters. First ask who is authoritative, then ask one of those servers, and check for the aa flag in the reply:

$ dig +short petermartin.nl NS
chelsea.ns.cloudflare.com.
finley.ns.cloudflare.com.

$ dig @chelsea.ns.cloudflare.com petermartin.nl +noall +comments +answer
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 38772
;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; WARNING: recursion requested but not available

;; ANSWER SECTION:
petermartin.nl.         300     IN      A       23.88.98.40

The aa in the flags line is the whole point. That answer came from the source, with no cache in between, so it is the truth as of this second. The TTL of 300 is the zone's configured value rather than a countdown.

Ignore the warning. It appears because dig asks for recursion by default and this server, being authoritative only, does not offer it. That is exactly how an authoritative name server should behave.

You can stop asking, with +norecurse, which clears the "recursion desired" bit and silences the warning. Sent to an authoritative server it says "answer only from your own zones, do not go and look it up". Sent to a resolver that is not meant to serve you, it reveals the fact bluntly:

$ dig +norecurse +noall +comments example.com
;; ->>HEADER<<- opcode: QUERY, status: REFUSED, id: 28371
;; flags: qr ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1

This is also the polite way to inspect a cache. Because the query never triggers a lookup, an answer means the record was already cached, and no answer means it was not.

6.4 Are All My Name Servers in Sync? +nssearch

A zone usually lives on several name servers, and they are supposed to hold identical data. The +nssearch option finds every authoritative server for a domain and asks each one for its SOA record:

$ dig -4 +nssearch petermartin.nl
SOA chelsea.ns.cloudflare.com. dns.cloudflare.com. 2411557357 10000 2400 604800 1800 from server 162.159.38.177 in 6 ms.
SOA chelsea.ns.cloudflare.com. dns.cloudflare.com. 2411557357 10000 2400 604800 1800 from server 108.162.195.58 in 6 ms.
SOA chelsea.ns.cloudflare.com. dns.cloudflare.com. 2411557357 10000 2400 604800 1800 from server 172.64.35.58 in 6 ms.

Compare the serial numbers. Identical serials mean the servers agree. A server lagging behind means a zone transfer is failing, and that server is handing out stale records to whichever visitors happen to reach it. That produces the maddening "it works for some people" symptom.

The -4 in that command forces IPv4. Without it, dig also tries the IPv6 addresses of every name server, and on a machine without working IPv6 you get a screen of network unreachable messages before the useful output.

6.5 DNSSEC: +dnssec

DNSSEC signs DNS records so a resolver can prove an answer was not tampered with. The +dnssec option sets the DO (DNSSEC OK) bit, which asks the server to include the signatures:

$ dig @1.1.1.1 +dnssec +noall +comments +answer cloudflare.com
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 33698
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags: do; udp: 1232
;; ANSWER SECTION:
cloudflare.com.         300     IN      A       104.16.132.229
cloudflare.com.         300     IN      A       104.16.133.229
cloudflare.com.         300     IN      RRSIG   A 13 2 300 20260822105534 20260820085534 34505 cloudflare.com. BHEr7q0Tdn...

Three things confirm DNSSEC is working: flags: do in the EDNS line means the request asked for it, the RRSIG record is the signature itself, and ad in the header flags means this resolver checked the signature and it was valid.

Run the same command without @1.1.1.1 on a typical desktop and the ad flag and the RRSIG may both be missing. That is not a broken domain; it is the local stub resolver not doing validation and not passing the records through. When you test DNSSEC, always name a resolver you trust to validate.

DNSSEC failure has a signature symptom: SERVFAIL from validating resolvers, and a perfectly normal answer from the authoritative server. There is a domain kept permanently broken for testing:

$ dig @1.1.1.1 +short dnssec-failed.org
                       # nothing at all

$ dig @1.1.1.1 dnssec-failed.org | grep status
;; ->>HEADER<<- opcode: QUERY, status: SERVFAIL, id: 64920

$ dig @1.1.1.1 +cd +short dnssec-failed.org     # +cd = checking disabled
96.99.227.255

That last command is the diagnostic trick. If +cd produces an answer where the normal query gives SERVFAIL, the problem is DNSSEC validation and not the records themselves. Name a validating resolver explicitly, as above: a local stub that does no validation has nothing to disable, and +cd changes nothing there.

For the final word, use delv. Where dig only displays signatures, delv checks them and says so in its first line:

$ delv @1.1.1.1 cloudflare.com
; fully validated
cloudflare.com.         300     IN      A       104.16.132.229
cloudflare.com.         300     IN      RRSIG   A 13 2 300 20260822110511 ...

$ delv @1.1.1.1 dnssec-failed.org
;; resolution failed: failure

The words fully validated are the confirmation you want after enabling DNSSEC on a zone. Add +rtrace to watch each fetch delv makes on the way.

6.6 Zone Transfers: AXFR

A zone transfer copies an entire zone in one request. It is how secondary name servers get their data, and a correctly configured server allows it only from the addresses it knows:

$ dig @chelsea.ns.cloudflare.com petermartin.nl AXFR
; Transfer failed.

That refusal is the correct and expected result. A public zone transfer hands an attacker every hostname you have, including the ones you thought were private, so it is worth testing your own name servers this way. There is a domain that deliberately allows it, for teaching:

$ dig @nsztm1.digi.ninja zonetransfer.me AXFR +noall +answer
zonetransfer.me.        7200    IN      SOA     nsztm1.digi.ninja. robin.digi.ninja. 2019100801 172800 900 1209600 3600
zonetransfer.me.        7200    IN      MX      0 ASPMX.L.GOOGLE.COM.
zonetransfer.me.        301     IN      TXT     "google-site-verification=tyP28J7JAUHA9fw..."

Zone transfers always use TCP, because a whole zone rarely fits in a UDP packet. dig switches transport for you.

6.7 When UDP Is Not Enough: +tcp and Truncation

DNS queries normally travel over UDP, which is fast but limited in size. When a reply is too large, the server sets the tc (truncated) flag, and dig automatically retries over TCP. You will see this with large TXT sets, big DNSKEY records, and DNSSEC answers.

$ dig +tcp +noall +answer +stats example.com
example.com.            215     IN      A       172.66.147.243
example.com.            215     IN      A       104.20.23.154
;; Query time: 7 msec
;; SERVER: 127.0.0.53#53(127.0.0.53) (TCP)
;; MSG SIZE  rcvd: 491

The transport appears in brackets on the SERVER line, so you can always tell which one was used. Two more options let you experiment with the size limits:

$ dig +bufsize=512 +ignore example.com   # small buffer, do not retry over TCP
$ dig +bufsize=1232 example.com          # the size most resolvers use today

The +ignore option tells dig not to fall back to TCP, so you can see the truncated reply for yourself. This combination is how you catch a firewall that allows DNS over UDP port 53 but blocks TCP port 53. That misconfiguration works fine for years and then breaks the day you enable DNSSEC, because the answers suddenly get bigger.

6.8 Timeouts and Retries

By default dig waits five seconds per try and makes three attempts, which is a long time to sit and watch when a server is simply dead. Both are adjustable:

$ dig +time=2 +tries=1 @192.0.2.1 example.com    # give up quickly
;; communications error to 192.0.2.1#53: timed out

Use this in monitoring scripts, where a fast failure is more useful than a slow one. Note the difference between +tries (total UDP attempts, default 3) and +retry (retries after the first attempt, default 2). They describe the same thing from two angles.

6.9 Seeing What a Visitor Elsewhere Sees: +subnet

Content delivery networks and geo-balanced setups answer differently depending on where the visitor is. The EDNS Client Subnet option lets you ask on behalf of another network:

$ dig @8.8.8.8 +subnet=145.53.0.0/24 +noall +comments +answer example.com
; CLIENT-SUBNET: 145.53.0.0/24/24
example.com.            300     IN      A       104.20.23.154

This answers the question "why does the customer in another country get a different IP address than I do" without asking anyone to run commands for you. Not every resolver honours the option, and passing a full address instead of a network shares more about the client than you probably intend, so use a /24.

6.10 Batch Mode and Your Own Defaults

The -f flag (short for file) reads a list of lookups from a file, one per line, written exactly as you would type them:

$ cat domains.txt
petermartin.nl A
joomla.org MX

$ dig -f domains.txt +noall +answer
petermartin.nl.         483     IN      A       23.88.98.40
joomla.org.             600     IN      MX      1 aspmx.l.google.com.
joomla.org.             600     IN      MX      5 alt1.aspmx.l.google.com.

If you always want the same options, put them in ~/.digrc, one per line or all on one. dig applies them before your command line arguments:

$ cat ~/.digrc
+noall +answer +ttlunits

This is convenient and it is a trap. Any script you write will inherit those defaults on your machine and behave differently on a server without the file. Scripts should therefore pass -r, which tells dig to ignore ~/.digrc entirely.

6.11 Encrypted Transports: +tls and +https

Classic DNS travels in plain text, so anyone on the path can read and modify it. Modern dig speaks both encrypted alternatives:

$ dig +tls @1.1.1.1 +short example.com     # DNS over TLS, port 853
$ dig +https @1.1.1.1 +short example.com   # DNS over HTTPS, port 443

These are useful for two jobs: proving that a resolver really offers the encrypted service it advertises, and checking whether a network blocks it. Add +tls-ca to validate the server's certificate rather than merely encrypting the connection.

6.12 Output for Machines: +yaml

Parsing dig's normal output with awk works until the day a record type formats itself differently. For anything that must be reliable, ask for YAML:

$ dig +yaml example.com
- type: MESSAGE
  message:
    type: RECURSIVE_RESPONSE
    message_size: 72b
    socket_protocol: UDP
    response_address: "127.0.0.53"
    response_port: 53
    response_message_data:
      opcode: QUERY
      status: NOERROR
      id: 57077
      flags: qr rd ra

Every field of the reply is a labelled key, including the status and the flags that +short throws away. Combined with a tool like yq, this turns dig into a dependable data source for monitoring.

Back to top

7. Something Most Users Do Not Know

7.1 dig ANY Stopped Working, on Purpose

Older tutorials suggest dig example.com ANY to see every record at once. Try it today:

$ dig @8.8.8.8 +noall +answer example.com ANY
example.com.            321     IN      HINFO   "RFC8482" ""

That is not an error and the domain is not broken. RFC 8482, published in 2019, allows a server to answer ANY queries with a single minimal record instead of dumping the zone. Large providers adopted it quickly, because ANY queries produced huge replies from tiny requests, which made them a favourite amplifier for denial-of-service attacks.

The reply above is the polite modern refusal, and the record even names the RFC that explains itself. The practical consequence: to see every record, ask for each type by name, or use a zone transfer if you control the server.

This one detail dates a piece of DNS advice instantly. If a tutorial recommends ANY for an overview, it was written before 2019 and may be out of date in other ways too.

7.2 dig Never Reads /etc/hosts

This surprises people who have been using Linux for years. dig speaks the DNS protocol to a DNS server. It does not use the name service switch, so /etc/hosts, mDNS, LDAP, and every other source your system consults are invisible to it.

Your browser, curl, ping, and every normal application use the full resolution order. dig uses exactly one step of it. This is why a name can work everywhere and still "not exist" according to dig:

$ getent hosts xps           # the operating system's answer
127.0.1.1       xps

$ dig @1.1.1.1 +short xps    # a real DNS server's answer
                             # nothing: this name exists only in /etc/hosts

There is a wrinkle that muddies the water. On systems running systemd-resolved, the stub resolver at 127.0.0.53 reads /etc/hosts itself and answers from it. Because that stub is the default server in /etc/resolv.conf, a plain dig appears to see hosts entries after all. Name any other server with @ and the illusion disappears.

When a name resolves for your application but not for dig, or the other way round, you have not found a DNS bug. You have found the difference between asking the operating system and asking a name server. getent hosts answers the first question; dig answers the second.

7.3 The Exit Code Does Not Mean What You Assume

A shell script naturally tests whether a command succeeded. With dig, that test is almost meaningless:

$ dig this-does-not-exist-pm.nl > /dev/null
$ echo $?
0                              # NXDOMAIN is still a successful query

$ dig dnssec-failed.org > /dev/null
$ echo $?
0                              # so is SERVFAIL

The manual is explicit about it. A zero means "a DNS response was received", not "the name exists":

CodeMeaning
0 A response was received, including NXDOMAIN
1 Usage error
8 Could not open the batch file
9 No reply from the server
10 Internal error

So in a script, test the output or the status, never the exit code alone:

$ if [ -n "$(dig +short example.com A)" ]; then echo "resolves"; fi

$ dig +noall +comments example.com | grep -q "status: NOERROR" && echo ok

The first form has its own edge: +short is empty both for a name that does not exist and for a name that exists without that record type. When the difference matters, read the status.

7.4 dig Ignores the Search List, and Everything Else Uses It

If /etc/resolv.conf contains a search line, then typing ping server1 may successfully reach server1.internal.example.com, because the resolver appends the search domain. dig deliberately does not do this: the manual states that the search list is not used by default.

$ dig +short server1              # asks for "server1" as a top-level name
$ dig +search +short server1      # now the search list is applied

This is a sensible default for a diagnostic tool, since you usually want to test the exact name you typed. It also explains a specific confusion on internal networks: the short name works everywhere except in dig. Add +search, or just type the fully qualified name.

7.5 Borrowing Somebody Else's Cache With a VPN

Everything so far assumes you can pick the resolver with @. That covers DNS, but it does not cover the question a client usually asks after a migration, which is "does the site work for a normal visitor now?" A browser does more than one lookup, and your machine, your router, and your ISP have all been caching the old answer for as long as it suited them.

The trick I use is to turn on a VPN and load the site normally. A VPN does not just move your traffic; it usually hands your machine a different resolver at the other end of the tunnel, in a different network, with a cache that has never been asked about your domain. So you get a genuinely cold lookup and a real page load, in a browser, the way a visitor would experience it. Check what you actually switched to:

$ resolvectl status | grep -A2 'Current DNS Server'
Current DNS Server: 192.168.1.1
       DNS Servers: 192.168.1.1

$ dig +noall +stats petermartin.nl | grep SERVER
;; SERVER: 127.0.0.53#53(127.0.0.53) (UDP)

Run those before and after connecting. If the addresses do not change, the VPN took over your routing but left DNS alone, and you are still reading the same cache you were trying to escape.

This is the practical answer to the router problem in section 5.8. When the box on my own desk stretches a 300 second TTL to 600, no amount of dig on that machine shows me what the rest of the world sees, because the stale copy lives between me and everything else. A VPN steps around it completely.

Two honest limits. A VPN gives you one extra vantage point, not a global view, and its exit is often in a large data centre rather than on a consumer connection like your visitors. And if the VPN client leaves DNS pointed at your local resolver, a configuration people call a DNS leak, you have changed nothing at all. Check the SERVER line before drawing conclusions.

7.6 Knowing Where dig Stops

Part of expertise is knowing which tool takes over. dig answers questions about DNS, and stops there.

NeedUseWhy
How this machine resolves a name getent hosts Uses the real resolution order, including /etc/hosts
Which upstream servers are in use resolvectl status Per-interface DNS servers, DNSSEC mode, and the local cache
Whether DNSSEC really validates delv Performs validation itself and explains the failure
One quick answer, nothing else host Shorter to type when you are not debugging
Whether the service behind the name works curl, openssl s_client A correct DNS answer says nothing about the web server
What is on the wire tcpdump port 53 Shows queries your applications make, which dig cannot
What the world sees A public checking service Queries from many countries at once, which one machine cannot

That last row matters during migrations. Your machine, and the three public resolvers you tested, are still only four vantage points out of thousands.

Back to top

8. Best Practices

  • Always read the SERVER line. Before you conclude anything about a domain, know which server produced the answer you are reading. Most false conclusions start here.
  • Ask twice: a cache and the source. Run the query against a public resolver and against the authoritative name server. If they disagree, you are waiting for a TTL, not fixing a record.
  • Look for the aa flag when verifying a change. Without it you are reading a cached copy, however fresh it looks.
  • Learn +noall +answer and use +short only in scripts. The moment something breaks, you need the status and the flags that +short discards.
  • Check the status word before the answer. NOERROR with zero answers, NXDOMAIN, and SERVFAIL are three different problems that all look like "no result".
  • Set each TTL by what that record has to do. 300 for addresses, 86400 for NS, 3600 for mail and text records. A high TTL buys resilience when your name servers fail; a low one buys the ability to change your mind.
  • Lower the TTL before a migration, not during. Drop it to 300 seconds a day ahead, migrate, then raise it again. This is the single most effective DNS habit there is.
  • Watch the cutover instead of guessing at it. watch -n 5 -d dig +noall +answer example.com shows the TTL falling to zero and the new answer arriving. It turns "has it changed yet?" into something you can see.
  • Know which half you are waiting on. Publishing speed belongs to your DNS provider, cache lifetime belongs to your TTL. Confirm the first with -4 +nssearch before blaming the second.
  • Get off your own network before you declare it broken. A VPN, or a phone on mobile data, gives you a resolver that never cached your old answer. Your router may be holding a record well past its TTL.
  • Use +trace when a domain behaves strangely. Delegation problems are invisible in a normal lookup and obvious in a trace.
  • Check both sets of NS records after any move. Ask the parent zone and ask the zone itself. Resolvers follow the parent, so a mismatch is invisible at your DNS provider and very visible to your visitors.
  • Compare serials with -4 +nssearch when only some visitors have trouble. One lagging secondary produces exactly that symptom.
  • Create a record before anything asks for it. Nothing can cache a negative answer for a name nobody has queried, which sidesteps the negative TTL entirely.
  • Never trust dig's exit code. Test the output or the status word instead, and pass -r in scripts so a personal ~/.digrc cannot change the result.
  • Test your own name servers for open zone transfers. dig @yourserver yourdomain AXFR should fail. If it succeeds, fix it today.
  • Reach for the documentation. The manual page is thorough, and it honestly admits in its own BUGS section that there are probably too many query options.
$ man dig                # the full manual page
$ dig -h                 # every flag and query option, on one screen
$ man resolv.conf        # what dig reads when you do not name a server
$ man delv               # the DNSSEC validating companion
Back to top

9. Common Mistakes

9.1 Common Myths

MythReality
"dig shows how my computer resolves a name." It shows what one DNS server replies. Your applications also use /etc/hosts and other sources that dig skips entirely. Use getent hosts for that question.
"An empty +short means the domain does not exist." It also means SERVFAIL, or a name that exists without that record type. Only the status line distinguishes them.
"dig ANY lists every record." Since RFC 8482, most large providers answer with a single HINFO "RFC8482" record. Ask for each type by name.
"The TTL in the output is the zone's TTL." From a resolver it is the time left in that cache, counting down. Ask the authoritative server for the configured value.
"DNS propagation takes 24 to 48 hours." Nothing propagates. Caches expire, each according to the TTL it was given. A 300 second TTL means five minutes.
"NXDOMAIN is how a missing name always reports." Not on signed zones that use compact denial of existence. A validating resolver reports NOERROR with zero answers instead. Ask the authoritative server without +dnssec to see the real NXDOMAIN.
"Only positive answers get cached." Negative answers are cached too, for the number of seconds in the SOA minimum field. A record you just created can still read as missing.
"The name servers at my DNS provider are the ones the world uses." The world follows the NS records in the parent zone, set at your registrar. The two lists can disagree, and the parent wins.
"nslookup is deprecated, so never use it." ISC reversed that plan in 2004 with BIND 9.3. It is supported. dig is better for debugging because it shows status codes and flags, not because nslookup is going away.
"If dig returns the right IP, the site works." DNS is one step. The web server, the certificate, the firewall, and the virtual host configuration all come after it.
"dig exits with an error when a name does not exist." It exits 0, because a response was received. NXDOMAIN is a successful query with a negative answer.

9.2 Traps to Avoid

  • Forgetting @ after a change. Your resolver holds the old value for the rest of its TTL. Ask the authoritative server before you conclude the change failed.
  • Testing a bare hostname. dig does not apply the search list, so a short internal name that works in a browser returns nothing. Use the full name or +search.
  • Copying a TXT record with its quotes. dig prints SPF and DKIM values in quotes because that is the wire format. Long values arrive as several quoted chunks that a resolver joins together, so do not paste the quotes into a control panel.
  • Reading only the first block of +trace. The interesting failure is usually in the middle, where one level delegates to name servers that do not answer.
  • Putting a CNAME on the zone apex. The standards forbid a CNAME alongside other records, and the apex always has SOA and NS. Providers offer ALIAS or CNAME flattening instead, and dig will show you a plain A record as a result.
  • Blaming the domain for a SERVFAIL. Retry with +cd against a validating resolver such as @1.1.1.1. If the answer appears, it is DNSSEC validation, which means a broken chain of trust and not a missing record.
  • Ignoring split horizon. A company resolver may serve a private answer for the same name a public resolver answers differently. Both are correct; they are different views.
  • Letting ~/.digrc leak into scripts. Your defaults are not on the server. Pass -r for predictable behaviour.
  • Trusting one vantage point during a migration. Check several public resolvers, and remember they are still a tiny sample of the internet.
  • Waiting on IPv6 timeouts. If a command with many name servers hangs, add -4. On a machine without working IPv6 that alone can turn a minute into a second.
Back to top

10. Summary

The dig command has a narrow job and does it completely: one question, one server, the whole answer. That is exactly what DNS troubleshooting requires.

  • dig stands for domain information groper, an expansion that was dropped from the manual page in 2017. Steve Hotz wrote the first version in the late 1980s; it shipped with BIND 4 by 1990 and was rewritten for BIND 9 in 2000.
  • It sends one DNS query to one name server and prints the raw reply. It is read-only, so you can run it freely against any domain.
  • The status: word is the verdict: NOERROR, NXDOMAIN, SERVFAIL, or REFUSED. NOERROR with ANSWER: 0 means the name exists but has no record of that type.
  • The aa flag means the answer came from the authoritative source rather than a cache. Look for it whenever you verify a change.
  • Always read the SERVER line. 127.0.0.53 is your own machine's stub resolver, not the internet.
  • @server chooses who answers. Comparing a public resolver with the authoritative name server solves most DNS mysteries in two commands.
  • +short is for scripts, +noall +answer is for reading. The display options can be combined freely to show exactly the sections you want.
  • A record line is five fields: name, TTL, class, type, data. The class is almost always IN, and the trailing dot marks a fully qualified name.
  • Records sharing a name, class, and type form one RRset. They share a TTL, DNSSEC signs the set rather than each line, and their order carries no meaning.
  • The TTL column counts down inside a cache, which is why the number changes between runs. Lowering the TTL before a migration is the habit that makes cutovers painless.
  • There is no single best TTL. Addresses want 300, NS records want a day, mail and text records want an hour. A high TTL keeps a site resolving when the name servers fail; a low one lets you change your mind quickly.
  • Pair dig with watch to see a cutover happen: watch -n 5 -d dig +noall +answer example.com shows the TTL fall to zero and the new answer replace the old one.
  • A TTL is a request, not a guarantee. Consumer routers and some ISP resolvers hold records longer than the zone asked, so a VPN or another network is the honest way to see what visitors get.
  • Negative answers are cached too. The SOA in the authority section is the signal, and its minimum field is how many seconds the "no" survives.
  • Glue records are addresses the parent zone supplies when a name server lives inside the zone it serves, breaking the circular lookup. No glue appears when the name servers live elsewhere.
  • A domain has two sets of NS records, the parent's and its own. Resolvers follow the parent's, so compare both when a delegation misbehaves.
  • On zones using compact denial of existence, a missing name reports NOERROR rather than NXDOMAIN to any resolver that requests DNSSEC.
  • +trace walks the delegation from the root and exposes problems no ordinary lookup reveals. -4 +nssearch compares SOA serials across every authoritative server.
  • +dnssec shows the signatures and the ad flag; +cd proves whether a SERVFAIL is a validation failure; delv explains why.
  • dig ANY no longer dumps a zone. RFC 8482 lets servers reply with a single HINFO "RFC8482" record instead.
  • dig never reads /etc/hosts and does not apply the search list. getent hosts answers the question "how does this machine resolve the name".
  • The exit code is 0 even for NXDOMAIN. Test the output or the status word in scripts, and add -r so ~/.digrc cannot interfere.
  • When in doubt, run dig -h for the option list, or man dig for the detail.

This is the quick reference worth keeping:

dig example.com                    full answer, with header and flags
dig +short example.com             just the data
dig +noall +answer example.com     the answer section, nicely readable
dig example.com MX                 a specific record type
dig -t MX -q example.com           the same, unambiguous for scripts
dig -x 8.8.8.8                     reverse lookup, address to name

dig @1.1.1.1 example.com           ask a specific resolver
dig +short example.com NS          who is authoritative
dig @ns1.example.com example.com   ask the source, then look for the aa flag
dig +norecurse example.com         is this already in the cache?

dig +trace example.com             follow the delegation from the root
dig @a.gtld-servers.net example.com NS +noall +authority +additional
                                   what the PARENT says, plus any glue
dig @ns1.example.com example.com NS +short
                                   what the ZONE says; the two must agree
dig -4 +nssearch example.com       compare SOA serials on every name server
dig +multiline example.com SOA     the SOA timers, with labels
dig +ttlunits +noall +answer x.com TTLs in minutes and hours
ns=$(dig +short example.com NS | head -1); dig @$ns example.com +noall +answer
                                   the TTL as published, not as cached

watch -n 5 -d dig +noall +answer example.com
                                   watch the TTL count down to the cutover

dig +dnssec @1.1.1.1 example.com   request signatures, check for the ad flag
dig +cd example.com                skip validation, to identify a DNSSEC SERVFAIL
dig @ns1.example.com example.com AXFR    zone transfer; this should fail

dig +tcp example.com               force TCP
dig +time=2 +tries=1 example.com   fail fast, for monitoring
dig -f domains.txt +noall +answer  a list of lookups from a file
dig +yaml example.com              structured output for scripts
dig -r ...                         ignore ~/.digrc

DNS problems have a reputation for being mysterious, but most of that mystery is caching plus a tool that hides the details. Once you read the status line, the flags, and the server line, the answers stop being ambiguous.

A domain that resolves the same way everywhere, delivers its mail, and renews its certificates without anyone thinking about it is not luck: it is DNS set up with intent. DNS is also where a surprising amount of downtime quietly begins: the migration that half the visitors never saw, the mail that stopped arriving after a change nobody wrote down, the certificate that will not renew because of a CAA record set years ago.

None of those announce themselves, and every one of them is visible from the outside with the commands in this article. If your domains carry a delegation nobody has checked since the last move, mail records that were pasted in once and never read again, or a TTL that makes every change an all-day wait, it pays to read the zone from the outside before the next change rather than during it, so the answer is the same wherever in the world someone asks.

Back to top
Linux command: dig
Peter Martin
Peter Martin
Joomla Specialist

Peter is a Joomla specialist and a Linux admin for fast, secure and scalable websites.