DNS Troubleshooting on Linux: A Systematic Approach
\"It's always DNS\" is a joke because the failure modes are so scattered: resolver config, caching, search domains, split DNS. Here is where to actually look.
Key takeaways
- \"It's always DNS\" is a joke because the failure modes are so scattered: resolver config, caching, search domains, split DNS.
- Here is where to actually look.
On this page
DNS Troubleshooting on Linux: A Systematic Approach
"It's always DNS" is a running joke because DNS resolution on a modern Linux box passes through more layers than people expect: an application's own resolver library, nsswitch.conf, systemd-resolved's local stub, /etc/resolv.conf, search domains, and often a caching layer on top of all of it. A name that resolves correctly from one tool and fails from another usually means two of those layers disagree, not that DNS itself is broken. The fix is finding out which layer is lying.
Start with the tool that bypasses the most layers#
dig talks directly to a nameserver and skips the local resolver stack entirely, which makes it the right first tool: it tells you what the actual authoritative or upstream server thinks, independent of anything misconfigured locally:
$ dig +short api.example.com
203.0.113.42
$ dig api.example.com
; <<>> DiG 9.18.24 <<>> api.example.com
;; ANSWER SECTION:
api.example.com. 284 IN A 203.0.113.42
;; SERVER: 127.0.0.53#53(127.0.0.53)
;; WHEN: Mon Aug 26 14:02:11 UTC 2026
That SERVER: 127.0.0.53 line matters. On most current distros that's systemd-resolved's local stub listener, not your actual upstream DNS server. To skip the stub and query an upstream directly:
$ dig @8.8.8.8 api.example.com +short
203.0.113.42
If the direct query to a public resolver returns a different answer than the local stub, the problem is in local resolution (caching or config), not in DNS itself.
Find out what's actually answering locally#
$ resolvectl status
Global
Protocols: +LLMNR +mDNS -DNSOverTLS DNSSEC=no/unsupported
resolv.conf mode: stub
Link 2 (eth0)
Current Scopes: DNS
DNS Servers: 10.0.0.2 10.0.0.3
DNS Domain: ~.
resolvectl status (on distros using systemd-resolved) shows the real per-interface DNS servers and search domains, often more accurate than reading /etc/resolv.conf directly, because that file may just point at the local 127.0.0.53 stub rather than showing the real upstream servers systemd-resolved is actually using.
If the box doesn't run systemd-resolved, /etc/resolv.conf is the direct source of truth:
$ cat /etc/resolv.conf
nameserver 10.0.0.2
nameserver 10.0.0.3
search prod.internal example.com
options ndots:5
The ndots and search-domain trap#
ndots:5 (a common default in Kubernetes pods, inherited from many base images) means: if a name has fewer than 5 dots, try it against every search domain before trying it as-is. A lookup for api in a pod with search prod.internal.svc.cluster.local example.com doesn't try api first — it tries api.prod.internal.svc.cluster.local, then api.svc.cluster.local, and so on, only falling back to the bare name last, and that's the classic cause of DNS lookups that are slow (multiple failed queries before the real one) or resolve to the wrong thing entirely when an unexpected search domain happens to have a matching record.
$ dig +search +showsearch api
;; SEARCH LIST: prod.internal.svc.cluster.local example.com
If a hostname resolves differently (or times out longer) than a fully-qualified version of the same name, this is the first thing to check:
$ time dig api # walks the search list
$ time dig api.example.com # fully qualified, no search-list walk
Caching: three layers that can each be stale independently#
systemd-resolved's own cache: flush it and retest before assuming anything upstream changed:bash.bash$ sudo resolvectl flush-cachesnscdorunbound, if either is running on the box, cache independently ofsystemd-resolved. Check withsystemctl status nscd unbound; a box can be running more than one caching layer without anyone remembering why.- Application-level caching. The JVM, for example, caches successful DNS lookups forever by default (
networkaddress.cache.ttl) unless a security manager is active. This is a frequent cause of "the DNS record changed 20 minutes ago but the app is still hitting the old IP" even though the OS resolved correctly, the app just never asked again.
Reading DNS off the wire when nothing else explains it#
$ sudo tcpdump -i eth0 -n port 53
14:05:02.118331 IP 10.0.0.5.51422 > 10.0.0.2.53: 41231+ A? api.example.com. (33)
14:05:02.119820 IP 10.0.0.2.53 > 10.0.0.5.51422: 41231 1/0/0 A 203.0.113.42 (49)
If a query never shows up in the capture at all, the application isn't even reaching the network for DNS. It's serving from an in-process cache, a hosts-file entry, or NSS module (like nss-mdns or a container's /etc/hosts injection) further up the resolution chain than the network layer. getent hosts <name> shows you the result after the full NSS chain runs, which is the closest thing to "what will the application actually see":
$ getent hosts api.example.com
203.0.113.42 api.example.com
The fix in order#
dig @<upstream> <name>: bypass every local layer and confirm what DNS actually says at the source.resolvectl status(or/etc/resolv.confdirectly): confirm which servers and search domains the box is actually configured to use, not what you assume it's using.- If a bare or short name behaves differently than the FQDN, suspect
ndotsand the search-domain walk; check withdig +search +showsearch. - Flush
systemd-resolved's cache and check for a second caching daemon (nscd,unbound) before assuming the record itself is wrong. - If the app-level behavior still doesn't match what
dig/getentshow, suspect application-level DNS caching (the JVM is the classic offender); that's a restart or a cache-TTL config, not a DNS problem at all. tcpdump port 53when nothing above explains it: if the query never leaves the box, something upstream of the network (hosts file, NSS module, in-process cache) is answering first.
This pairs with Linux network debugging with tcpdump, ss, and eBPF for the rest of the network-layer diagnostic toolkit, and the broader Linux troubleshooting guide.
The call we'd make#
Reach for dig @<upstream> before anything else. It's the one query that isolates "is this actually a DNS problem" from "is this a local resolver, cache, or search-domain problem," and every other step in this guide exists to answer that second question once the first one is ruled out.
Get the DevOps Troubleshooting Cheat Sheet
Subscribe and get our free one-page reference for the errors that eat an afternoon — CrashLoopBackOff, OOMKilled, Terraform state locks, and more — plus new guides as we publish them.
Security Misconfiguration: The OWASP Category Nobody Talks About
Security misconfiguration quietly outranks flashier bugs as a top cause of breaches, yet teams rarely treat it as a real engineering problem.
SAST vs DAST: Which Security Testing Do You Need
A practical comparison of static and dynamic application security testing, what each catches, and how to combine them in your pipeline.
More from Linux
Explore more articles in this category
ext4 vs XFS vs Btrfs: Choosing a Filesystem for a Server
The default filesystem your distro picks is not always the right one for your workload. Here is what actually differs and when each one wins.
journald Log Management: Retention, Filtering, and Forwarding
journald is the default log sink on every systemd distro, and most of it runs on defaults nobody chose. Here is how to actually control it.
Resizing a Linux Filesystem Live with LVM (No Downtime)
The disk is full and the app is still running. Here is how to grow a logical volume and its filesystem underneath live workloads, safely.
You might have missed
Evergreen posts worth revisiting.