General
The Kubernetes DNS Catastrophe: How ndots:5 Silently Drowns CoreDNS and Destroys P99 Latency
calricon / field note
If your microservices suffer from mysterious, intermittent 5-second latency spikes, stop profiling your application code. The culprit is almost certainly Kubernetes' default ndots:5 configuration, which silently multiplies every external DNS query into five sequential lookups and overwhelms CoreDNS.
The Kubernetes DNS Catastrophe: How ndots:5 Silently Drowns CoreDNS and Destroys P99 Latency
If your Kubernetes microservices suffer from intermittent, inexplicable 5-second latency spikes, stop profiling your application runtime, tuning your database connection pools, or blaming your cloud provider's network fabric. The culprit is almost certainly hiding in plain sight inside your pod's /etc/resolv.conf: the default ndots:5 setting. By instructing the Linux resolver to treat any domain name containing fewer than five dots as a relative local search query, Kubernetes silently amplifies every single external outbound API call into five to eight sequential DNS queries. Under heavy traffic, this invisible query explosion drowns CoreDNS, drops UDP packets, and triggers glibc's hardcoded 5-second retransmit timeout—turning high-throughput clusters into sluggish, fragile distributed bottlenecks.
The core premise is brutal in its simplicity: Kubernetes was designed under the assumption that internal service-to-service communication dominates your cluster, prioritizing convenient short-name resolution (like querying billing-service) over external network hygiene. But in modern enterprise architectures where workloads constantly communicate with managed cloud databases, third-party payment gateways, SaaS APIs, and external vector search endpoints, ndots:5 acts as an architectural distributed denial-of-service attack against your own DNS infrastructure.
The Anatomy of a 5-Second Latency Spike
To understand why P99 tail latency collapses in production, you have to look under the hood of standard Linux glibc DNS resolution. When a pod is scheduled, the kubelet populates /etc/resolv.conf with search domains and resolver options that look like this:
nameserver 10.96.0.10 search default.svc.cluster.local svc.cluster.local cluster.local options ndots:5
The ndots:5 parameter instructs the resolver: "If a requested domain name contains fewer than 5 dots, append the search path domains sequentially before querying the domain as an absolute, fully-qualified domain name (FQDN)."
Now, consider an ordinary backend service making an HTTPS request to an external third-party API, such as api.stripe.com. Because api.stripe.com contains only two dots (which is strictly less than five), the resolver assumes it is an internal cluster resource and generates the following cascade of queries in strict sequential order:
api.stripe.com.default.svc.cluster.local.→ CoreDNS replies NXDOMAIN (Not Found)api.stripe.com.svc.cluster.local.→ CoreDNS replies NXDOMAINapi.stripe.com.cluster.local.→ CoreDNS replies NXDOMAINapi.stripe.com.→ CoreDNS finally resolves the upstream public IP
What should have been a single, instantaneous DNS lookup has become four discrete UDP roundtrips to CoreDNS. If your microservice handles 1,000 requests per second to external services, your cluster is bombarding CoreDNS with 4,000 useless queries every second—over 75% of which return NXDOMAIN errors.
Why This Causes Intermittent Cluster Outages
Under normal, low-traffic conditions, CoreDNS answers these redundant queries in fractions of a millisecond, masking the underlying architectural inefficiency. But when traffic surges:
- UDP Buffer Overflows and Kernel Drops: Because DNS operates primarily over stateless UDP on port 53, Linux socket buffers on CoreDNS pods easily saturate under millions of synthetic NXDOMAIN requests. When a buffer overflows, the Linux kernel silently drops the packet.
- The 5-Second glibc Retransmit Cliff: Standard Linux glibc implementations (used across Debian, Ubuntu, and standard container base images) configure a default
RES_TIMEOUTof 5 seconds. If a UDP packet is dropped due to socket congestion or conntrack table exhaustion, glibc does not immediately retry; it blocks the calling thread and waits exactly 5,000 milliseconds before sending a retransmit. - Cascading Application Thread Starvation: When an HTTP client thread blocks for five seconds waiting on a dropped DNS query, upstream thread pools and connection workers quickly deplete. Upstream microservices experience timeout cascades, circuit breakers trip, and healthy services begin returning HTTP 504 Gateway Timeouts.
"A 5-second latency spike in a microservice is never a mystery; it is the cryptographic signature of dropped UDP packets interacting with glibc defaults."
The Conntrack Race Condition: The Linux Kernel Trap
The problem runs even deeper than CoreDNS pod capacity. On high-throughput Kubernetes worker nodes, every DNS query over UDP must navigate the Linux kernel's Netfilter connection tracking (conntrack) subsystem.
Because glibc issues parallel A and AAAA (IPv4 and IPv6) queries across two distinct UDP sockets with the same source port and destination port, it triggers a notorious Linux kernel race condition. The kernel's nf_conntrack module attempts to insert two entries with identical tuples into the conntrack hash table simultaneously, fails to resolve the lock, and silently drops one of the UDP responses with an insert_failed error. The result? Another unrecoverable 5-second timeout, completely independent of how many replicas or CPU resources you throw at CoreDNS.
The Modern Remediation Playbook: How Elite SRE Teams Fix DNS
Eliminating DNS-induced latency spikes requires abandoning Kubernetes defaults and implementing defensive network engineering:
- The One-Character Immediate Fix: Trailing Dots
The fastest, zero-infrastructure fix for external API calls is appending a trailing dot to external domain names in your application configuration:
Changehttps://api.stripe.comtohttps://api.stripe.com.
In DNS protocol syntax, a trailing dot explicitly declares a domain as an absolute Fully Qualified Domain Name (FQDN). When glibc encounters a trailing dot, it completely bypasses thendots:5evaluation and the search domain list, querying the root nameserver immediately. This single change instantly slashes outbound DNS query volume by up to 80% without modifying any cluster infrastructure. - Lower ndots in Pod Spec DNS Config
For microservices that communicate primarily with external APIs or fully qualified internal addresses, override the kubelet default by settingdnsConfigdirectly in your Deployment specification:spec: template: spec: dnsConfig: options: - name: ndots value: "2"Settingndots: 2ensures that any domain with at least two dots (likeapi.stripe.comorstorage.googleapis.com) is treated as an absolute FQDN on the first attempt. Keep in mind: if you reduce ndots, your application must use fully qualified names (service.namespace.svc.cluster.local) when communicating with pods across different Kubernetes namespaces. - Deploy NodeLocal DNSCache
The ultimate infrastructure solution for enterprise Kubernetes is deploying NodeLocal DNSCache as a DaemonSet across every node in the cluster.
NodeLocal DNSCache runs a lightweight caching agent on a local dummy network interface (169.254.20.10) on every physical host. Instead of sending UDP queries across the network to CoreDNS pods on other nodes, pods query the local host cache via loopback.- Cache Hits: Resolved in sub-millisecond local memory without touching network interfaces.
- Cache Misses: Forwarded from the node to CoreDNS using reliable, persistent TCP connections rather than drop-prone UDP, completely eliminating conntrack race conditions and packet drop timeouts.
- Leverage CoreDNS Autopath Plugin
If modifying application code or deploying NodeLocal DNSCache is infeasible, enable the CoreDNSautopathplugin. Autopath inspects the source pod's namespace on incoming queries and resolves the search path server-side in a single roundtrip, returning the resolved IP address on the first query instead of forcing the client to churn through multiple sequential NXDOMAIN queries.
The Strategic Verdict
Default configurations in distributed systems are designed for out-of-the-box developer convenience, not production scale. Treating your cluster's DNS infrastructure with the same architectural rigor as your primary database is essential for reliable scale.
Audit your CoreDNS metrics for NXDOMAIN surges, deploy NodeLocal DNSCache across your worker nodes, enforce absolute FQDNs for external traffic, and stop letting ndots:5 quietly sabotage your P99 SLA.