Skip to main content

DNS Deep Dive: Resolution, Records, Security, and Modern Protocols

Published: February 21, 2026 Updated: May 20, 2026 Larry Qu 13 min read

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:

  1. 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.8 or 1.1.1.1).
  2. The recursive resolver checks its cache. On a miss, it queries a root nameserver.
  3. The root responds with a referral to the .com TLD nameservers.
  4. The recursive resolver queries a .com TLD nameserver.
  5. The TLD responds with the authoritative nameservers for example.com.
  6. The recursive resolver queries an authoritative nameserver.
  7. The authoritative server returns the requested record (e.g., an A record).
  8. The recursive resolver caches the result and returns it to the client.
  9. 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

Resources

Comments

👍 Was this article helpful?