DNS Cheatsheet

TTL and Caching

Use this DNS reference while you build software engineering projects, review code, or refresh the syntax you reach for most.

What a TTL actually controls

The TTL on a record is how many seconds a resolver may keep serving that answer from cache without re-asking. It is a maximum, not a guarantee.

example.com.  300  IN  A  93.184.215.14
              ^^^ this record may be cached for up to 300 seconds

The counter starts when the resolver receives the answer, not when you publish it. So the real worst case for a change is the old TTL, from the moment the last resolver cached the old value.

# watch the TTL count down against a caching resolver
dig @8.8.8.8 example.com A +noall +answer
dig @8.8.8.8 example.com A +noall +answer   # same value, lower TTL

# a query straight to the authoritative server always shows the full TTL
dig @ns1.example.com example.com A +noall +answer +norecurse

A decreasing TTL on repeat queries proves you are reading a cache. A constant TTL means you reached the authority.

Picking a TTL

TTLValueFits
601 minFailover targets, records mid-migration
3005 minA sane default for anything that might change
36001 hourNormal web and app records
144004 hoursStable records, less resolver traffic
864001 dayMX, NS, TXT policy records, apex of a static site
1728002 daysDelegation NS at the TLD, set by the registry

Low TTLs cost query volume and add a lookup to every cold request. High TTLs cost you flexibility. 300 to 3600 covers almost everything.

The migration playbook

This is the whole reason TTLs get attention.

1. At least 48 hours before the change, lower the TTL on the records you will move. Publish api IN 300 A <old-ip>. 2. Wait out the OLD TTL. If it was 86400, every resolver that cached the old record before your edit keeps the 86400 value until it expires. Lowering the TTL does not shorten the copy already in a cache. 3. Verify the low TTL is live everywhere you care about, not just your own resolver. 4. Make the change. Point the record at the new address. 5. Keep the old endpoint serving for at least the low TTL, doubled. Stragglers exist: resolvers that ignore TTLs, clients that cached at the application layer, and JVMs holding a name forever. 6. Raise the TTL back once traffic to the old endpoint is zero.

# step 3, check the low TTL from several resolvers
for r in 8.8.8.8 1.1.1.1 9.9.9.9 208.67.222.222; do
  printf '%-16s ' "$r"
  dig @$r api.example.com A +noall +answer | head -1
done

# step 5, confirm the new value is what resolvers now hand out
dig @1.1.1.1 api.example.com +short

Lowering the TTL after you need it is useless. The step people skip is step 1, and then they spend a day wondering why traffic still lands on the old box.

Negative caching

A "does not exist" answer is cached too, and its lifetime comes from the zone's SOA MINIMUM field, capped by the SOA record's own TTL.

$ dig doesnotexist.example.com A

;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 5512
;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 1

;; AUTHORITY SECTION:
example.com. 300 IN SOA ns1.example.com. hostmaster.example.com.
                        2026091501 7200 3600 1209600 300

The 300 at the end of the SOA RDATA is the negative TTL. This NXDOMAIN is cached for 300 seconds.

AnswerCached for
Positive (records returned)The record's own TTL
NXDOMAIN (name absent)SOA MINIMUM, capped by the SOA TTL
NODATA (NOERROR, no answers)SOA MINIMUM, same rule
SERVFAILImplementation defined, usually 1 to 5 minutes

Set SOA MINIMUM to 300 or lower. A common failure is publishing a new record right after someone queried the name and got NXDOMAIN, then waiting out a one-day negative TTL. Nothing in the output tells you that is what happened, beyond the SOA in the authority section.

Where else things cache

DNS TTLs only bind resolvers. Four other layers cache independently.

LayerHonors TTLHow to clear
Recursive resolverYesWait, or ask the operator. Some expose a purge form
OS stub cacheMostlysudo resolvectl flush-caches (systemd), sudo dscacheutil -flushcache; sudo killall -HUP mDNSResponder (macOS), ipconfig /flushdns (Windows)
BrowserPartlychrome://net-internals/#dns then Clear host cache. Chrome caps at about 60s
Java / JVMNo, forever by defaultnetworkaddress.cache.ttl=30 in java.security, or -Dsun.net.inetaddr.ttl=30
Connection pools, app-levelNoRestart, or use a client that re-resolves
# Google and Cloudflare purge forms, useful mid-incident
# https://dns.google/cache
# https://one.one.one.one/purge-cache/

# check what a specific resolver holds without recursing
dig @8.8.8.8 example.com A +norecurse +noall +answer

The JVM one bites hard. A default JVM with a security manager caches a resolved address for the life of the process, so a failover that DNS handled perfectly still sends every Java client to the dead box until it restarts.

Gotchas

- A CNAME chain caches per hop. The final address lives for the target's TTL, not the alias's, so a 60-second CNAME to a 3600-second A record still pins the address for an hour. - dig +short hides the TTL. Use +noall +answer when the TTL is the thing you are checking. - Resolvers may clamp your TTL. Many enforce a floor (often 30 to 60 seconds) and some enforce a ceiling, so a TTL of 1 does not mean per-request lookups. - dig never reads the OS cache, so flushing the OS cache changes nothing about dig output and dig proves nothing about what your app sees. - Registry-level NS TTLs are not yours. The TLD sets the delegation TTL, commonly 172800, so changing nameservers is a two-day operation regardless of what your zone says. - Anycast resolvers have independent caches per site. Repeating a query against 8.8.8.8 from two locations can show two different TTLs and two different answers.