The Domain Name System (DNS) translates human-readable domain names into IP addresses — it is the first request every internet application makes. Understanding DNS from the wire up helps engineers debug latency, harden security, and design resilient distributed systems.
This guide covers the full resolution path (stub → recursive → root → TLD → authoritative), every major record type including the new SVCB/HTTPS (RFC 9460), encryption protocols (DoH, DoT, DoQ), DNSSEC, DNS in Kubernetes with CoreDNS, and production security practices.
How DNS Works
The Problem DNS Solves
Computers locate each other by IP addresses, but humans work with names. DNS bridges this gap with a hierarchical, distributed database.
DNS Hierarchy
graph TD
Root["Root Zone (.)"] --> TLD1[".com TLD"]
Root --> TLD2[".org TLD"]
Root --> TLD3[".net TLD"]
TLD1 --> Auth["Authoritative NS<br/>for example.com"]
Auth --> NS1["ns1.example.com"]
Auth --> NS2["ns2.example.com"]
Auth --> NS3["ns3.example.com"]
The hierarchy has three tiers:
- Root nameservers (13 logical authorities, operated by organizations like Verisign, ICANN) — they know where TLD nameservers live.
- TLD nameservers (
.com,.org,.net,.io, etc.) — they know which nameservers are authoritative for each domain. - Authoritative nameservers — they hold the actual DNS records for a domain (A, MX, CNAME, etc.).
Resolution Step by Step
When a client requests example.com for the first time, the full resolution path from warm cache is:
- The stub resolver (in the OS or browser) forwards the query to a configured recursive resolver (usually ISP or public resolver like
8.8.8.8or1.1.1.1). - The recursive resolver checks its cache. On a miss, it queries a root nameserver.
- The root responds with a referral to the
.comTLD nameservers. - The recursive resolver queries a
.comTLD nameserver. - The TLD responds with the authoritative nameservers for
example.com. - The recursive resolver queries an authoritative nameserver.
- The authoritative server returns the requested record (e.g., an A record).
- The recursive resolver caches the result and returns it to the client.
- The stub resolver returns the IP to the application.
import socket
# Simple forward resolution
ip = socket.gethostbyname('example.com')
print(f"Resolved: {ip}")
Recursive vs Iterative Queries
Recursive resolution — the resolver does all the work. The client sends one query and gets an answer. Most stub resolvers use recursive mode.
Iterative resolution — the server responds with a referral to the next server in the chain if it does not have the answer. Root and TLD servers operate in iterative mode; they never perform recursion for clients.
Stub vs Recursive vs Authoritative
| Role | What It Does | Example Software |
|---|---|---|
| Stub resolver | Thin client in the OS or application that forwards queries to a recursive resolver | libc resolver, systemd-resolved |
| Recursive resolver | Performs full resolution on behalf of clients; caches results | Unbound, BIND (recursive mode), systemd-resolved |
| Authoritative server | Serves DNS records for zones it owns | BIND, NSD, PowerDNS, CoreDNS |
Query Flow Diagram
sequenceDiagram
participant Client as Client App
participant Stub as Stub Resolver
participant Recursive as Recursive Resolver
participant Root as Root NS
participant TLD as TLD NS (.com)
participant Auth as Authoritative NS
Client->>Stub: gethostbyname("example.com")
Stub->>Recursive: Recursive query
Note over Recursive: Cache miss
Recursive->>Root: Where is .com?
Root-->>Recursive: Referral to .com TLD
Recursive->>TLD: Where is example.com?
TLD-->>Recursive: Referral to auth NS
Recursive->>Auth: A record for example.com
Auth-->>Recursive: 93.184.216.34
Note over Recursive: Cache result (TTL)
Recursive-->>Stub: Answer
Stub-->>Client: IP address
DNS Record Types
A and AAAA Records
Map a domain to an IP address — A for IPv4, AAAA for IPv6.
# A Record - IPv4
example.com. IN A 93.184.216.34
www.example.com. IN A 93.184.216.34
# AAAA Record - IPv6
example.com. IN AAAA 2606:2800:220:1:248:1893:25c8:1946
CNAME Record
Creates an alias from one domain to another. The resolver follows the chain and returns the final IP.
blog.example.com. IN CNAME www.example.com.
cdn.example.com. IN CNAME example.cloudfront.net.
# CNAME at the zone apex is forbidden by RFC 1034.
# Use SVCB/HTTPS records or ALIAS/ANAME (provider-specific) instead.
MX Record
Specifies mail servers for a domain. Lower priority values are preferred.
example.com. IN MX 10 mail1.example.com.
example.com. IN MX 20 mail2.example.com.
example.com. IN MX 30 mail.backup-provider.com.
TXT Record
Holds arbitrary text, commonly used for email authentication and domain verification.
# SPF - authorized mail senders
example.com. IN TXT "v=spf1 include:_spf.google.com ~all"
# DKIM - cryptographic email signing
selector._domainkey.example.com. IN TXT \
"v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA..."
# DMARC - reporting and enforcement policy
_dmarc.example.com. IN TXT \
"v=DMARC1; p=quarantine; rua=mailto:[email protected]"
NS and SOA Records
NS records delegate a domain to its authoritative nameservers. SOA records contain zone metadata — serial number, refresh interval, admin contact.
; NS records
example.com. IN NS ns1.example.com.
example.com. IN NS ns2.example.com.
; SOA record with timing parameters
example.com. IN SOA ns1.example.com. admin.example.com. (
2026052001 ; Serial (YYYYMMDDNN)
3600 ; Refresh (1 hour)
1800 ; Retry (30 minutes)
604800 ; Expire (1 week)
86400 ) ; Minimum TTL (1 day)
SVCB and HTTPS Records (RFC 9460)
Standardized in 2023-2024, SVCB (Service Binding) and its HTTPS subtype let a domain advertise connection parameters directly in DNS. The client learns the supported protocols (HTTP/2, HTTP/3), alternate port, and even Encrypted Client Hello (ECH) parameters in a single query — no additional lookups or upgrades needed.
; HTTPS record for example.com
; ALPN: h3 = HTTP/3 over QUIC, h2 = HTTP/2
; port: 443 (default, can be omitted)
example.com. IN HTTPS 1 . alpn="h3,h2" ech="AAAA..."
; Alias form: point to a different name (like CNAME at apex)
example.com. IN HTTPS 0 cdn.example.com.
CTL (the alias form with priority 0) circumvents the classic restriction against CNAME records at the zone apex. The non-alias form (priority 1+) carries ALPN, port, and ECH parameters, reducing the round trips needed to negotiate a connection.
Adoption: As of mid-2025, measurements of the top 10K domains show over 18% serve HTTPS records, driven largely by Cloudflare and CDN deployments. Safari uses the HTTPS record as the trigger for QUIC (HTTP/3) connections.
Other Important Records
; PTR - Reverse DNS (IP -> name)
34.216.184.93.in-addr.arpa. IN PTR example.com.
; SRV - Service location (used by SIP, LDAP, XMPP)
_http._tcp.example.com. IN SRV 10 5 80 www.example.com.
; CAA - Certification Authority Authorization
example.com. IN CAA 0 issue "letsencrypt.org"
example.com. IN CAA 0 iodef "mailto:[email protected]"
; SSHFP - SSH public key fingerprint
example.com. IN SSHFP 2 1 123456789abcdef...
DNS TTL (Time To Live)
Every record has a TTL that tells resolvers how long to cache it. Common values range from 300 seconds (5 minutes) for fast-changing records to 86400 seconds (24 hours) for stable records like MX or NS.
; Short TTL for dynamic environments
example.com. IN A 93.184.216.34 300
; Long TTL for stable records
example.com. IN MX 10 mail1.example.com. 86400
DNS Resolution in Applications
Python DNS Examples
import dns.resolver
import dns.reversename
# Forward resolution
resolver = dns.resolver.Resolver()
answers = resolver.resolve('example.com', 'A')
for rdata in answers:
print(f"A: {rdata}")
# MX records with preference
answers = resolver.resolve('example.com', 'MX')
for rdata in answers:
print(f"MX: preference={rdata.preference}, target={rdata.exchange}")
# Reverse DNS
addr = dns.reversename.from_address('93.184.216.34')
answers = resolver.resolve(addr, 'PTR')
print(f"PTR: {answers[0]}")
# Query via a specific resolver
resolver.nameservers = ['1.1.1.1', '1.0.0.1']
answers = resolver.resolve('example.com', 'A')
print(f"Via Cloudflare: {answers[0]}")
Using Multiple DNS Servers with Fallback
import dns.resolver
DNS_SERVERS = ['8.8.8.8', '1.1.1.1', '208.67.222.222']
def resolve_with_fallback(hostname, rtype='A'):
for server in DNS_SERVERS:
try:
resolver = dns.resolver.Resolver()
resolver.nameservers = [server]
answers = resolver.resolve(hostname, rtype)
return [str(r) for r in answers]
except Exception:
continue
raise Exception(f"Failed to resolve {hostname}")
ips = resolve_with_fallback('example.com')
print(ips)
DNS Resolution with Caching
from dns.resolver import Cache
cache = Cache()
resolver = dns.resolver.Resolver()
resolver.cache = cache
# First call: performs live resolution and caches
ans1 = resolver.resolve('example.com', 'A')
# Second call: returns from cache (faster)
ans2 = resolver.resolve('example.com', 'A')
DNS in Modern Infrastructure
Round-Robin DNS for Load Balancing
Multiple A records for the same name cause resolvers to rotate responses, distributing traffic across servers:
api.example.com. IN A 203.0.113.10
api.example.com. IN A 203.0.113.11
api.example.com. IN A 203.0.113.12
This provides simple load balancing without a dedicated load balancer, but has no health checking — failed servers are not automatically removed.
Split-Horizon DNS (Split-Brain DNS)
Different DNS responses for the same domain based on the client’s network. Internal users resolve app.example.com to a private IP (10.0.1.50); external users get a public IP (203.0.113.50).
# BIND view configuration
view "internal" {
match-clients { 10.0.0.0/8; 172.16.0.0/12; };
zone "example.com" {
type master;
file "/etc/bind/db.example.com.internal";
};
};
view "external" {
match-clients { any; };
zone "example.com" {
type master;
file "/etc/bind/db.example.com.external";
};
};
DNS in Kubernetes (CoreDNS)
Kubernetes runs CoreDNS as its cluster DNS. Every pod uses it for service discovery — services resolve to their cluster IPs, and pods resolve via headless service records.
# A service named "api" in namespace "default" resolves to:
# api.default.svc.cluster.local
kubectl run test --image=busybox --rm -it -- nslookup kubernetes.default.svc.cluster.local
CoreDNS configuration lives in a ConfigMap:
apiVersion: v1
kind: ConfigMap
metadata:
name: coredns
namespace: kube-system
data:
Corefile: |
.:53 {
errors
health
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
fallthrough in-addr.arpa ip6.arpa
}
forward . /etc/resolv.conf
cache 30
reload
}
Kubernetes DNS search domains append automatically. A pod querying api actually tries api.default.svc.cluster.local, then api.svc.cluster.local, then api.cluster.local. Use the FQDN with a trailing dot to skip search domains in high-throughput paths.
EDNS Client Subnet (ECS)
ECS (RFC 7871) lets recursive resolvers pass a portion of the client’s IP prefix to authoritative servers. This enables geo-aware responses — a CDN can return a nearby edge IP even when the query arrives via a far-away resolver.
import dns.edns
import dns.message
import dns.query
# Set ECS subnet in a DNS query
msg = dns.message.make_query('example.com', 'A')
subnet = dns.edns.ECSOption('1.2.3.0', 24)
msg.use_edns(options=[subnet])
response = dns.query.udp(msg, '8.8.8.8')
for ans in response.answer:
print(ans)
DNS64 and NAT64
DNS64 (RFC 6147) synthesizes AAAA records from A records when the client is on an IPv6-only network that reaches the IPv4 internet through a NAT64 gateway. The resolver appends a well-known prefix (typically 64:ff9b::/96) to the IPv4 address.
Client (IPv6-only) → DNS64 Resolver → NAT64 Gateway → IPv4 Server
Example synthesis:
A record: 93.184.216.34
Synthesized AAAA: 64:ff9b::5db8:d822
DNS Tools and Debugging
dig Commands
# Basic query
dig example.com
# Specific record types
dig A example.com
dig AAAA example.com
dig MX example.com
dig TXT example.com
dig HTTPS example.com # SVCB/HTTPS record
# Trace the full resolution path
dig +trace example.com
# Query a specific resolver
dig @1.1.1.1 example.com A
# Reverse DNS
dig -x 93.184.216.34
# DNSSEC validation check
dig example.com +dnssec +multi
# Short output, just the answer
dig +short example.com
nslookup and whois
# Basic nslookup
nslookup example.com
# Specific type
nslookup -type=MX example.com
# Query a custom server
nslookup example.com 8.8.8.8
# Reverse lookup
nslookup 93.184.216.34
# Domain registration and nameserver info
whois example.com
Python Diagnostic Script
import socket
import time
domains = [('google.com', 'A'), ('cloudflare.com', 'A'), ('example.com', 'A')]
for domain, rtype in domains:
try:
start = time.time()
socket.gethostbyname(domain)
elapsed = (time.time() - start) * 1000
print(f"{domain}: {elapsed:.1f}ms")
except Exception as e:
print(f"{domain}: FAILED - {e}")
DNS Security
DNS Encryption Protocols
Plaintext DNS (UDP port 53) is visible to every network hop between client and resolver. Three encryption protocols protect the transport:
| Protocol | Port | Encryption | Adoption | Used By |
|---|---|---|---|---|
| DNS over TLS (DoT) | 853 | TLS | OS-level (Android, macOS) | systemd-resolved, Unbound |
| DNS over HTTPS (DoH) | 443 | TLS inside HTTP/2 | Browser-level (Firefox, Chrome) | Cloudflare 1.1.1.1, Google 8.8.8.8 |
| DNS over QUIC (DoQ) | 853 | QUIC/TLS 1.3 | Emerging (2025+) | AdGuard, aioquic |
DoH is harder to block (it looks like regular HTTPS on port 443). DoT uses a dedicated port, making it simpler to firewall but also easier to block. DoQ uses QUIC transport for 0-RTT connection establishment and better performance on lossy networks.
import requests
# Cloudflare DoH (RFC 8484 JSON endpoint)
DOH_URL = "https://cloudflare-dns.com/dns-query"
def resolve_doh(domain, rtype='A'):
headers = {'accept': 'application/dns-json'}
params = {'name': domain, 'type': rtype}
resp = requests.get(DOH_URL, headers=headers, params=params)
data = resp.json()
return [ans['data'] for ans in data.get('Answer', [])]
ips = resolve_doh('example.com')
print(ips)
For dedicated in-depth coverage, see the separate articles on DNS over HTTPS, DNS over QUIC, and DNS Security with DoH/DoT.
DNSSEC
DNSSEC adds cryptographic signatures to DNS records so resolvers can verify that responses are authentic and have not been tampered with. It does not encrypt queries — a network observer can still see which domains are queried.
# Verify DNSSEC with dig
dig example.com +dnssec
# Check if DNSSEC validation passed (ad flag)
dig example.com +dnssec +multi | grep "flags:"
# BIND DNSSEC configuration
options {
dnssec-enable yes;
dnssec-validation auto;
};
# Generate DNSSEC keys
dnssec-keygen -a RSASHA256 -b 2048 example.com
# Sign the zone
dnssec-signzone -o example.com db.example.com
Common DNS Attacks and Mitigations
| Attack | Mechanism | Mitigation |
|---|---|---|
| DNS spoofing / cache poisoning | Injecting forged DNS responses | DNSSEC, source port randomization |
| DNS amplification DDoS | Forging source IP to amplify small queries into large responses | Response Rate Limiting (RRL), BCP 38 |
| DNS tunneling | Encoding data in DNS queries for C2 or exfiltration | DNS filtering, traffic analysis |
| DNS hijacking | Redirecting queries to a rogue resolver | DNSSEC, registry locks, encrypted DNS |
| NXDOMAIN flood | Flooding with queries for non-existent domains | RRL, negative caching |
| Random subdomain attack | Querying random subdomains to exhaust authoritative server resources | RRL, anycast distribution |
DNS Filtering and Threat Blocking
DNS filtering blocks resolution of known malicious domains. Pi-hole operates as a DNS sinkhole at the network level, while enterprise solutions integrate threat intelligence feeds.
BLOCKED = {
'malware.example.com',
'phishing.bad-site.com',
}
def should_block(domain):
if domain in BLOCKED:
return True
for blocked in BLOCKED:
if domain.endswith('.' + blocked):
return True
return False
# Pi-hole dnsmasq-style blocklist entry
address=/doubleclick.net/0.0.0.0
address=/malware.example.com/0.0.0.0
DANE (DNS-Based Authentication of Named Entities)
DANE (RFC 6698) uses DNSSEC to publish TLS certificate associations in DNS via TLSA records. A client can verify that a server’s TLS certificate matches the TLSA record, reducing reliance on external certificate authorities.
; TLSA record for _443._tcp.example.com
; Usage: 3 (DANE-EE), Selector: 1 (SPKI), Matching: 1 (SHA-256)
_443._tcp.example.com. IN TLSA 3 1 1 \
123456789abcdef123456789abcdef123456789abcdef123456789abcdef12
Production Security Recommendations
| Practice | Rationale |
|---|---|
| Enable DNSSEC validation on all recursive resolvers | Prevents spoofed answers from reaching clients |
| Use DoH or DoT for stub-to-recursive traffic | Encrypts the last mile; prevents ISP surveillance |
| Deploy Response Rate Limiting (RRL) | Throttles amplification and NXDOMAIN attacks |
| Run anycast for authoritative servers | Distributes query load and absorbs DDoS traffic |
| Monitor DNS query logs for anomalies | Detects tunneling, data exfiltration, and beaconing |
| Use separate resolvers for internal vs external queries | Prevents internal hostname leakage |
DNS Operations
TTL Strategy
| Record Type | Typical TTL | When to Lower |
|---|---|---|
| A / AAAA (stable) | 3600 (1 hour) | Before planned IP changes: reduce to 300 (5 min) 24-48 hours ahead |
| A / AAAA (CDN) | 60-300 | CDNs expect short TTLs for fast failover |
| CNAME | 3600 | Match the target’s TTL |
| MX | 86400 (24 hours) | Mail server IPs rarely change |
| NS | 86400 | Nameserver delegations are stable |
| TXT (SPF/DKIM) | 3600 | Email authentication changes need fast propagation |
Multiple Nameservers and Anycast
# Production nameserver layout
nameservers:
- ns1.cloudprovider.com # Primary (AWS Route 53)
- ns2.cloudprovider.com # Secondary (same provider)
- ns3.otherprovider.com # Tertiary (different provider)
Deploy authoritative servers on an anycast network so the same IP address is announced from multiple locations. Queries automatically route to the nearest healthy instance, reducing latency and absorbing DDoS traffic.
Conclusion
DNS is the foundation of every internet interaction. The resolution path — from stub resolver through recursive resolvers and authoritative servers — involves multiple hops, caching layers, and fallback logic. Modern DNS adds encryption (DoH, DoT, DoQ), authentication (DNSSEC, DANE), and richer metadata (SVCB/HTTPS records) to a protocol originally designed for a trusted network.
Key takeaways for production deployments:
- Use encrypted transport (DoH/DoT/DoQ) for client-to-resolver traffic
- Enable DNSSEC validation and sign your zones
- Deploy authoritative servers on anycast with multiple providers
- Set TTLs appropriately and plan low-TTL windows before changes
- Monitor query patterns for signs of abuse (tunneling, amplification)
- Use CoreDNS for Kubernetes service discovery and split-horizon for internal/external separation
Related Articles
- DNS over HTTPS (DoH): Complete Guide
- DNS over QUIC (DoQ): Protocol and Performance
- DNS Security and DoH/DoT Complete Guide 2026
- HTTP/3 and QUIC: The Future of Transport Protocols
- TLS 1.3 Deep Dive
Resources
- RFC 1034 — Domain Names: Concepts and Facilities
- RFC 1035 — Domain Names: Implementation and Specification
- RFC 9460 — Service Binding and Parameter Specification via the DNS (SVCB and HTTPS)
- DNS over HTTPS (RFC 8484)
- DNS over TLS (RFC 7858)
- DNSSEC — RFC 9364
- CoreDNS — GitHub
- Cloudflare DNS Resolver (1.1.1.1)
- Kubernetes DNS for Services and Pods
Comments