What DNS Resolution Issues Look Like
DNS resolution translates domain names into IP addresses. It happens before any HTTP connection can start, so DNS problems add latency to (or completely block) every resource on the page that requires a lookup. A single page load can require 10–30 DNS lookups across different domains (your origin, CDN, fonts, analytics, ads, third-party widgets).
| Lookup Time | Assessment | Impact |
|---|---|---|
| < 20ms | Good | Cached or nearby resolver |
| 20–100ms | Acceptable | Typical for uncached lookups |
| 100–300ms | Slow | Adds noticeable delay, especially on mobile |
| > 300ms | Problem | Indicates misconfiguration, distant resolver, or failing nameserver |
| Timeout | Failure | Resource doesn't load at all |
Common symptoms:
- Intermittent page load failures — DNS resolution times out for some users but not others (often geographic or ISP-specific).
- Slow initial page load, fast subsequent loads — First visit requires fresh DNS lookups; subsequent visits use browser DNS cache.
- Specific resources failing — Third-party scripts/fonts/images fail because their DNS provider has issues, while your own content loads fine.
- Post-migration downtime — After changing DNS records, some users still hit the old IP address due to TTL caching.
Diagnosing DNS Issues
Browser DevTools
Open Network tab → click on a slow request → Timing tab. The "DNS Lookup" row shows how long resolution took for that specific request. If DNS Lookup is consistently > 50ms for your own domain, investigate further.
For a page-wide view, use the Resource Timing API:
// List all resources with DNS lookup times > 0ms
// (Cached lookups show 0ms because the browser already knows the IP)
performance.getEntriesByType('resource')
.filter(r => r.domainLookupEnd - r.domainLookupStart > 0)
.sort((a, b) => (b.domainLookupEnd - b.domainLookupStart) - (a.domainLookupEnd - a.domainLookupStart))
.forEach(r => {
const dns = Math.round(r.domainLookupEnd - r.domainLookupStart);
const domain = new URL(r.name).hostname;
console.log(`${domain}: ${dns}ms`);
});
Command Line
# Basic lookup — shows IP and response time
dig example.com
# Query specific DNS providers to compare speeds
dig @8.8.8.8 example.com # Google Public DNS
dig @1.1.1.1 example.com # Cloudflare DNS
dig @208.67.222.222 example.com # OpenDNS
# Full trace from root servers — shows each hop in the resolution chain
dig +trace example.com
# Check all record types
dig example.com ANY
# Measure DNS response time specifically
dig example.com | grep "Query time"
# Check nameserver delegation
dig NS example.com
# Reverse DNS (verify PTR record matches)
dig -x 93.184.216.34
# Check if DNSSEC is configured
dig example.com +dnssec +short
Check DNS Propagation After Changes
After updating DNS records, propagation depends on the previous TTL value. If the old record had TTL 86400 (24 hours), some resolvers will serve the old IP for up to 24 hours.
# Check what different global resolvers return
# Use a propagation checker like whatsmydns.net, or query directly:
for resolver in 8.8.8.8 1.1.1.1 208.67.222.222 9.9.9.9; do
echo "=== $resolver ==="
dig @$resolver example.com +short
done
Common Issues and Fixes
1. Slow DNS Resolution (High Latency)
Cause: Distant or overloaded authoritative nameservers, no anycast, low TTL forcing frequent re-lookups.
Fix — Use a DNS provider with global anycast: Cloudflare DNS, AWS Route 53, Google Cloud DNS, and NS1 all use anycast networks with points of presence worldwide. This means DNS queries are answered by the nearest server.
Fix — Increase TTL for stable records: If your IP address rarely changes, increase TTL from the default (often 300s) to 3600s or higher. This reduces re-lookup frequency:
; Before: resolved every 5 minutes
example.com. 300 IN A 93.184.216.34
; After: resolved every hour (or served from cache)
example.com. 3600 IN A 93.184.216.34
Tradeoff: Higher TTL means slower propagation when you do change records. For static infrastructure, 3600–86400s is appropriate. For records you change frequently, keep TTL at 300–600s.
2. DNS Lookup Failures
Cause: Nameserver down, domain expired, misconfigured delegation, or DNSSEC validation failure.
Diagnose:
# Check if nameservers respond
dig NS example.com
# Then query each nameserver directly
dig @ns1.example.com example.com
dig @ns2.example.com example.com
# Check domain registration status
whois example.com | grep -i "expir\|status"
Fix: Ensure you have at least 2 nameservers (3+ recommended) on different networks. If one fails, clients fall back to others. Most managed DNS providers handle this automatically.
3. DNS Propagation Delays After Migration
Cause: Old TTL values were high, so resolvers worldwide cache the old IP until the TTL expires.
Fix — Lower TTL before migration:
Step 1: 48+ hours before migration
- Lower TTL to 60–300 seconds
- Wait for old TTL to expire everywhere
Step 2: Make the DNS change
- Update A/AAAA/CNAME records to new IP
Step 3: Verify propagation
- Check from multiple resolvers (see commands above)
- Monitor error rates in your analytics
Step 4: After propagation completes
- Raise TTL back to normal (3600+)
4. DNSSEC Validation Failures
Cause: DNSSEC signatures expired, DS record at registrar doesn't match DNSKEY at nameserver, or clock skew on validating resolver.
# Check DNSSEC chain of trust
dig example.com +dnssec +cd # +cd disables validation to see if the record exists
delv example.com # shows validation chain
# Common error: SERVFAIL when DNSSEC is broken
dig example.com # Returns SERVFAIL
dig example.com +cd # Returns the actual record (bypassing DNSSEC)
# This means DNSSEC is misconfigured, not the record itself
Fix: Either fix the DNSSEC chain (update DS records at registrar to match current DNSKEY) or disable DNSSEC if you don't need it. A broken DNSSEC configuration is worse than no DNSSEC — it causes resolution failures for all users on validating resolvers.
5. Too Many DNS Lookups Per Page
Each unique domain on your page requires a separate DNS lookup on first visit. Pages loading resources from 20+ domains pay a significant DNS tax.
Audit your domains:
// Count unique domains loaded by the page
const domains = new Set(
performance.getEntriesByType('resource')
.map(r => new URL(r.name).hostname)
);
console.log(`Unique domains: ${domains.size}`);
domains.forEach(d => console.log(` - ${d}`));
Fix — Consolidate domains: Self-host Google Fonts instead of loading from fonts.googleapis.com + fonts.gstatic.com. Use your CDN for images instead of a separate image CDN domain. Each domain you eliminate saves 20–100ms on first visit.
Fix — DNS prefetch for remaining third-party domains:
<!-- Resolve DNS early for domains you can't eliminate -->
<link rel="dns-prefetch" href="https://www.googletagmanager.com">
<link rel="dns-prefetch" href="https://connect.facebook.net">
<link rel="dns-prefetch" href="https://cdn.jsdelivr.net">
Monitoring DNS Performance
Resource Timing API (Real User Monitoring)
// Send DNS metrics to your analytics platform
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
const dnsTime = entry.domainLookupEnd - entry.domainLookupStart;
if (dnsTime > 0) {
// Send to analytics — track domain, dns time, and user geography
analytics.track('dns_lookup', {
domain: new URL(entry.name).hostname,
dns_ms: Math.round(dnsTime),
connection_ms: Math.round(entry.connectEnd - entry.connectStart),
});
}
}
}).observe({ type: 'resource', buffered: true });
Navigation Timing (Page-Level DNS)
// DNS time for the main document
const nav = performance.getEntriesByType('navigation')[0];
const pageDns = nav.domainLookupEnd - nav.domainLookupStart;
console.log(`Page DNS lookup: ${Math.round(pageDns)}ms`);
Related Guides
- Prefetch and Preconnect — resource hints that mitigate DNS latency
- HTTP/3 Adoption — QUIC includes 0-RTT reconnection which reduces DNS + connection overhead