If you've ever shipped a DNS change and then spent the next 45 minutes refreshing whatsmydns.net to watch the green checkmarks tick across a world map, this post is for you.
whatsmydns is a lovely tool. It is also fundamentally manual. You paste a hostname, click "Search," and eyeball a grid of ~20 resolvers to see which ones have picked up your change. There's no API. There's no programmatic way to run the check on a schedule. There's no way to wire the result into your deploy pipeline so the build halts if propagation hasn't completed.
That gap is what the NexGenData DNS Propagation Checker fills. One API call queries 15 globally distributed, operator-distinct DNS resolvers for a given hostname and record type, returns structured JSON, and detects anomalies like Cloudflare's answer rotation and regional CDN inconsistencies.
This post explains which 15 resolvers it hits, why those specific 15, why checking outside of Google/Cloudflare/Quad9 matters more than most engineers realize, and how to wire the actor into real workflows with cURL and Python.
Why DNS Propagation Is a Real Problem (and Not Just Vibes)
"DNS propagation" is one of those phrases that gets used imprecisely. Technically, DNS doesn't "propagate"—authoritative nameservers answer queries directly, and caching resolvers forget old answers when TTLs expire. There's no update being pushed from a central point.
But the experienced reality is different. When you update an A record, users in different parts of the world see the change at different times because:
- Different recursive resolvers cache independently. Google's 8.8.8.8 in us-east caches separately from Google's 8.8.8.8 in eu-west. Cloudflare's 1.1.1.1 in Asia has its own cache.
- Resolvers honor TTLs unevenly. Some respect your authoritative TTL precisely; others cap at a minimum (typically 60s) or maximum (typically 24h).
- Some networks run their own caching layer on top. ISPs often proxy to a public resolver but cache answers themselves. Corporate VPNs do this too.
- Anycast routing changes which PoP you hit. A user flying from LA to Tokyo might suddenly hit a different Cloudflare PoP that hasn't pulled the new answer yet.
The net effect: a DNS change that looks "propagated" from your laptop can still be serving stale answers to a double-digit percentage of global traffic for hours. If you flipped that DNS record as part of a blue/green deploy, a CDN migration, or a mail server move, that percentage is your users seeing 404s, broken TLS, or email bouncing.
Why 15 Resolvers, and Specifically These 15
You could check 100 resolvers. Or 5. The number 15 is a balance between two realities:
- Most resolvers are downstream of a handful of major upstreams. Thousands of ISP resolvers in the world ultimately proxy to Google, Cloudflare, Quad9, OpenDNS, or Verisign. Checking all of them is redundant.
- A small number of operators run their own distinct infrastructure with their own caches and peering arrangements. If you only check the "big three" (Google, Cloudflare, Quad9), you miss those—and those are where real-world users actually hit propagation gaps.
The 15 resolvers the actor checks are chosen to be operator-distinct —each one is run by a different organization, with its own infrastructure, in a different geography:
| Resolver | Operator | Region | Notes | |---------------------------|-------------------|---------------|-----------------------------------| | 8.8.8.8 | Google | Global anycast| Dominant consumer resolver | | 1.1.1.1 | Cloudflare | Global anycast| Fastest globally on average | | 9.9.9.9 | Quad9 | Global anycast| Swiss-based, filters malware | | 208.67.222.222 | OpenDNS (Cisco) | Global anycast| Enterprise / family filter use | | 64.6.64.6 | Verisign | US | Infrastructure-tier, different peering| | 77.88.8.8 | Yandex | Russia | Critical for RU/CIS user traffic | | 94.140.14.14 | AdGuard | Global | DNS-level ad blocking userbase | | 185.228.168.168 | CleanBrowsing | Global | Family-filter and schools use | | 156.154.70.1 | Neustar/UltraDNS | Global | Enterprise DNS, different cache | | 101.101.101.101 | TWNIC Quad101 | Taiwan / APAC | Regional resolver, distinct cache | | 223.5.5.5 | Alibaba | China / APAC | Major APAC consumer resolver | | 80.80.80.80 | Freenom | Netherlands | European regional | | 216.146.35.35 | Dyn (Oracle) | US | Legacy but still in corporate use | | 8.26.56.26 | Comodo Secure DNS | Global | Security-focused filtering | | 199.85.126.10 | Norton ConnectSafe| US | Consumer security product |
The reasoning for each cluster:
- Global dominants (Google, Cloudflare, Quad9): What the average consumer hits.
- Regional critical (Yandex, Alibaba, TWNIC): Where non-Western user traffic actually resolves. Checking only 1.1.1.1 tells you nothing about what users in Moscow or Shanghai are seeing.
- Enterprise and filtering (OpenDNS, CleanBrowsing, Comodo, Norton, AdGuard): Corporate networks and family-filter setups route through these. A propagation bug here silently breaks a chunk of your B2B customers.
- Infrastructure-tier (Verisign, Neustar, Dyn): Distinct peering and caching; frequently used as upstream by smaller ISPs.
This coverage is empirically enough to catch all the real-world propagation anomalies I've seen in production—without padding the list with 80 resolvers that are really just downstream of these 15.
What Each Query Actually Returns
For each of the 15 resolvers, per record type requested (A, AAAA, CNAME, MX, TXT, NS, CAA, SOA), the actor returns:
- The response: the record(s) returned, or
NXDOMAIN/SERVFAIL - Response time in milliseconds
- TTL (as served by that resolver)
- Whether the answer matches the authoritative answer (pulled separately from the domain's NS records)
The "matches authoritative" bit is the one that matters. A resolver returning your old IP is not propagated. A resolver returning the new IP matches. The actor computes a propagationPct across the 15 resolvers so you get a single number you can assert on in CI.
Related guides on NexGenData
Explore more tools and guides in this category:
- Public Registry Data Tools - full directory - WHOIS, RDAP, DNS, DMARC, RBL, and other open-data registry tooling
- Bulk Domain WHOIS Lookup - API Alternatives to Whois.com (2026) - bulk WHOIS lookup alternatives to Whois.com
- Bulk RBL/DNSBL Check for Sender Reputation - 30 blocklists in one API call
- DMARC/SPF/DKIM Bulk Auditor for $0.005 Per Domain - DMARC/SPF/DKIM auditing for $0.005/domain
Try It: Single Domain, A Record, cURL
curl "https://api.apify.com/v2/acts/ITdMyAckvJED46a8V/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
-X POST \
-H 'Content-Type: application/json' \
-d '{
"domain": "example.com",
"recordTypes": ["A"],
"compareToAuthoritative": true
}'
Response (abridged):
[
{
"domain": "example.com",
"recordType": "A",
"authoritative": {
"nameservers": ["a.iana-servers.net", "b.iana-servers.net"],
"answer": ["23.192.228.80", "23.192.228.84", "23.215.0.136", "23.215.0.138", "96.7.128.175", "96.7.128.198"]
},
"resolvers": [
{
"resolver": "8.8.8.8",
"operator": "Google",
"answer": ["23.192.228.80", "23.192.228.84", "23.215.0.136", "23.215.0.138", "96.7.128.175", "96.7.128.198"],
"ttl": 3600,
"responseTimeMs": 14,
"matchesAuthoritative": true
},
{
"resolver": "1.1.1.1",
"operator": "Cloudflare",
"answer": ["23.192.228.80", "23.192.228.84", "23.215.0.136", "23.215.0.138", "96.7.128.175", "96.7.128.198"],
"ttl": 3596,
"responseTimeMs": 9,
"matchesAuthoritative": true
},
{
"resolver": "77.88.8.8",
"operator": "Yandex",
"answer": ["23.192.228.80", "23.192.228.84", "23.215.0.136", "23.215.0.138", "96.7.128.175", "96.7.128.198"],
"ttl": 3200,
"responseTimeMs": 87,
"matchesAuthoritative": true
}
],
"propagationPct": 100,
"inconsistencies": []
}
]
The inconsistencies array is where interesting things live. On a freshly changed record, you'll see entries like:
"inconsistencies": [
{
"resolver": "156.154.70.1",
"operator": "Neustar/UltraDNS",
"returnedAnswer": ["192.0.2.10"],
"authoritativeAnswer": ["192.0.2.20"],
"issue": "stale_record",
"staleTtl": 1243
}
]
That's telling you: Neustar hasn't picked up your change yet, and has 1,243 seconds of stale TTL left.
Bulk Mode: Assert Propagation in a Deploy Pipeline
The pattern I use most: check propagation after a DNS change as part of a deploy pipeline, and fail the deploy if propagation is below a threshold. Python:
import os
import sys
from apify_client import ApifyClient
client = ApifyClient(os.environ["APIFY_TOKEN"])
DOMAIN = "api.example.com"
THRESHOLD_PCT = 90 # require 90% of resolvers to be current
run = client.actor("nexgendata/dns-propagation-checker").call(run_input={
"domain": DOMAIN,
"recordTypes": ["A", "AAAA"],
"compareToAuthoritative": True,
})
results = list(client.dataset(run["defaultDatasetId"]).iterate_items())
failed = False
for r in results:
pct = r["propagationPct"]
rec = r["recordType"]
print(f"{rec}: {pct}% of 15 resolvers current")
if pct < THRESHOLD_PCT:
failed = True
for inc in r["inconsistencies"]:
print(f" Stale on {inc['operator']} ({inc['resolver']}): "
f"{inc['returnedAnswer']} vs authoritative {inc['authoritativeAnswer']}")
if failed:
print(f"\nPropagation below {THRESHOLD_PCT}% threshold. Halting deploy.")
sys.exit(1)
print("\nPropagation OK. Proceeding.")
Drop that into a GitHub Actions step after your DNS-updating job and it'll halt the deploy until the new record has reached the threshold globally. This is a real thing you want in CI for blue/green deployments where the switchover is DNS-based.
Cloudflare Answer Rotation (and Why Your Checks Lie Without It)
A thing that trips people up: Cloudflare's A records for proxied hostnames return different IPs each query, because the Cloudflare edge picks one of a pool. If you naively compare the answer from resolver A to the answer from resolver B, they'll disagree even though both are fully propagated.
The actor handles this. When it detects that the authoritative answer comes from a Cloudflare nameserver (ns[1-6].cloudflare.com) and the returned IPs are within Cloudflare's published ranges, it relaxes the match criterion to "is this IP in the expected Cloudflare ranges?" rather than "exact match to authoritative."
The same kind of logic applies to:
- Anycast CDN records (Fastly, Akamai, CloudFront) that rotate per-query
- DNS-based load balancing (AWS Route 53 weighted/latency records)
- GeoDNS (different answers by client subnet)
You can toggle this with strictMatch: true if you want exact-string comparison and don't care about the rotation semantics.
JavaScript: On-Demand Check for a Web Dashboard
If you're exposing a "did my DNS change go through" tool inside an internal dashboard:
async function checkPropagation(domain, recordType = "A") {
const res = await fetch(
`https://api.apify.com/v2/acts/ITdMyAckvJED46a8V/run-sync-get-dataset-items?token=${process.env.APIFY_TOKEN}`,
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
domain,
recordTypes: [recordType],
compareToAuthoritative: true,
}),
}
);
const [result] = await res.json();
return {
pct: result.propagationPct,
current: result.resolvers.filter(r => r.matchesAuthoritative).length,
total: result.resolvers.length,
stale: result.inconsistencies.map(inc => ({
operator: inc.operator,
ttlRemaining: inc.staleTtl,
})),
};
}
// Usage
const status = await checkPropagation("api.example.com", "A");
console.log(`${status.current}/${status.total} resolvers current (${status.pct}%)`);
if (status.stale.length) {
console.log("Stale on:");
status.stale.forEach(s => console.log(` ${s.operator} (TTL ${s.ttlRemaining}s remaining)`));
}
Wire that into a dashboard widget and you've replaced whatsmydns.net with something scriptable.
Real Use Cases I Use This For
1. Blue/Green Deploy Gating
A company I work with does traffic cutover via DNS—old environment on old IPs, new environment on new IPs, flip the A record when ready. Without a propagation check, the "flip" can leave 10% of users on the old environment for hours. With the check running in the deploy pipeline, the pipeline waits until ≥95% propagation before marking the deploy complete.
2. Email Migration Verification
Moving MX records from an old mail host to a new one is a classic "one-resolver-is-stale" story. A 5% stale resolver share means 5% of inbound mail goes to the old host and bounces. The actor's MX record check flags this across all 15 resolvers.
3. Post-Incident DNS Forensics
When something breaks and the incident postmortem involves "was it DNS," the actor's historical results (the actor writes each run to a persistent dataset) let you see the propagation state at the moment of the incident rather than trying to reconstruct it from memory.
4. Geographic Routing Sanity Checks
Launching in a new region (APAC, EMEA) and want to verify your GeoDNS is routing correctly? Check the same domain from Yandex, Alibaba, TWNIC, and Comodo and look at the IPs returned. If all 15 return the same single IP, your GeoDNS isn't working. If regional resolvers return different IPs, it is.
5. Cloudflare / Provider Outage Detection
When Cloudflare's control plane hiccups (it happens), authoritative answers for proxied records go weird. Running a propagation check across 15 resolvers surfaces this as "Cloudflare nameservers returning different answers than other NS" faster than waiting for a status page update.
How Does This Compare to the Alternatives?
| Tool | API | Bulk | Resolvers checked | Notes | |--------------------------|-----|------|-------------------|------------------------------------| | whatsmydns.net | No | No | ~20 | Manual, UI-only | | dig (one resolver at a time) | N/A | No | 1 | The "DIY" baseline | | DNSChecker.org | No | No | ~30 | UI-only, similar to whatsmydns | | Pingdom DNS check | Yes | Yes | Varies | Part of $15/mo+ monitoring plans | | UptimeRobot | Yes | Yes | Limited | Per-resolver check requires Pro | | NexGenData Propagation Checker | Yes | Yes | 15 distinct operators | ~$0.003 per domain |
For the use cases above (CI gating, scheduled checks, dashboards), the API-accessible version wins by default; the only question is which API-accessible version, and the per-domain price + operator-distinctness of this actor makes it the one I actually use.
Answering the Common Questions
Q: Is 15 resolvers really enough? A: For practical propagation confidence, yes. I tested the actor against 50+ resolvers in development; the marginal information above 15 was essentially zero because the extras were all downstream of the 15 already covered. The operator-distinctness matters more than raw count.
Q: Can I add my own resolvers? A: Yes. Pass customResolvers: ["1.0.0.1", "my-corporate-resolver.example.com"] in the input and the actor will query those in addition to the built-in 15.
Q: Does it support DNS-over-HTTPS (DoH)? A: Yes, optionally. useDoh: true will query the resolvers over DoH instead of plain UDP DNS. Slower (a few hundred ms per query) but useful if your egress environment blocks UDP/53.
Q: What about DNSSEC? A: The actor reports whether each resolver validates DNSSEC for the response (AD flag set in the response). It doesn't do independent DNSSEC validation; for that, use a tool like delv or dnsviz.net.
Q: Can it check a single record type only (like just MX)? A: Yes. recordTypes: ["MX"]. You can also pass multiple types in one run and get all results back.
Q: How does pricing work? A: Roughly $0.003 per domain checked, per record type. A single A-record check across 15 resolvers is about $0.003; an A+AAAA+MX+TXT check is four times that. Bulk discounts apply above 1,000 checks per run.
Related NexGenData Actors for a Full DNS/Deliverability Stack
- Email DMARC Auditor — When you're deploying SPF/DMARC/DKIM changes, pair with propagation checks to confirm both that the record exists and that all resolvers see it.
- Email RBL Checker — After an IP migration, verify both DNS propagation and that your new IP isn't on any blocklists before cutting over email traffic.
- Google Cache Viewer — Historical view of a site's content when propagation issues make current DNS unreliable.
- Website Performance Monitor — Pair with DNS propagation checks to catch "DNS is propagated but origin is slow" vs "DNS is stale" as distinct classes of issue.
Try It
Single domain, A record, right now:
curl "https://api.apify.com/v2/acts/ITdMyAckvJED46a8V/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
-X POST \
-H 'Content-Type: application/json' \
-d '{"domain": "yourdomain.com", "recordTypes": ["A"]}'
Or from the UI: apify.com/nexgendata/dns-propagation-checker.
Apify's free tier includes $5 of credits. That's enough for roughly 1,500 A-record propagation checks, or a few hundred multi-record checks.
Want more tools like this? I'm a solo dev shipping Apify actors that plug the gaps between "free but manual" and "$150/mo SaaS"—subscribe to the newsletter for new releases.
Build your own actors : sign up at apify.com and ship alongside mine.
Resources:
Top comments (0)