DNS
Backend Fundamentals · Core Backend Fundamentals · 9 min read
1. What problem does this solve?
Networks route IP packets using physical and logical addresses (IPv4 32-bit integers like 192.0.2.1 or IPv6 128-bit integers like 2001:db8::1). However, building software systems that rely on hardcoded IP addresses creates three major engineering problems:
- Human Usability: Humans cannot memorize or reliably manage thousands of raw numerical IP addresses across environments.
- Indirection and Decoupling: Software requires an abstraction layer between identity (e.g.,
api.payment.com) and location (192.0.2.1). An infrastructure migration, autoscaling event, failover, or network renumbering changes IP addresses. Without indirection, every client application globally would need to update its hardcoded configuration instantly whenever an IP changes. - Global Service Routing: A single global service needs to route requests to different IP addresses based on the client's geographic location, network conditions, system health, or load distribution.
The Domain Name System (DNS) solves these problems by providing a globally distributed, hierarchical, eventually-consistent lookup mechanism that translates human-readable domain names into IP addresses, routing instructions, cryptographic signatures, and arbitary metadata.
2. Mental model
The Simple Analogy
Think of DNS as a multi-tiered, globally distributed phone directory or postal lookup service:
- Root Level: The main switchboard that knows which phonebook covers which country code (
.com,.org,.io). - Top-Level Domain (TLD) Level: The regional registry office that maintains a directory of registered businesses within that jurisdiction (e.g.,
.comregistry knows who managesexample.com). - Authoritative Level: The company's internal operator directory that holds the exact extension for every department (
api.example.com\rightarrowExtension 101).
The Precise Engineering Model
Engineers should model DNS as a globally distributed, hierarchical, read-heavy, eventually-consistent Key-Value database with:
- Keys: Fully Qualified Domain Names (FQDNs), structured as dot-separated hierarchical node trees (e.g.,
api.v1.payment.example.com.). - Values: Sets of Resource Records (RRs) tagged with a specific record type (
A,AAAA,CNAME,MX,TXT, etc.). - Caching Strategy: TTL-based (Time-To-Live) write-through/read-aside caching at every tier (Kernel, OS, Local Resolver, ISP, Edge CDN).
- Partitioning Strategy: Horizontal domain delegating boundaries called Zones.
[ . ] (Root Zone)
/ \
[ com. ] [ io. ] (TLDs)
/
[ example.com. ] (Authoritative Zone)
/ \
[ api.example.com. ] [ db.example.com. ]
3. Deep explanation
DNS Record Types Every Backend Engineer Must Know
| Record Type | Purpose | Production Use Case |
|---|---|---|
| A | Maps hostname to an IPv4 address (32-bit). | Direct routing to an IPv4 load balancer or server. |
| AAAA | Maps hostname to an IPv6 address (128-bit). | Dual-stack network endpoints. |
| CNAME | Maps hostname to another hostname (Canonical Name alias). | Pointing a subdomain (app.example.com) to a CloudFront or AWS ALB hostname. |
| ALIAS / ANAME | Non-standard virtual record mapped to hostnames at the zone apex (example.com). | Bypasses CNAME zone apex limitations by returning resolved A/AAAA records at query time. |
| MX | Mail Exchanger. Maps domain to mail handling servers with preference weights. | Routing corporate email services (e.g., Google Workspace). |
| TXT | Arbitrary text data (up to 255 bytes per string, multiple allowed). | Domain ownership verification, SPF, DKIM, DMARC security policies. |
| NS | Nameserver record. Delegates a DNS zone to an authoritative server cluster. | Delegating subdomains to external managed services (e.g., AWS Route 53, Cloudflare). |
| SOA | Start of Authority. Zone administrative metadata (serial number, refresh timeouts, primary NS). | Zone sync synchronization between primary and secondary DNS servers. |
| SRV | Service record specifying port and hostname for services. | Service discovery protocols (Consul, Active Directory, SIP). |
| PTR | Pointer record for reverse DNS lookups (IP to Hostname). | Anti-spam validations, security audit logging. |
The Recursive vs. Iterative Query Resolution Engine
DNS resolution relies on two operational modes:
- Recursive Query: "Find the final IP for me; do not return until you have the answer or an error." Client machines and application servers send recursive queries to Recursive Resolvers (e.g., Cloudflare
1.1.1.1, Google8.8.8.8, or AWS VPC DNS169.254.169.253). - Iterative Query: "Give me the answer, or give me the address of the next server I should ask." The Recursive Resolver executes iterative queries down the hierarchy:
- Asks Root Server (
.)\rightarrowReturns TLD Server NS for.com. - Asks TLD Server (
.com.)\rightarrowReturns Authoritative NS forexample.com.. - Asks Authoritative Server (
ns1.p20.dynect.net)\rightarrowReturnsArecord93.184.216.34.
- Asks Root Server (
Application ---> Recursive Resolver ---> Root Server (.)
---> TLD Server (.com)
---> Authoritative Server (example.com)
Protocol Transports: UDP, TCP, DoT, DoH
- UDP Port 53: Primary protocol for standard queries. Low latency, zero-connection overhead. Historically limited to 512 bytes payload size.
- EDNS0 (Extension Mechanisms for DNS): Extends UDP payload buffer size up to 4096 bytes to allow DNSSEC signatures without forced TCP fallback.
- TCP Port 53: Used when DNS response payload exceeds UDP buffer limits, or for Zone Transfers (AXFR/IXFR) between primary and secondary nameservers.
- DNS over TLS (DoT - Port 853): Encrypts DNS queries inside TLS. Operates at the transport layer.
- DNS over HTTPS (DoH - Port 443): Encrypts DNS queries using standard HTTP/2 or HTTP/3 frames. Prevents middlebox tampering, ISP sniffing, and DNS hijacking.
The Dynamics of TTL and Cache Propagation
Every DNS record contains a TTL (Time-To-Live) field in seconds.
- Resolvers hold records in cache for
T_{\text{cached}} = \min(\text{Record TTL}, \text{Resolver Max TTL Config}). - Negative Caching: When a domain does not exist (
NXDOMAIN), resolvers cache the failure according to the TTL specified in the zone's SOA record (SOA Minimum TTLorSOA TTL). - Cache Invalidation Does Not Exist in DNS: There is no global "cache purge" protocol across recursive DNS servers worldwide. If you publish a record with a TTL of 86400 (24 hours), downstream clients and ISPs will serve stale data for up to 24 hours after you change it.
Node.js Platform Specifics: dns.lookup() vs. dns.resolve()
In Node.js, backend developers routinely introduce fatal performance bottlenecks by confusing the two native DNS resolution paths:
+-----------------------------------+
| Node.js Network Call (fetch/axios)|
+-----------------------------------+
|
v
+---------------------------------------+
| dns.lookup(hostname) |
+---------------------------------------+
|
v
+---------------------------------------+
| libuv Threadpool (Synchronous System) |
| Call to OS glibc getaddrinfo() |
+---------------------------------------+
/ | \
v v v
Reads /etc/hosts System DNS Cache OS Network Resolver
-
dns.lookup():- Uses the underlying operating system's
getaddrinfo(3)C-library function. - Synchronous and Threadpool Bound: Executed on the
libuvworker threadpool (default size = 4). High volume DNS queries usingdns.lookup()will exhaust threadpool threads, causing severe event-loop stalls for async file system calls, crypto, and other system IO operations. - Respects Operating System Rules: Reads
/etc/hosts, respects system search domains, and uses OS level configuration (nsswitch.conf). - Default for
http.request,fetch,axios,pg,redis, andmongoose.
- Uses the underlying operating system's
-
dns.resolve()/dns.resolve4()/dns.promises:- Uses
c-ares, a dedicated C library for asynchronous DNS requests. - Completely Non-blocking: Does not use the
libuvthreadpool. Makes direct UDP/TCP network socket calls to configured DNS servers. - Bypasses OS Configurations: Ignores
/etc/hostscompletely. Resolves strictly via network query to upstream nameservers.
- Uses
4. How it works
End-to-End DNS Resolution Flow
sequenceDiagram
autonumber
participant App as Node.js Application
participant OS as OS Stub Resolver / Cache
participant Rec as Recursive Resolver (e.g. 1.1.1.1)
participant Root as Root Nameserver (.)
participant TLD as TLD Nameserver (.com)
participant Auth as Authoritative NS (AWS Route53)
App->>OS: Resolve "api.example.com"
alt Cache Hit in OS
OS-->>App: Return Cached IPv4 (198.51.100.45)
else Cache Miss
OS->>Rec: Recursive Query: A api.example.com
alt Cache Hit in Resolver
Rec-->>OS: Return Cached IPv4 (TTL remaining: 240s)
else Iterative Walk Required
Rec->>Root: Iterative Query: A api.example.com
Root-->>Rec: Referral to .com TLD NS (NS records)
Rec->>TLD: Iterative Query: A api.example.com
TLD-->>Rec: Referral to Authoritative NS (ns-123.awsdns.com)
Rec->>Auth: Iterative Query: A api.example.com
Auth-->>Rec: Answer: 198.51.100.45 (TTL 300s)
Rec->>Rec: Cache Answer for 300s
Rec-->>OS: Return IPv4: 198.51.100.45
end
OS->>OS: Cache Answer in OS Layer
OS-->>App: Return IPv4 Address
end
Step-by-Step Execution Sequence
- Application Call: The Node.js HTTP runtime parses the URL
https://api.example.com/v1/orders. - OS Stub Lookup: Node.js delegates to the resolver interface. The OS checks its internal cache and system
/etc/hostsfile. - Recursive Dispatch: On cache miss, the OS sends a UDP datagram to the configured Recursive Resolver IP (e.g.,
169.254.169.253in AWS EC2). - Recursive Cache Check: The Recursive Resolver inspects its local memory cache.
- Root Delegation: If missing, it queries a Root Nameserver (
.) forapi.example.com. The Root returns the IP pointers for the.comTLD servers. - TLD Delegation: The Recursive Resolver queries the
.comTLD nameserver. The TLD server responds with theNSrecords of the domain (ns1.example.com). - Authoritative Query: The Recursive Resolver queries
ns1.example.comfor theArecord ofapi.example.com. - Authoritative Response: Authoritative server returns record
198.51.100.45along with TTL metadata (300seconds). - Caching & Return: The Recursive Resolver caches the record for up to 300 seconds, returns the payload to the client OS, which passes it to Node.js.
- Socket Connection: The application runtime opens a TCP handshake (
SYN) to198.51.100.45:443.
5. Practical implementation
Production-Grade Custom Asynchronous Caching Resolver
This production pattern solves the libuv threadpool starvation issue caused by default dns.lookup calls under high concurrent egress load. It utilizes non-blocking c-ares (dns.resolve4), caches host IPs in memory with strict adherence to DNS TTLs, and hooks natively into HTTP agents or undici.
import dns from 'node:dns/promises';
import http from 'node:http';
import https from 'node:https';
import { Resolver } from 'node:dns/promises';
interface CacheEntry {
address: string;
family: number;
expiresAt: number;
}
/**
* High-performance, non-blocking, TTL-aware DNS Resolver for Node.js microservices.
* Bypasses OS threadpool blocking and maintains in-memory lookup cache.
*/
export class AsyncCachingDnsResolver {
private cache = new Map<string, CacheEntry>();
private resolver: Resolver;
constructor(customNameservers?: string[]) {
this.resolver = new Resolver();
if (customNameservers && customNameservers.length > 0) {
this.resolver.setServers(customNameservers);
}
}
/**
* Signature matches node:net lookup function for direct injection into http.Agent
*/
public lookup = async (
hostname: string,
options: any,
callback: (err: NodeJS.ErrnoException | null, address: string, family: number) => void
): Promise<void> => {
try {
const result = await this.resolveHost(hostname);
callback(null, result.address, result.family);
} catch (err) {
callback(err as NodeJS.ErrnoException, '', 4);
}
};
/**
* Resolves IPv4 record with in-memory TTL caching via c-ares
*/
public async resolveHost(hostname: string): Promise<{ address: string; family: number }> {
const now = Date.now();
const cached = this.cache.get(hostname);
if (cached && cached.expiresAt > now) {
return { address: cached.address, family: cached.family };
}
try {
// Non-blocking UDP/TCP query via c-ares, retrieving TTL explicitly
const records = await this.resolver.resolve4(hostname, { ttl: true });
if (!records || records.length === 0) {
throw new Error(`ENODATA: No A records found for domain ${hostname}`);
}
// Pick a random record for basic client-side round-robin load distribution
const selected = records[Math.floor(Math.random() * records.length)];
// Calculate absolute expiry epoch (TTL is in seconds)
const ttlMs = Math.max(selected.ttl, 5) * 1000; // Enforce min 5s safety TTL
const expiresAt = now + ttlMs;
const entry: CacheEntry = {
address: selected.address,
family: 4,
expiresAt,
};
this.cache.set(hostname, entry);
return { address: entry.address, family: entry.family };
} catch (error) {
// If resolution fails, but we have an expired entry, fall back to stale cache (Graceful Degradation)
if (cached) {
console.warn(`[DNS Warning] Lookup failed for ${hostname}. Serving stale cache entry.`);
return { address: cached.address, family: cached.family };
}
throw error;
}
}
/**
* Purge expired cache entries periodically to avoid memory leaks
*/
public purgeExpired(): void {
const now = Date.now();
for (const [key, entry] of this.cache.entries()) {
if (entry.expiresAt <= now) {
this.cache.delete(key);
}
}
}
}
// ============================================================================
// Integration Example with Node.js HTTPS Global Agent
// ============================================================================
const customResolver = new AsyncCachingDnsResolver(['1.1.1.1', '8.8.8.8']);
// Clean up memory cache every 60 seconds
setInterval(() => customResolver.purgeExpired(), 60_000).unref();
// Custom HTTPS agent injecting non-blocking lookup engine
export const highThroughputHttpsAgent = new https.Agent({
keepAlive: true,
maxSockets: 100,
maxFreeSockets: 10,
timeout: 5000,
lookup: customResolver.lookup as any, // Custom lookup hook
});
// Demonstrating usage with standard HTTP request module
function makeRequest(urlStr: string) {
const url = new URL(urlStr);
const req = https.request(
url,
{
agent: highThroughputHttpsAgent,
method: 'GET',
},
(res) => {
console.log(`[HTTP Response] Status Code: ${res.statusCode} for ${url.hostname}`);
res.resume(); // Consume stream to free memory
}
);
req.on('error', (err) => {
console.error(`[HTTP Error] Request failed:`, err.message);
});
req.end();
}
// Example Invocation
makeRequest('https://aws.amazon.com');
6. Production considerations
Scalability and Global Traffic Routing
- Latency-Based Routing: DNS services (Route 53, Cloudflare) measure latency from global vantage points and route users to the region providing the lowest network round-trip time (RTT).
- Geo-DNS Routing: Resolves traffic based on the client's geographic continent, country, or state (useful for compliance, data sovereignty, and localized content delivery).
- Weighted Round Robin (WRR): Distributes inbound traffic across multiple endpoint targets (e.g., 90% to primary region, 10% to canary region).
Reliability & Resilience
- Multi-Provider DNS Architecture: Never rely on a single DNS vendor for high-availability production applications. If AWS Route 53 or Cloudflare suffers a global control plane outage, your service disappears. Run primary-secondary or dual-authoritative providers (e.g., Route 53 + NS1) with identical zone records.
- Emergency Migration Pre-warming: Before doing a cloud migration or datacenter switch, reduce your record TTL to 60 seconds at least 24-48 hours prior. This allows rapid cutback if the new target fails. Once stable, raise the TTL back to
300or3600seconds to reduce upstream query costs and downstream exposure to DNS outage events.
Security Defenses
- DNSSEC (DNS Security Extensions): Adds cryptographic RSA/ECDSA signatures to DNS records. Protects against DNS Cache Poisoning and Man-in-the-Middle (MitM) Spoofing by validating chain-of-trust signatures from Root down to the Authoritative zone.
- CAA Records (Certification Authority Authorization): TXT-like record that specifies which Certificate Authorities (e.g., Let's Encrypt, DigiCert) are allowed to issue TLS certificates for your domain name. Prevents rogue SSL cert issuance.
- DNS Amplification Mitigation: Standard UDP DNS requests can yield large responses from tiny queries. Attackers spoof victim IP addresses to generate massive DDoS traffic. Mitigated via Response Rate Limiting (RRL) and disabling open recursion on DNS servers.
Observability Metrics
Monitor DNS in backend environments via these key RED/USE metrics: *