DNS Deep Dive 2026 — Optimize Web Speed from the First Step with Anycast, Prefetch, and Cloudflare DNS
Posted on: 4/20/2026 10:11:36 PM
Table of contents
- 1. DNS Resolution Architecture — the journey from domain to IP
- 2. Anycast Routing — the secret to global DNS speed
- 3. DNS Prefetch & Preconnect — client-side tuning
- 4. DNS Encryption — DoH, DoT, and DNS over QUIC
- 5. Cloudflare DNS Free Tier — enterprise-grade at zero cost
- 6. DNS production tuning — practical checklist
- 7. Advanced patterns — DNS for system design
- 8. Conclusion
- 9. References
Every time you enter a URL in a browser, the first step isn't TCP connection or TLS handshake — it's DNS resolution. This "invisible" step directly impacts Time to First Byte (TTFB) and user experience. This post goes deep into DNS architecture, performance optimization techniques, and how to leverage Cloudflare's free DNS for production.
1. DNS Resolution Architecture — the journey from domain to IP
1.1 Four tiers of DNS servers
The DNS system works as a hierarchical tree of 4 server types, each with a distinct role:
graph TD
A["Browser/Client"] -->|"1. Query: anhtu.dev?"| B["Recursive Resolver
(1.1.1.1 / 8.8.8.8)"]
B -->|"2. Ask Root"| C["Root Nameserver
(13 clusters globally)"]
C -->|"3. Return TLD: .dev -> ns.nic.google"| B
B -->|"4. Ask TLD"| D["TLD Nameserver
(.dev / .com / .io)"]
D -->|"5. Return NS: cloudflare"| B
B -->|"6. Ask Authoritative"| E["Authoritative NS
(Cloudflare DNS)"]
E -->|"7. Return A record: 104.21.x.x"| B
B -->|"8. Return IP to client"| A
style A fill:#f8f9fa,stroke:#e94560,color:#2c3e50
style B fill:#e94560,stroke:#fff,color:#fff
style C fill:#2c3e50,stroke:#fff,color:#fff
style D fill:#2c3e50,stroke:#fff,color:#fff
style E fill:#16213e,stroke:#fff,color:#fff
Figure 1: Full DNS resolution flow — 8 steps from client to IP address
| Server Type | Role | Example | Global count |
|---|---|---|---|
| Recursive Resolver | Middleman, caches results, runs the whole lookup | 1.1.1.1, 8.8.8.8, ISP DNS | Thousands |
| Root Nameserver | Entry point, directs to TLD servers | a.root-servers.net -> m.root-servers.net | 13 clusters (1000+ Anycast instances) |
| TLD Nameserver | Manages domains by extension (.com, .dev, .io) | ns.nic.google (.dev), a.gtld-servers.net (.com) | Hundreds |
| Authoritative NS | Holds the official records for a domain (A, AAAA, CNAME, MX...) | Cloudflare NS, AWS Route 53, Azure DNS | Per-provider |
1.2 DNS Caching and TTL Strategy
DNS caches exist at multiple tiers: browser cache → OS cache → router cache → recursive resolver cache. Each DNS record has a TTL (Time to Live) specifying how long the cache is valid.
TTL strategy for production
- TTL 300s (5 minutes): Most common, balancing performance and flexibility. Use for A/AAAA records that need quick failover.
- TTL 3600s (1 hour): For records that rarely change (MX, TXT, SPF). Reduces load on the authoritative server.
- TTL 86400s (24 hours): For NS records and very stable domains. Maximizes cache hit rate.
- TTL 60s: Only during fast cutovers (migration, blue-green deploy). Increases load on resolvers.
1.3 Important DNS record types
| Record Type | Purpose | Example value | When to use |
|---|---|---|---|
| A | Maps domain -> IPv4 | 104.21.32.1 | Every website |
| AAAA | Maps domain -> IPv6 | 2606:4700:3030::6815:2001 | Dual-stack, mobile-first |
| CNAME | Alias one domain to another | www -> anhtu.dev | Subdomains, CDN integration |
| CAA | Specifies which CAs may issue SSL | 0 issue "letsencrypt.org" | SSL/TLS security |
| MX | Mail server routing | 10 mail.google.com | Email services |
| TXT | Text data (SPF, DKIM, verification) | v=spf1 include:_spf.google.com ~all | Email auth, domain verification |
2. Anycast Routing — the secret to global DNS speed
2.1 Anycast vs Unicast
In the traditional Unicast model, each DNS server has one unique IP. The client always connects to that exact server regardless of distance. With Anycast, the same IP is announced from many locations. BGP routing automatically sends the request to the nearest server.
graph LR
subgraph "Unicast (traditional)"
U1["User in Vietnam"] -->|"150ms"| US["Single US server"]
U2["User in Japan"] -->|"80ms"| US
U3["User in Australia"] -->|"120ms"| US
end
subgraph "Anycast (modern)"
A1["User in Vietnam"] -->|"5ms"| SG["Edge Singapore"]
A2["User in Japan"] -->|"3ms"| JP["Edge Tokyo"]
A3["User in Australia"] -->|"4ms"| AU["Edge Sydney"]
end
style US fill:#ff9800,stroke:#fff,color:#fff
style SG fill:#4CAF50,stroke:#fff,color:#fff
style JP fill:#4CAF50,stroke:#fff,color:#fff
style AU fill:#4CAF50,stroke:#fff,color:#fff
Figure 2: Unicast vs Anycast — proximity routing slashes latency
Why Anycast matters
Cloudflare operates an Anycast network with 330+ PoPs (Points of Presence) globally. A 1.1.1.1 query is routed to the nearest PoP — usually under 10ms. In contrast, ISP DNS in Vietnam typically has 30-80ms latency due to centralized servers.
2.2 Anycast & DDoS resilience
Anycast doesn't just optimize speed — it's also a DDoS shield. When an attacker floods an Anycast IP, the traffic is automatically distributed across hundreds of PoPs instead of concentrated on a single target. Each PoP handles a small fraction, absorbing the attack without overload.
3. DNS Prefetch & Preconnect — client-side tuning
3.1 Resource Hints for DNS
Browsers offer 3 Resource Hint mechanisms that reduce perceived latency by doing DNS lookups before the user actually needs them:
<!-- dns-prefetch: only resolves DNS (lightest, ~5ms overhead) -->
<link rel="dns-prefetch" href="//fonts.googleapis.com">
<link rel="dns-prefetch" href="//cdn.jsdelivr.net">
<link rel="dns-prefetch" href="//storage.anhtu.dev">
<!-- preconnect: DNS + TCP + TLS handshake (more expensive but more effective) -->
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="preconnect" href="https://api.example.com">
<!-- preload: immediately fetches a specific resource (when you're sure you need it) -->
<link rel="preload" href="/fonts/inter.woff2" as="font" type="font/woff2" crossorigin>
sequenceDiagram
participant B as Browser
participant DNS as DNS Resolver
participant S as Server
Note over B: No prefetch
B->>DNS: Resolve fonts.gstatic.com
DNS-->>B: IP (50ms)
B->>S: TCP Handshake (30ms)
S-->>B: ACK
B->>S: TLS Handshake (40ms)
S-->>B: Certificate
B->>S: GET /font.woff2
S-->>B: Response
Note over B: Total: 120ms+ before download begins
Note over B: With preconnect
B->>DNS: Resolve early (parallel with HTML parse)
DNS-->>B: IP
B->>S: TCP + TLS (parallel)
S-->>B: Ready
Note over B: When font is needed -> connection ready -> 0ms overhead
Figure 3: Preconnect removes 120ms+ of delay by running in parallel with HTML parsing
3.2 Resource Hint best practices
Important notes
- Limit preconnect to 4-6 domains — each connection uses memory and CPU. Too many competes for bandwidth against critical resources.
- dns-prefetch is a safe fallback — very low cost (~1KB memory), can be used for 8-10 domains without issue.
- Prioritize critical third parties: CDN, font provider, analytics, API endpoints. Don't prefetch domains only used after user interaction.
- Combine both: preconnect for domains you definitely need soon, dns-prefetch for domains you might need.
<!-- Optimal pattern for a Vue + .NET site -->
<head>
<!-- Critical: API and CDN (preconnect) -->
<link rel="preconnect" href="https://api.myapp.com">
<link rel="preconnect" href="https://cdn.myapp.com" crossorigin>
<!-- Important: fonts and analytics (dns-prefetch) -->
<link rel="dns-prefetch" href="//fonts.googleapis.com">
<link rel="dns-prefetch" href="//www.googletagmanager.com">
<!-- Optional: third parties possibly needed after interaction -->
<link rel="dns-prefetch" href="//connect.facebook.net">
</head>
4. DNS Encryption — DoH, DoT, and DNS over QUIC
4.1 Why encrypt DNS?
Traditional DNS sends queries as plaintext over UDP port 53. That means your ISP, any attacker on the network, or any middlebox can:
- Read every domain you visit (privacy breach)
- Modify responses to redirect to fake IPs (DNS spoofing/hijacking)
- Block access selectively (censorship)
In 2025, a US Executive Order mandated DNS encryption across federal systems. As of 2026, 87% of organizations have been hit by at least one DNS attack.
4.2 Comparing the three encrypted DNS protocols
| Criterion | DNS over HTTPS (DoH) | DNS over TLS (DoT) | DNS over QUIC (DoQ) |
|---|---|---|---|
| Port | 443 (shared with HTTPS) | 853 (dedicated) | 853 (QUIC/UDP) |
| Transport | HTTPS/2 or HTTP/3 | TLS 1.3 over TCP | QUIC (UDP + TLS 1.3) |
| Average latency | 12-18ms | 20-25ms | 8-12ms |
| Privacy | High (hidden inside HTTPS traffic) | Medium (dedicated port, easy to identify) | High (UDP, hard to distinguish) |
| Network admin visibility | Low (hard to block/monitor) | High (port 853 easy to block) | Medium |
| 0-RTT support | Yes (HTTP/3) | Yes (TLS 1.3 session resumption) | Yes (native QUIC) |
| High-loss networks | Poor (TCP head-of-line blocking) | Poor (TCP) | Good (UDP, 47% lower latency) |
| Browser support | Chrome, Firefox, Edge, Safari | Android native, systemd-resolved | Emerging |
graph LR
subgraph "DNS Plaintext (legacy)"
P1["Client"] -->|"UDP :53
Plaintext"| P2["Resolver"]
P3["Attacker"] -.->|"Read/Modify"| P1
end
subgraph "DoH (recommended for browsers)"
D1["Client"] -->|"HTTPS :443
Encrypted"| D2["Resolver"]
D3["Attacker"] -.->|"Cannot read"| D1
end
subgraph "DoQ (future)"
Q1["Client"] -->|"QUIC :853
0-RTT"| Q2["Resolver"]
end
style P3 fill:#ff9800,stroke:#fff,color:#fff
style D3 fill:#4CAF50,stroke:#fff,color:#fff
style D2 fill:#e94560,stroke:#fff,color:#fff
style Q2 fill:#16213e,stroke:#fff,color:#fff
Figure 4: DNS protocol evolution — from plaintext to encrypted with 0-RTT
4.3 Configuring DoH on server and client
// DoH configuration for browsers (Chrome flags or Group Policy)
{
"dns_over_https": {
"mode": "secure",
"templates": "https://cloudflare-dns.com/dns-query"
}
}
# systemd-resolved configuration (Linux server)
# /etc/systemd/resolved.conf
[Resolve]
DNS=1.1.1.1#cloudflare-dns.com 1.0.0.1#cloudflare-dns.com
DNSOverTLS=yes
DNSSEC=yes
# Verify
resolvectl status
resolvectl query anhtu.dev
// .NET 10 — HttpClient automatically uses OS DNS settings
// To force DoH programmatically in your app:
using var handler = new SocketsHttpHandler
{
ConnectCallback = async (context, token) =>
{
// Custom DNS resolution if needed
var socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
await socket.ConnectAsync(context.DnsEndPoint, token);
return new NetworkStream(socket, ownsSocket: true);
}
};
// Simpler: configure OS-level DoH and .NET inherits automatically
5. Cloudflare DNS Free Tier — enterprise-grade at zero cost
5.1 Notable free-tier features
| Feature | Cloudflare Free | AWS Route 53 | Azure DNS |
|---|---|---|---|
| Hosting cost | $0 | $0.50/zone/month | $0.50/zone/month |
| Query cost | $0 (unlimited) | $0.40/million queries | $0.40/million queries |
| DNSSEC | Free, 1-click | $0 (manual setup) | Not supported on custom domains |
| DDoS Protection | Unmetered, always on | Shield Standard free | Basic DDoS included |
| Anycast | 330+ PoPs | ~60 edge locations | ~60 regions |
| Propagation speed | <5 seconds | ~60 seconds | ~60 seconds |
| API/Automation | REST API + Terraform | SDK + Terraform | SDK + Terraform |
5.2 DNSSEC — preventing DNS spoofing
DNSSEC (DNS Security Extensions) adds a cryptographic signature to every DNS response. Resolvers can verify that a record actually came from the authoritative server and wasn't tampered with in transit.
graph TD
A["Authoritative NS"] -->|"1. Sign record
with private key"| B["DNS Response
+ RRSIG record"]
B -->|"2. Send signed response"| C["Recursive Resolver"]
C -->|"3. Verify signature
with DNSKEY (public)"| D{"Valid?"}
D -->|"Yes"| E["Return IP to client"]
D -->|"No"| F["SERVFAIL — reject response"]
G["Attacker"] -.->|"Inject fake response"| C
C -.->|"Signature mismatch -> reject"| G
style A fill:#4CAF50,stroke:#fff,color:#fff
style F fill:#ff9800,stroke:#fff,color:#fff
style G fill:#e94560,stroke:#fff,color:#fff
style E fill:#4CAF50,stroke:#fff,color:#fff
Figure 5: DNSSEC chain of trust — rejects any tampered response
5.3 Configuring Cloudflare DNS for production
# Step 1: Add domain to Cloudflare (Free plan)
# Dashboard -> Add Site -> choose Free plan
# Step 2: Update Nameservers at your registrar
# ns1: aria.ns.cloudflare.com
# ns2: duke.ns.cloudflare.com
# Step 3: Configure DNS records
# A anhtu.dev -> 104.21.32.1 (Proxied)
# AAAA anhtu.dev -> 2606:4700:... (Proxied)
# CNAME www -> anhtu.dev (Proxied)
# MX anhtu.dev -> aspmx.l.google.com (DNS only)
# TXT anhtu.dev -> "v=spf1 include:..." (DNS only)
# Step 4: Enable DNSSEC (1-click in Dashboard -> DNS -> DNSSEC)
# Copy DS record -> paste at registrar
Proxy mode (Orange Cloud) vs DNS only
- Proxied: traffic goes through Cloudflare's CDN — caching, DDoS protection, hides origin IP. Use for web traffic (A, AAAA, CNAME).
- DNS only: pure DNS resolution, traffic goes straight to the origin. Use for MX, TXT, and non-HTTP services (SSH, game servers).
6. DNS production tuning — practical checklist
6.1 Frontend DNS optimization
<!-- 1. Resource Hints in <head> -->
<link rel="preconnect" href="https://api.yourapp.com">
<link rel="dns-prefetch" href="//cdn.yourapp.com">
<!-- 2. HTTP headers (configured at server/CDN) -->
<!-- Link: <https://api.yourapp.com>; rel=preconnect -->
<!-- 3. Reduce the number of third-party domains -->
<!-- Every new domain = 1 DNS lookup (50-300ms) -->
<!-- Consolidate: use 1 CDN instead of 5 third-party scripts -->
6.2 Server-side DNS optimization
// .NET 10 — Configure DNS caching in HttpClient
builder.Services.AddHttpClient("ExternalApi", client =>
{
client.BaseAddress = new Uri("https://api.partner.com");
})
.ConfigurePrimaryHttpMessageHandler(() => new SocketsHttpHandler
{
// Keep connections alive -> avoid DNS re-resolution
PooledConnectionLifetime = TimeSpan.FromMinutes(10),
// Cap DNS refresh interval
PooledConnectionIdleTimeout = TimeSpan.FromMinutes(5),
// Enable multiple HTTP/2 connections
EnableMultipleHttp2Connections = true
});
// Don't create a new HttpClient per request -> DNS lookup every time!
// IHttpClientFactory manages connection pooling + DNS refresh automatically
// Vue 3 + Vite — auto-inject dns-prefetch for build assets
// vite.config.ts
export default defineConfig({
plugins: [
{
name: 'dns-prefetch-plugin',
transformIndexHtml(html) {
const domains = [
'//fonts.googleapis.com',
'//cdn.jsdelivr.net',
'//storage.anhtu.dev'
];
const links = domains
.map(d => ``)
.join('\n ');
return html.replace('', ` ${links}\n `);
}
}
]
});
6.3 Monitoring DNS performance
# Measure DNS resolution time
dig @1.1.1.1 anhtu.dev +stats | grep "Query time"
# ;; Query time: 3 msec
# Compare providers
for dns in 1.1.1.1 8.8.8.8 208.67.222.222; do
echo -n "$dns: "
dig @$dns anhtu.dev +stats | grep "Query time"
done
# Check DNSSEC
dig anhtu.dev +dnssec +short
# Verify chain: dig DS anhtu.dev @1.1.1.1
# Measure from the browser (Navigation Timing API)
# performance.getEntriesByType('navigation')[0].domainLookupEnd
# - performance.getEntriesByType('navigation')[0].domainLookupStart
// DNS monitoring in a Vue app
// composables/useDnsMetrics.ts
export function useDnsMetrics() {
const metrics = ref<PerformanceNavigationTiming | null>(null);
onMounted(() => {
const nav = performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming;
metrics.value = nav;
const dnsTime = nav.domainLookupEnd - nav.domainLookupStart;
const connectTime = nav.connectEnd - nav.connectStart;
const ttfb = nav.responseStart - nav.requestStart;
// Report metrics to backend if DNS exceeds threshold
if (dnsTime > 100) {
console.warn(`Slow DNS: ${dnsTime.toFixed(0)}ms for ${location.hostname}`);
// reportMetric('dns_slow', { dnsTime, host: location.hostname });
}
});
return { metrics };
}
7. Advanced patterns — DNS for system design
7.1 DNS-based load balancing
graph TD
A["Client query: api.myapp.com"] --> B["Cloudflare DNS
(Load Balancing)"]
B -->|"Health check OK"| C["Pool A: APAC
Singapore + Tokyo"]
B -->|"Health check OK"| D["Pool B: EU
Frankfurt + London"]
B -->|"Health check FAIL"| E["Pool C: US
Down"]
B -->|"Geo steering:
VN user -> APAC"| C
B -->|"Geo steering:
DE user -> EU"| D
F["Health Monitor"] -->|"Check /health every 60s"| C
F -->|"Check /health every 60s"| D
F -->|"FAIL -> remove from pool"| E
style B fill:#e94560,stroke:#fff,color:#fff
style C fill:#4CAF50,stroke:#fff,color:#fff
style D fill:#4CAF50,stroke:#fff,color:#fff
style E fill:#ff9800,stroke:#fff,color:#fff
Figure 6: DNS load balancing with geo steering and automatic failover
7.2 Split-Horizon DNS (internal vs external)
In microservice architectures, the same domain can resolve differently depending on the query origin:
# Example: api.myapp.internal
# From inside the cluster (Kubernetes):
# -> resolves to ClusterIP 10.96.x.x (internal service)
# From outside (public):
# -> resolves to load balancer IP 104.21.x.x
# CoreDNS config (Kubernetes)
apiVersion: v1
kind: ConfigMap
metadata:
name: coredns
data:
Corefile: |
.:53 {
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
fallthrough in-addr.arpa ip6.arpa
}
forward . 1.1.1.1 1.0.0.1
cache 30
loop
reload
}
7.3 DNS failover pattern for zero-downtime
TTL strategy for failover
- Normal: TTL 300s — 5-minute cache, sufficient performance.
- Before maintenance: drop TTL to 60s — wait for old TTL to expire (5 minutes).
- Cutover: update DNS to new server — propagates in 60s.
- After stabilization: raise TTL back to 300s.
This pattern allows failover in <2 minutes instead of 5-10 minutes with high TTLs.
8. Conclusion
DNS is the first and most overlooked layer in the web performance stack. Small changes bring meaningful improvements:
- Move to Cloudflare DNS (free): 330+ Anycast PoPs, <5s propagation, 1-click DNSSEC
- Add dns-prefetch/preconnect: cuts 100-300ms for third-party domains
- Enable DoH/DoT: protects privacy, prevents DNS spoofing, acceptable latency (12-18ms)
- Right TTL strategy: 300s for web, 60s before failover, 3600s for static records
- Monitor DNS metrics: Navigation Timing API in the client, dig/drill on the server
DNS optimization isn't as flashy as lazy loading or code splitting, but it affects every request — and deployment cost is near zero with the Cloudflare Free tier.
9. References
.NET Aspire — The Cloud-Native Platform That Makes .NET Developers Stop Fearing Microservices
View Transitions API — Native-App-Smooth Page Transitions Without a Framework
Disclaimer: The opinions expressed in this blog are solely my own and do not reflect the views or opinions of my employer or any affiliated organizations. The content provided is for informational and educational purposes only and should not be taken as professional advice. While I strive to provide accurate and up-to-date information, I make no warranties or guarantees about the completeness, reliability, or accuracy of the content. Readers are encouraged to verify the information and seek independent advice as needed. I disclaim any liability for decisions or actions taken based on the content of this blog.