
What Is DNS Caching, and How Does It Work?
You update an A record, test the site, and it still loads the old server. A colleague on another network sees the new one. Ten minutes later you see it too, but your phone doesn't. None of this is a bug — it's DNS caching doing exactly what it was designed to do. Caching is the reason DNS is fast enough to sit in front of every web request, and also the reason DNS changes never seem to happen everywhere at once.
This article explains what DNS caching is, the layers where answers get cached, how TTLs control expiry, how resolvers tune caching with techniques like prefetching and serve-stale, and how to inspect each cache so you know which one is holding an old answer. It builds on what a TTL is; here the focus is on the caches themselves.
What Is DNS Caching?
DNS caching is the temporary storage of DNS answers so that repeated lookups for the same name don't have to go back to the authoritative servers. Every DNS record carries a time to live (TTL) set by the zone owner. Any cache that stores the record must discard or refresh it once that many seconds have passed.
Without caching, every page load would trigger a full walk from the root servers down to your domain's nameservers — several round trips across the internet before the browser could even open a connection. With caching, the vast majority of lookups are answered in a millisecond or less from memory somewhere close to the user.
Caching also protects the DNS system itself. Root, TLD, and authoritative servers would collapse under the load if every resolver asked them for google.com every time a user did.
The Layers of DNS Cache
A single lookup can pass through several caches before it ever reaches an authoritative server. The first one with a fresh answer wins.
| Layer | Where it lives | Typical behavior |
|---|---|---|
| Application | Browser (Chrome, Firefox, Edge), runtimes like the JVM | Short-lived internal cache, often capped at a minute or so |
| Operating system | Windows DNS Client, macOS mDNSResponder, Linux systemd-resolved or nscd | Honors TTLs, shared by all apps on the machine |
| Local network | Home router or a forwarder like dnsmasq or Pi-hole | Caches for every device on the LAN |
| Recursive resolver | ISP resolver, public resolvers like 1.1.1.1 or 8.8.8.8 | Large shared cache, serves millions of clients |
| Authoritative server | Your DNS host | Not a cache — the source of truth |
Browser and application caches
Browsers keep their own small DNS cache to avoid even the cost of a system call. You can inspect or clear these caches at chrome://net-internals/#dns in Chrome, about:networking#dns in Firefox, and edge://net-internals/#dns in Edge. Browsers may also resolve names ahead of time — see DNS prefetching — which fills this cache before you click a link.
Some runtimes cache aggressively by default. The Java Virtual Machine, for example, has historically cached successful lookups for a fixed period controlled by the networkaddress.cache.ttl security property, independent of the record's TTL. If a long-running Java service keeps connecting to an old IP after a DNS change, that's the first place to look.
Operating system caches
Your OS runs a stub resolver that caches answers for every application on the machine. You can inspect it directly:
# Windows: list cached entries with their remaining TTL
Get-DnsClientCache | Select-Object Entry, RecordName, Type, TimeToLive, Data
Get-DnsClientCache shows every entry in the Windows DNS Client cache, including how many seconds each one has left. The older ipconfig /displaydns command shows the same data in plain text.
# Linux with systemd-resolved: cache statistics
resolvectl statistics
resolvectl statistics reports the current cache size along with hit and miss counters, which tells you whether the local cache is actually being used. macOS doesn't offer a simple command to list cached entries, but sudo killall -INFO mDNSResponder writes a cache dump to the system log.
Recursive resolver caches
The most important cache is the recursive resolver — your ISP's, your company's, or a public one. It's shared by huge numbers of users, so popular records are almost always in it. It also caches every intermediate step: root NS records, TLD delegations, and your domain's NS set, each with its own TTL. That means a lookup for a brand new subdomain of example.com usually skips the root and TLD entirely and goes straight to your nameservers. The post on the role of a DNS resolver covers the resolver's job more broadly.
How a Cache Uses the TTL
When a resolver fetches a record, it stores it alongside the time it arrived. Each time a client asks for it, the resolver returns the record with a decremented TTL — the original TTL minus the seconds it's been cached. You can watch this happen:
dig +noall +answer example.com A @1.1.1.1
sleep 10
dig +noall +answer example.com A @1.1.1.1
example.com. 187 IN A 104.20.23.154
example.com. 177 IN A 104.20.23.154
The TTL drops by ten between the two queries because both were answered from the same cached copy. When it reaches zero, the next query triggers a fresh lookup from the authoritative servers, and the TTL resets to the full value set in the zone.
Compare that with the authoritative server, which always returns the configured value:
dig +noall +answer +norecurse example.com A @hera.ns.cloudflare.com
If the authoritative TTL is 300 and a resolver returns 187, you know the resolver cached it 113 seconds ago. Large public resolvers run many independent cache nodes behind one anycast address, so consecutive queries can occasionally show different TTLs.
You can read the remaining TTL in code as well. Node.js returns it when you pass the ttl option:
import { Resolver } from "node:dns/promises";
const resolver = new Resolver();
resolver.setServers(["1.1.1.1"]);
const records = await resolver.resolve4("example.com", { ttl: true });
for (const { address, ttl } of records) {
console.log(`${address} expires from this cache in ${ttl}s`);
}
This queries Cloudflare's resolver directly (bypassing the OS cache) and prints each address with its remaining TTL. Save it as ttl.mjs and run it with node ttl.mjs.
What Gets Cached
Caches don't only store successful answers:
- Positive answers — A, AAAA, MX, CNAME, and so on — cached for their TTL.
- Delegations — NS and glue records from parent zones, cached for the parent's TTL.
- Negative answers — "this name doesn't exist" (NXDOMAIN) or "this name exists but has no record of that type" (NODATA), cached for a period derived from the zone's SOA record. This catches people out often enough that it gets its own post: negative caching in DNS.
- Failures — many resolvers briefly cache SERVFAIL results so they don't hammer a broken server.
- DNSSEC records — signatures and keys, so validation doesn't require extra round trips every time.
How Resolvers Tune Their Caches
Resolver operators don't always honor TTLs exactly as published. Common adjustments include:
- Maximum TTL caps. Resolvers often cap TTLs at a day or a week, so a record published with an absurdly long TTL can't linger forever.
- Minimum TTL floors. Some resolvers enforce a minimum of a few seconds to protect themselves from records with a TTL of 0. This is controversial, because it overrides the zone owner's intent.
- Prefetching. When a popular record is close to expiry and someone requests it, the resolver refreshes it in the background so the next client never sees a cache miss.
- Serve-stale (RFC 8767). If the authoritative servers are unreachable when a record expires, the resolver can keep serving the expired answer for a while rather than returning an error. This turns many authoritative outages into non-events for users.
Here's how those settings look in Unbound:
# /etc/unbound/unbound.conf
server:
cache-max-ttl: 86400 # never cache anything longer than one day
cache-min-ttl: 0 # respect the zone owner's TTL
prefetch: yes # refresh popular records before they expire
serve-expired: yes # serve stale data if upstream is down
serve-expired-ttl: 86400 # but only for up to a day past expiry
msg-cache-size: 64m
rrset-cache-size: 128m
These options set an upper bound on cache lifetimes, enable prefetch and serve-stale, and give Unbound more memory for its caches. Unbound's documentation recommends making rrset-cache-size roughly double msg-cache-size. Validate with unbound-checkconf, then reload with unbound-control reload.
BIND has equivalents in its options block — max-cache-ttl, prefetch, and stale-answer-enable — and you can dump its cache to disk with rndc dumpdb -cache.
Caching and DNS Changes
Caching is why DNS changes are never instant. When you change a record, every cache that already holds the old value keeps serving it until its copy expires. The practical consequences:
- The worst case is the old TTL, not the new one. If a record had a 24-hour TTL and you change it, some resolvers will hold the old value for up to 24 hours, even if you set the new TTL to 60 seconds.
- Lower the TTL in advance. Drop it to 300 or less at least one full old-TTL period before a planned change, make the change, then raise it again afterwards.
- Your own machine is just one cache. Seeing the new value locally doesn't mean the world sees it. The post on checking whether DNS changes have propagated covers how to test from multiple vantage points.
If you need to clear a cache you control, see how to flush the DNS cache on Windows, macOS, and Linux and how to clear the DNS cache in Chrome, Firefox, and Edge. Public resolvers can't be flushed from your side, although some providers offer a web form to purge a specific name from their cache.
Caching and Security
Because a cached answer is reused for many users, a forged answer injected into a cache affects everyone who uses that resolver until the TTL expires. That's the core of DNS cache poisoning. Modern resolvers defend against it with source port randomization, query ID randomization, strict checks on which records they'll accept from which server, and DNSSEC validation. Keeping resolver software patched matters, because cache-handling bugs have historically been the root cause of the most serious DNS vulnerabilities.
Choosing TTLs With Caching in Mind
As a zone owner, your TTL is the one lever you have over other people's caches:
- Stable records (MX, NS, records for long-lived infrastructure): 3600 to 86400 seconds. Fewer queries, faster lookups for users, and more resilience if your DNS host has an outage.
- Records you might change quickly (failover targets, load balancer IPs): 60 to 300 seconds.
- Very short TTLs (under 30 seconds) force constant re-resolution, add latency to cold lookups, and aren't reliably honored by every cache. Use them only when you genuinely need fast failover.
DNS Caching FAQ
Each record is cached for its TTL, which the zone owner sets — commonly between 300 seconds and 24 hours. Some caches cap or extend that range according to their own policy.
In several places: the browser, the operating system, home routers or local forwarders, and recursive resolvers run by ISPs or public DNS providers. Each layer has its own copy and its own expiry clock.
The resolver reports how many seconds remain before its cached copy expires. An authoritative server always returns the full configured TTL; a resolver returns the full TTL minus the time it has been cached.
No. You can only lower the TTL ahead of a change so caches expire sooner. Some public resolvers offer a purge tool for a single name, but most caches simply wait for the TTL to run out.
Yes. A cached lookup typically takes under a millisecond locally, compared with tens or hundreds of milliseconds for a full resolution from the root down.
Yes. Flushing only removes stored answers; the next lookup fetches them again. The only cost is a slightly slower first lookup for each name afterwards.
Serve-stale, defined in RFC 8767, lets a resolver keep returning an expired cached answer when it can't reach the authoritative servers. It improves availability during outages at the cost of possibly serving outdated data.
Yes. Nonexistent names and missing record types are cached as negative answers based on the zone's SOA record, and many resolvers briefly cache SERVFAIL results as well.
Conclusion
DNS caching is what makes the system practical: answers are stored in browsers, operating systems, routers, and recursive resolvers so that almost every lookup is served from memory nearby rather than from the other side of the planet. Each cached record lives for its TTL, counting down until it expires and is fetched again. Resolvers refine that behavior with TTL caps, prefetching, and serve-stale to balance speed, freshness, and resilience.
For site owners, the key is to plan around caches instead of fighting them. Pick TTLs that match how often records change, lower them well before planned migrations, and when something looks stale, query the authoritative server and a few resolvers directly to find out which cache is holding the old answer.
Here are some useful references for going deeper on DNS caching:
- RFC 1035: Domain Names - Implementation and Specification — defines the TTL field and how cached records are timed out.
- RFC 8767: Serving Stale Data to Improve DNS Resiliency — the specification for serve-stale behavior.
- Cloudflare Learning Center: What is caching? — an introduction to caching concepts across the web stack.
- NLnet Labs: Unbound Documentation — configuration reference for cache sizing, prefetch, and serve-expired.
- Microsoft Learn: Get-DnsClientCache — reference for inspecting the Windows DNS client cache.


