Propagation & Caching Basics

How DNS changes ripple across the global resolver hierarchy, how every layer caches independently, and how to drive zero-downtime record changes through the resulting cache lifecycles.

  • Map the recursive resolver chain and authoritative handshakes so you can predict, not guess, how long a change takes to become visible.
  • Align TTL values with the actual cache expiration behavior of ISP resolvers, enterprise forwarders, and CDN PoPs — not the value you typed in the zone.
  • Stage rollouts with pre-lowered TTLs and dual-stack origins to eliminate split-brain routing during cutovers.
  • Treat DNS TTL and CDN Cache-Control as two distinct caching layers, and synchronize them with purge APIs during migrations.

This guide sits under DNS Fundamentals & Advanced Record Configuration and assumes you already know what an A, CNAME, or NS record is — see Understanding DNS Record Types if not. Here the focus is the time dimension: what happens between the moment you write a record and the moment every client in the world sees it.

The two independent caching planes: DNS resolution and CDN HTTP A client query flows through stub resolver, recursive resolver, root, TLD, and authoritative servers. Each DNS layer caches by TTL. A separate CDN edge cache stores HTTP responses by Cache-Control and is invalidated by purge, not by DNS TTL. Two independent caching planes DNS resolution plane (cached by TTL) Stub resolver OS cache Recursive 8.8.8.8 / 1.1.1.1 Root . servers TLD (.com NS) Authoritative NS source of truth TTL countdown per layer HTTP plane (cached by Cache-Control, purge-invalidated) Browser max-age CDN edge PoP s-maxage Origin DNS TTL does NOT expire HTTP cache → use purge API A change to the origin IP is invisible until both planes turn over

The mechanics of DNS propagation

“Propagation” is a misnomer. Nothing is pushed. Authoritative servers hold the source of truth and never broadcast a change. Instead, recursive resolvers pull records on demand and hold the answer until its TTL expires. A DNS change becomes globally visible only as each independent cache, on its own schedule, lets the old answer age out and fetches the new one. There is no central event; there is only a fleet of timers counting down at different rates.

A cold query walks the delegation chain top-down: the stub resolver on the client asks a recursive resolver, which (if it holds nothing cached) asks a root server for the TLD nameservers, asks the TLD for your domain’s authoritative nameservers, and finally asks an authoritative server for the record. Each hop is cached separately and with its own TTL — the NS delegation, the glue, and the leaf record all expire on different clocks. This is why a nameserver change (slow, governed by the parent zone’s NS TTL) behaves very differently from an A-record change (governed by the leaf TTL you control).

Negative caching matters as much as positive caching

Newly created records have a hidden enemy: negative caching. When a resolver queries a name that does not yet exist, the authoritative server returns NXDOMAIN, and per RFC 2308 the resolver caches that absence for the lesser of the SOA MINIMUM field and the SOA record’s own TTL. If your SOA MINIMUM is 3600, a subdomain you create now may keep returning NXDOMAIN for an hour to every resolver that happened to look it up beforehand. The fix is structural: keep the SOA MINIMUM low (300–900s) before you start adding records, or pre-create placeholder records.

yourdomain.com. 900 IN SOA ns1.provider.com. hostmaster.yourdomain.com. (
        2026062001  ; serial
        7200        ; refresh
        900         ; retry
        1209600     ; expire
        300 )       ; minimum  <- negative-cache TTL, keep it small

Resolver behavior is not uniform

Platform quirks change the effective propagation timeline more than any value in your zone file:

  • Cloudflare 1.1.1.1 honors authoritative TTLs closely and caps absurdly large TTLs, so it tends to converge fast and predictably.
  • Google Public DNS (8.8.8.8) runs a large anycast fleet with internal prefetching; different PoPs hold different cache ages, so two users in different cities can see the change minutes apart.
  • AWS Route 53 Resolver caches at the VPC level, and its default cache can mask a change from EC2 instances even after public resolvers have updated.
  • ISP and enterprise forwarders are the worst offenders: many clamp a minimum cache time (commonly 1800–3600s) and ignore low TTLs entirely.

Always verify against the resolver infrastructure your users actually use, not just 1.1.1.1.

Diagnostic commands:

# Trace the full delegation chain from root to authoritative NS
dig +trace yourdomain.com A

# Show the answer with its current cached TTL from a specific resolver
dig @8.8.8.8 yourdomain.com A +noall +answer

# Inspect the local systemd-resolved cache state
resolvectl status
resolvectl statistics   # cache hits/misses

Expected output: dig +trace returns iterative IN NS referrals ending at the authoritative server and the final A record. The single-resolver query shows a TTL that decreases on repeat queries — that countdown is your propagation odometer. resolvectl statistics reveals whether your own stub is serving stale answers.

TTL-driven cache lifecycles and edge routing

The TTL you set is a ceiling on how long a compliant resolver may cache an answer — never a floor and never a guarantee. The practical migration lever is to lower the TTL well before the change, wait at least one full old TTL so the low value itself propagates, then make the real change. See Mastering TTL Strategies for choosing values by record type and traffic profile.

The trap is sequencing. If today’s TTL is 86400 and you lower it to 300 then immediately change the IP, resolvers that cached the old record an hour ago will keep serving the old IP with the old 86400 TTL for up to a day — your 300 never reached them. You must let the TTL reduction itself age out first.

CDN edge caches are a second, independent plane. A CDN caches HTTP responses keyed by URL and Cache-Control, completely separate from how long resolvers cache the CDN’s IP. Lowering a DNS TTL does nothing to evict a stale object from an edge PoP. During an origin migration you have to drive both planes — DNS for where requests land and HTTP cache for what content they get.

Verification command:

# Inspect CDN cache headers and object age
curl -s -o /dev/null -D - https://cdn.yourdomain.com/asset.js \
  | grep -iE 'cache-control|age|cf-cache-status|x-cache'

Expected output:

cache-control: public, max-age=3600, s-maxage=86400
age: 1245
cf-cache-status: HIT

s-maxage controls the shared CDN cache duration (overriding max-age for shared caches); age is seconds since the edge fetched the object. If age exceeds the value you expect after an update, the edge is serving stale content and you need a purge — the DNS layer cannot help you here.

Provider-specific implementation

The cache lifecycle is universal; the levers differ per provider. Below, the same two operations — lower a DNS TTL, then invalidate the HTTP plane — across the major stacks.

Cloudflare

Cloudflare proxied (orange-cloud) records ignore your TTL on the wire and serve a fixed short TTL, because Cloudflare is the resolver-facing edge. Set TTL via the API on grey-cloud records, and use the zone purge API for the HTTP plane.

# Lower TTL on a DNS-only (grey-cloud) record
curl -s -X PATCH \
  "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records/$RECORD_ID" \
  -H "Authorization: Bearer $CF_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"ttl":300}'

# Purge the HTTP cache for specific files (preferred over purge_everything)
curl -s -X POST \
  "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/purge_cache" \
  -H "Authorization: Bearer $CF_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"files":["https://cdn.yourdomain.com/asset.js"]}'

AWS (Route 53 + CloudFront)

# Lower the TTL on the A record before migration
aws route53 change-resource-record-sets \
  --hosted-zone-id Z1EXAMPLE \
  --change-batch '{
    "Changes": [{
      "Action": "UPSERT",
      "ResourceRecordSet": {
        "Name": "yourdomain.com",
        "Type": "A",
        "TTL": 300,
        "ResourceRecords": [{"Value": "203.0.113.10"}]
      }
    }]
  }'

# Invalidate the CloudFront HTTP cache
aws cloudfront create-invalidation \
  --distribution-id E1234567890 \
  --paths '/asset.js' '/index.html'

CloudFront invalidations are eventually consistent (typically under a minute) and the first 1,000 paths/month are free — prefer specific paths over /* to avoid cost and to keep unaffected objects warm.

Azure, GCP, and Fastly

# Azure DNS: set TTL on a record set
az network dns record-set a update \
  --resource-group dns-rg --zone-name yourdomain.com \
  --name www --set ttl=300

# Azure Front Door / CDN: purge
az afd endpoint purge --resource-group dns-rg \
  --profile-name fd-profile --endpoint-name web \
  --content-paths '/asset.js'

# GCP Cloud DNS: lower TTL via a transaction
gcloud dns record-sets update www.yourdomain.com. \
  --type=A --zone=my-zone --ttl=300 --rrdatas=203.0.113.10

# Fastly: instant purge of a single URL (sub-second, no cost)
curl -s -X PURGE "https://cdn.yourdomain.com/asset.js" \
  -H "Fastly-Key: $FASTLY_TOKEN"

Fastly’s instant purge is genuinely instant (single-digit milliseconds globally), which changes migration strategy: you can rely on purge as a safety net rather than pre-warming, where on CloudFront you cannot.

Platform comparison

Provider DNS TTL lever HTTP-plane invalidation Wire behavior & propagation notes
Cloudflare API ttl on grey-cloud; fixed short TTL when proxied purge_cache (files / tags / everything) Proxied records hide your TTL; resolver-side convergence is fast and predictable
AWS Route 53 + CloudFront change-resource-record-sets TTL create-invalidation (eventually consistent, ~1 min) VPC resolver caches separately; first 1,000 invalidation paths/mo free
Azure DNS + Front Door record-set ... --set ttl afd endpoint purge Front Door also caches at POPs; honor s-maxage from origin
GCP Cloud DNS + Cloud CDN transaction / record-sets update --ttl gcloud compute ... invalidate-cdn-cache Anycast NS; negative caching governed by SOA
Fastly upstream DNS provider (Fastly is HTTP-only) PURGE instant (sub-second, free) Surrogate-Key purge enables content-tag invalidation without URL lists

Operational procedure: a zero-downtime origin change

  1. Audit the current TTL. dig @ns1.provider.com yourdomain.com SOA +noall +answer and the leaf record’s TTL. Note the largest TTL anywhere in the chain — that is your worst-case wait.
  2. Lower the leaf TTL to 300s. Apply via the provider command above. Do this before anything else.
  3. Wait at least one full old TTL. If the old TTL was 86400, wait a day so the 300s value actually reaches resolvers. Skipping this step is the single most common cause of “propagation took forever.”
  4. Stand up the new origin in parallel and verify it independently (direct IP curl, health checks green) before any DNS points to it.
  5. Cut over the record to the new IP. Because TTL is now 300s, most resolvers turn over within five minutes.
  6. Purge the CDN HTTP cache for affected paths so the edge fetches from the new origin. Remember: the DNS change alone will not evict cached objects.
  7. Watch convergence across multiple resolvers (see below) until all return the new IP.
  8. Restore a sane production TTL (e.g. 3600s) once you are confident, to reduce query load and cost.
  9. Keep the old origin alive for at least one TTL window past full convergence to catch laggard ISP caches, then decommission.

Live monitoring command:

# Poll two major resolvers every 2 seconds to track convergence
watch -n 2 'dig @8.8.8.8 yourdomain.com A +short; echo ---; dig @1.1.1.1 yourdomain.com A +short'

Expected output: alternating old/new IPs until both resolvers consistently return the new authoritative IP. Query latency drops from ~45 ms (recursive walk) to ~2 ms (cache hit) once the answer is cached.

TTL, caching, and propagation implications

Three numbers govern everything and people routinely confuse them. The leaf TTL controls how long resolvers serve a positive answer for www. The SOA MINIMUM controls negative caching for names that do not exist. The HTTP s-maxage/max-age controls how long the CDN and browser serve a response body. A migration touches all three planes, and a change to one does not move the others.

Anycast routing further blurs perceived propagation: the resolver you reach in Frankfurt and the one a user reaches in São Paulo are different physical caches at different ages, so “it’s updated for me” tells you nothing about global state. Stale-content resilience patterns interact here too — if your origin returns stale-while-revalidate, the edge will serve old bytes and refresh in the background, which is desirable for availability but extends the visible tail of a change. See Stale-While-Revalidate & Resilient Caching for tuning that tail deliberately.

For bulk or coordinated record changes — moving an entire zone, or swapping providers — propagation is dominated by the parent NS TTL rather than your leaf TTLs, which is a different and slower problem covered in DNS Zone Management.

Troubleshooting and rollback

Direct recursive queries isolate true propagation state far better than web-based “global DNS checkers,” which aggregate possibly-stale third-party caches and lie to you. Track the TTL countdown across repeated queries to confirm a resolver is honoring TTL rather than re-fetching.

# Confirm a resolver is caching and counting down (not re-querying)
dig @1.1.1.1 yourdomain.com +noall +answer
sleep 5
dig @1.1.1.1 yourdomain.com +noall +answer

If the second query’s TTL is exactly 5 lower, the resolver is caching correctly. If both show the same (high) TTL, you are hitting a fresh authoritative lookup each time — usually a sign of a near-zero TTL. If the TTL is higher than you set, an upstream forwarder is clamping a minimum.

Rollback procedure. If the new origin fails health checks after cutover: (1) revert the leaf record to the legacy IP via the same provider command; (2) immediately purge the CDN again so edges stop pulling from the broken origin; (3) because you pre-lowered TTL to 300s, recovery is bounded to ~5 minutes for compliant resolvers. This bounded blast radius is the entire reason for step 2 of the procedure — without the pre-lowered TTL, a bad cutover is a multi-hour outage.

If the symptom is DNSSEC-related rather than plain caching — answers go SERVFAIL on validating resolvers but resolve on +cd (checking-disabled) queries — the problem is signature/chain validation, not propagation. See Debugging DNSSEC Validation Failures. For deeper, scripted multi-resolver convergence diagnostics, see Debugging DNS Propagation Delays Across Global Resolvers.

Edge cases and gotchas

  • ISP TTL clamping. Many ISP resolvers enforce a minimum cache time and ignore low TTLs entirely — verify across several real-world resolvers, not just 1.1.1.1, before declaring a cutover complete.
  • The CDN pulls from origin DNS too. Some CDNs resolve your origin hostname and cache that IP on their own interval; a CDN may keep hitting the old origin even after public DNS updates. Use the CDN’s origin-DNS-refresh setting or an explicit origin IP.
  • NXDOMAIN poisoning of new names. Anything that queried a not-yet-existing subdomain pins NXDOMAIN for the SOA MINIMUM window — pre-create placeholders or keep MINIMUM low.
  • CNAME-chain TTLs. The visible TTL of a name reached through CNAMEs is the minimum across the chain; lowering only the alias does nothing if its target still has a high TTL.
  • Browser DNS cache. Chrome and Firefox keep their own short in-process DNS cache (often 60s) on top of the OS stub — a single browser “not updating” is rarely a server-side problem.
  • Mixed proxied/unproxied at one name. On Cloudflare, flipping the orange cloud changes the effective TTL and the served IP simultaneously; account for both when reasoning about timing.

Frequently Asked Questions

Why does DNS propagation sometimes take up to 48 hours? Because resolvers that cached the record before you lowered the TTL keep serving it for the full old TTL — if that was 86400 and you skipped the pre-lowering wait, the worst case is roughly a day. Some ISP resolvers also clamp a minimum cache time regardless of your TTL. With a properly pre-propagated 300s TTL, compliant resolvers converge within minutes.

Why is my site updated for me but not for a colleague? You are hitting different physical caches. Anycast routes each of you to a different resolver PoP, each at a different cache age, and your browser and OS hold their own short caches on top. “Updated for me” only describes one path through the hierarchy, never global state.

Does changing a CNAME affect cached A records? No. Resolvers cache the final resolved IP from the CNAME chain, governed by the minimum TTL across that chain. To speed up visibility, lower the TTL on the chain’s ultimate A/AAAA target, not just on the CNAME, and purge the CDN if HTTP content is what actually changed.

Why did my CDN keep serving old content after I changed the origin IP? DNS TTL and CDN Cache-Control are independent planes. The DNS change only affects where new requests land; cached objects already at the edge keep serving until their s-maxage expires or you purge. Always pair an origin cutover with a targeted CDN purge.

Back to DNS Fundamentals & Advanced Record Configuration