Linux command: dig
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:
digdoes 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 top2. 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.
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.
| Year | Milestone |
|---|---|
| 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.
| Command | What 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.
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.
| Block | What 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
| Field | Example | Meaning |
|---|---|---|
| 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.
| Status | Meaning | What 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.
| Flag | Stands for | Why 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.
Back to topNinety 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.
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:
| Option | Controls |
|---|---|
+[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.
| Type | Purpose |
|---|---|
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
Arecords still shows only oneRRSIG Aalongside 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
MXlist 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:
| Domain | A | NS | MX | TXT |
|---|---|---|---|---|
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:
| Record | TTL | Reasoning |
|---|---|---|
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 top6. 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
digis 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.
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 hostsanswers the first question;diganswers 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":
| Code | Meaning |
|---|---|
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.
| Need | Use | Why |
|---|---|---|
| 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 top8. Best Practices
- Always read the
SERVERline. 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
aaflag when verifying a change. Without it you are reading a cached copy, however fresh it looks. - Learn
+noall +answerand use+shortonly in scripts. The moment something breaks, you need the status and the flags that+shortdiscards. - Check the status word before the answer.
NOERRORwith zero answers,NXDOMAIN, andSERVFAILare 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.comshows 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 +nssearchbefore 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
+tracewhen a domain behaves strangely. Delegation problems are invisible in a normal lookup and obvious in a trace. - Check both sets of
NSrecords 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 +nssearchwhen 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-rin scripts so a personal~/.digrccannot change the result. - Test your own name servers for open zone transfers.
dig @yourserver yourdomain AXFRshould 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 top9. Common Mistakes
9.1 Common Myths
| Myth | Reality |
|---|---|
"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.
digdoes not apply thesearchlist, so a short internal name that works in a browser returns nothing. Use the full name or+search. - Copying a
TXTrecord with its quotes.digprints 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
CNAMEon the zone apex. The standards forbid aCNAMEalongside other records, and the apex always hasSOAandNS. Providers offerALIASorCNAMEflattening instead, anddigwill show you a plainArecord as a result. - Blaming the domain for a
SERVFAIL. Retry with+cdagainst 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
~/.digrcleak into scripts. Your defaults are not on the server. Pass-rfor 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.
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.
digstands 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, orREFUSED.NOERRORwithANSWER: 0means the name exists but has no record of that type. - The
aaflag means the answer came from the authoritative source rather than a cache. Look for it whenever you verify a change. - Always read the
SERVERline.127.0.0.53is your own machine's stub resolver, not the internet. @serverchooses who answers. Comparing a public resolver with the authoritative name server solves most DNS mysteries in two commands.+shortis for scripts,+noall +answeris 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,
NSrecords 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
digwithwatchto see a cutover happen:watch -n 5 -d dig +noall +answer example.comshows 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
SOAin the authority section is the signal, and itsminimumfield 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
NSrecords, 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
NOERRORrather thanNXDOMAINto any resolver that requests DNSSEC. +tracewalks the delegation from the root and exposes problems no ordinary lookup reveals.-4 +nssearchcomparesSOAserials across every authoritative server.+dnssecshows the signatures and theadflag;+cdproves whether aSERVFAILis a validation failure;delvexplains why.dig ANYno longer dumps a zone. RFC 8482 lets servers reply with a singleHINFO "RFC8482"record instead.dignever reads/etc/hostsand does not apply thesearchlist.getent hostsanswers the question "how does this machine resolve the name".- The exit code is
0even forNXDOMAIN. Test the output or the status word in scripts, and add-rso~/.digrccannot interfere. - When in doubt, run
dig -hfor the option list, orman digfor 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

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












