How to Check DNS Propagation
Learn to check DNS propagation with dig, nslookup, and Node.js. Understand TTL, caching, and why DNS changes take time to go live worldwide.
Check DNS Propagation
You just updated your DNS records. The registrar says the change is saved. But when you visit the domain, it still points to the old server. You clear your browser cache, flush your local DNS, try a different network — still the old IP. This is DNS propagation, and it is not broken. It is working exactly as designed. DNS changes take time to ripple through the global network of recursive resolvers because those resolvers cache records according to TTL values. This guide explains why propagation takes the time it does, how to check progress from the command line, and how to programmatically verify that your changes have reached every region.
Why DNS Propagation Takes Time
DNS is not a single database. It is a distributed, hierarchical caching system. When you change a DNS record, here is what actually happens:
The total propagation time depends on the previous TTL value — not the new one. If the old record had a TTL of 86400 (24 hours), some resolvers will serve the old value for up to 24 hours after you make the change.
Pre-change TTL strategy
If you know a DNS change is coming, lower the TTL 24-48 hours in advance:
; Before (high TTL for performance)
luxkern.com. 86400 IN A 203.0.113.50
; Step 1: Lower TTL 48 hours before the migration
luxkern.com. 300 IN A 203.0.113.50
; Step 2: After 48 hours, make the actual change
luxkern.com. 300 IN A 198.51.100.75
; Step 3: After confirming propagation, raise TTL again
luxkern.com. 86400 IN A 198.51.100.75With a 300-second (5-minute) TTL, propagation completes in minutes instead of hours.
Checking DNS Propagation with dig
dig is the standard DNS lookup tool on Linux and macOS. It shows the exact response from any nameserver you specify.Query a specific resolver
# Google's resolver
dig @8.8.8.8 luxkern.com A +shortOutput:
203.0.113.50# Cloudflare's resolver
dig @1.1.1.1 luxkern.com A +shortOutput:
198.51.100.75Different output means propagation is still in progress. Google's resolver has the old record cached; Cloudflare's has already fetched the new one.
Check TTL remaining
dig @8.8.8.8 luxkern.com A +noall +answerOutput:
luxkern.com. 247 IN A 203.0.113.50The
247 is the remaining TTL in seconds. This resolver will keep serving 203.0.113.50 for another 4 minutes before querying the authoritative nameserver again.Query the authoritative nameserver directly
# Find the authoritative nameservers
dig luxkern.com NS +shortOutput:
ns1.cloudflare.com.
ns2.cloudflare.com.# Query the authoritative nameserver
dig @ns1.cloudflare.com luxkern.com A +shortOutput:
198.51.100.75If the authoritative nameserver returns the new IP, the change is saved correctly. All resolvers will eventually converge to this value.
Check multiple record types
# A record (IPv4)
dig luxkern.com A +short
AAAA record (IPv6)
dig luxkern.com AAAA +short
MX record (mail)
dig luxkern.com MX +short
TXT record (SPF, DKIM, verification)
dig luxkern.com TXT +short
CNAME record
dig www.luxkern.com CNAME +shortTrace the full resolution path
dig luxkern.com A +traceThis shows every step of the resolution: root servers, TLD servers, authoritative servers. Useful for debugging delegation issues where the wrong nameservers are configured at the registrar.
Checking DNS Propagation with nslookup
nslookup ships with Windows and is available on Linux/macOS. Its output is less detailed than dig but sufficient for basic checks.# Default resolver
nslookup luxkern.com
Specific resolver
nslookup luxkern.com 8.8.8.8Output:
Server: 8.8.8.8
Address: 8.8.8.8#53
Non-authoritative answer:
Name: luxkern.com
Address: 203.0.113.50"Non-authoritative answer" means the response came from a resolver's cache, not directly from the authoritative nameserver.
# Query a specific record type
nslookup -type=MX luxkern.com
nslookup -type=TXT luxkern.com
nslookup -type=CNAME www.luxkern.comChecking DNS Propagation with Node.js
For automated checks or integration into dashboards, use Node.js's built-in
dns module.const dns = require("dns");
const { Resolver } = dns.promises;
const RESOLVERS = [
{ name: "Google", ip: "8.8.8.8" },
{ name: "Cloudflare", ip: "1.1.1.1" },
{ name: "Quad9", ip: "9.9.9.9" },
{ name: "OpenDNS", ip: "208.67.222.222" },
{ name: "Level3", ip: "4.2.2.1" },
];
async function checkPropagation(domain, expectedIP) {
console.log(Checking propagation for ${domain} (expecting ${expectedIP})\n);
let propagated = 0;
for (const resolver of RESOLVERS) {
const r = new Resolver();
r.setServers([resolver.ip]);
try {
const addresses = await r.resolve4(domain);
const match = addresses.includes(expectedIP);
if (match) propagated++;
console.log(
${match ? "OK" : "PENDING"} | ${resolver.name.padEnd(12)} (${resolver.ip.padEnd(15)}) -> ${addresses.join(", ")}
);
} catch (err) {
console.log(
ERROR | ${resolver.name.padEnd(12)} (${resolver.ip.padEnd(15)}) -> ${err.code}
);
}
}
console.log(
\nPropagation: ${propagated}/${RESOLVERS.length} resolvers (${Math.round((propagated / RESOLVERS.length) * 100)}%)
);
}
checkPropagation("luxkern.com", "198.51.100.75");Output:
Checking propagation for luxkern.com (expecting 198.51.100.75)
OK | Google (8.8.8.8 ) -> 198.51.100.75
OK | Cloudflare (1.1.1.1 ) -> 198.51.100.75
PENDING | Quad9 (9.9.9.9 ) -> 203.0.113.50
OK | OpenDNS (208.67.222.222 ) -> 198.51.100.75
PENDING | Level3 (4.2.2.1 ) -> 203.0.113.50
Propagation: 3/5 resolvers (60%)Run this script on an interval to track propagation over time:
watch -n 60 node check-propagation.jsExtended Node.js example: check all record types
const dns = require("dns");
const { Resolver } = dns.promises;
async function checkAllRecords(domain) {
const resolver = new Resolver();
resolver.setServers(["8.8.8.8"]);
const checks = [
{ type: "A", fn: () => resolver.resolve4(domain) },
{ type: "AAAA", fn: () => resolver.resolve6(domain) },
{ type: "MX", fn: () => resolver.resolveMx(domain) },
{ type: "TXT", fn: () => resolver.resolveTxt(domain) },
{ type: "NS", fn: () => resolver.resolveNs(domain) },
{ type: "CNAME", fn: () => resolver.resolveCname(domain) },
];
for (const check of checks) {
try {
const result = await check.fn();
console.log(${check.type.padEnd(6)}: ${JSON.stringify(result)});
} catch (err) {
if (err.code === "ENODATA") {
console.log(${check.type.padEnd(6)}: (no record));
} else {
console.log(${check.type.padEnd(6)}: ERROR - ${err.code});
}
}
}
}
checkAllRecords("luxkern.com");Batch Propagation Check Script
When migrating multiple subdomains, check them all at once.
#!/usr/bin/env bash
set -euo pipefail
EXPECTED_IP="198.51.100.75"
DOMAINS=(
"luxkern.com"
"www.luxkern.com"
"api.luxkern.com"
"app.luxkern.com"
"docs.luxkern.com"
)
RESOLVERS=(
"8.8.8.8"
"1.1.1.1"
"9.9.9.9"
"208.67.222.222"
)
for domain in "${DOMAINS[@]}"; do
ok=0
total=${#RESOLVERS[@]}
for resolver in "${RESOLVERS[@]}"; do
ip=$(dig @"$resolver" "$domain" A +short 2>/dev/null | head -1)
if [ "$ip" = "$EXPECTED_IP" ]; then
((ok++))
fi
done
pct=$((ok * 100 / total))
if [ "$ok" -eq "$total" ]; then
status="COMPLETE"
elif [ "$ok" -gt 0 ]; then
status="PARTIAL "
else
status="PENDING "
fi
echo "[$status] $domain: $ok/$total resolvers ($pct%)"
doneOutput:
[COMPLETE] luxkern.com: 4/4 resolvers (100%)
[COMPLETE] www.luxkern.com: 4/4 resolvers (100%)
[PARTIAL ] api.luxkern.com: 3/4 resolvers (75%)
[PARTIAL ] app.luxkern.com: 2/4 resolvers (50%)
[PENDING ] docs.luxkern.com: 0/4 resolvers (0%)Understanding TTL in Practice
TTL values have real trade-offs:
| TTL | Propagation Time | Cache Benefit | Use Case | |-----|-----------------|---------------|----------| | 60s | ~1 minute | Minimal | Active migration, failover | | 300s | ~5 minutes | Low | Frequently changing records | | 3600s | ~1 hour | Moderate | Standard web records | | 86400s | ~24 hours | High | Stable records, email MX |
Lower TTLs mean faster propagation but more DNS queries hitting your authoritative servers. For most web applications, 300-3600 seconds is the sweet spot.
Common TTL mistakes
Setting TTL to 0: Some resolvers ignore TTL=0 and cache for a minimum of 30-300 seconds anyway. You cannot guarantee instant propagation.
Forgetting the old TTL: When you change a record, the propagation time is determined by the *previous* TTL, not the new one. If the old TTL was 86400, you wait up to 24 hours even if the new TTL is 60.
TTL on CNAME records: The effective TTL for a CNAME chain is the minimum TTL of all records in the chain. A CNAME with TTL 3600 pointing to an A record with TTL 60 effectively has a 60-second TTL for the final IP.
Flushing Your Local DNS Cache
While waiting for propagation, you can flush your own resolver to see the latest value.
# macOS
sudo dscacheutil -flushcache; sudo killall -HUP mDNSResponder
Windows
ipconfig /flushdns
Linux (systemd-resolved)
sudo systemd-resolve --flush-caches
Chrome browser
Navigate to chrome://net-internals/#dns and click "Clear host cache"
Flushing only affects your machine. Other users and servers worldwide still see their cached values until TTL expires.
DNS Propagation and CDNs
CDNs like Cloudflare add a layer of complexity. When you use Cloudflare's proxy (orange cloud icon), the A record points to Cloudflare's IPs, not your origin. Changing your origin server IP requires updating it in Cloudflare's dashboard — DNS propagation is not involved because the public DNS record (Cloudflare's IP) does not change.
When migrating to or from a CDN, you are changing the actual DNS record, so normal propagation rules apply. Lower your TTL before the migration.
Using Luxkern DNS Checker
Checking propagation manually against 4-5 resolvers gives you a rough picture. The Luxkern DNS Checker queries resolvers across 20+ global locations simultaneously and shows you a real-time map of propagation status. No installation, no account required.
Try DNS Checker free — no credit card required.
For ongoing monitoring after the migration is complete, PingCheck continuously verifies that your domains resolve correctly and alerts you if DNS records change unexpectedly.
Try PingCheck free — no credit card required.
Troubleshooting Slow Propagation
Record changed at provider but resolvers still show old value
This is normal. Wait for the old TTL to expire. Check
dig +noall +answer to see the remaining TTL on cached records.Authoritative nameserver shows old value
Your change did not save properly at the DNS provider. Log in and verify. Some providers have a "publish" or "deploy" step after editing.
One specific resolver never updates
Some ISP resolvers do not respect TTLs and cache aggressively. You cannot fix this — it resolves itself eventually (usually within 48 hours).
SERVFAIL responses
The authoritative nameserver is unreachable or misconfigured. Check that your nameserver records at the registrar match your DNS provider's nameservers. Use
dig +trace to find where the chain breaks.DNS Propagation for Common Scenarios
Domain migration to a new host
Adding a subdomain
New records propagate instantly for resolvers that have never queried the subdomain. There is no "old" cached record to expire. The only delay is the negative cache TTL from the SOA record (typically 1 hour).
Email migration (MX records)
Email is more sensitive to DNS issues than web traffic. Lower the MX record TTL days in advance, and keep the old mail server accepting mail for at least 72 hours after the change.
For a deeper understanding of each DNS record type and when to use them, read DNS records explained for developers. For monitoring your endpoints after migration, see how to monitor API endpoints.
Try Luxkern's developer tools free — no credit card required.