DNS Basic Troubleshooting
A field-tested, step-by-step method for diagnosing DNS failures - response codes, dig one-liners, and three real incidents that weren't what they first looked like.
Roy Hagland
Published 9 July 2026
DNS trouble has a bad reputation for being either trivially easy ("just flush your cache") or a black box you poke at until it works. Neither is true. Most DNS incidents follow one of a handful of patterns, and you can diagnose almost all of them with six commands and a clear head about what each response code actually means.
Start Wide, Then Narrow
Before touching any DNS tool, rule out whether it's DNS at all. This single step saves more time than any other in this article.
Ping the destination by IP address. If that fails too, you have a routing or firewall problem, not a DNS problem.
If ping-by-IP works but the hostname doesn't resolve, or resolves to the wrong thing, you're in DNS territory. Keep reading.
Note which client is affected. One laptop? One subnet? Every device that uses a specific resolver? The blast radius tells you where in the chain to look first.
Reading the Response Code, Not Just the Error
Every DNS answer carries a status code. Most people glance past it and go straight to guessing. The code alone usually tells you which layer broke.
NOERROR with an empty answer — the zone exists and the server is authoritative for it, but there's no record of the type you asked for. Classic cause: querying
AAAAfor a host that only has anArecord.NXDOMAIN — the name genuinely does not exist anywhere in that zone. Check for typos, or a record that was deleted, or a CNAME target that no longer resolves.
SERVFAIL — the server tried and failed. Usually one of: a broken delegation (parent zone points to a nameserver that doesn't answer), a timeout talking to an upstream, or a DNSSEC validation failure. This is the code most people misdiagnose as "DNS is down" when it's actually a signing problem.
REFUSED — the server is up and answering, but it has decided not to help you. Almost always an ACL: you're asking a server that doesn't allow recursion from your IP, or a zone transfer that isn't whitelisted.
The Six Commands That Solve Most DNS Tickets
dig is the standard on Linux and macOS, but it isn't installed on Windows by default — so every example below shows Linux/macOS, PowerShell, and classic nslookup together in one block.
1. Ask plainly, for exactly the record type you need
# Linux / macOS
$ dig +short mail.example.com MX
10 mail-in1.example-mx.net.
20 mail-in2.example-mx.net.
# Windows (PowerShell)
PS> Resolve-DnsName -Name mail.example.com -Type MX
Name Type TTL Section NameExchange Preference
---- ---- --- ------- ------------ ----------
mail.example.com MX 300 Answer mail-in1.example-mx.net. 10
mail.example.com MX 300 Answer mail-in2.example-mx.net. 20
# Windows (nslookup)
C:> nslookup -type=MX mail.example.com
Server: UnKnown
Address: 192.168.1.1
Non-authoritative answer:
mail.example.com MX preference = 10, mail exchanger = mail-in1.example-mx.net
mail.example.com MX preference = 20, mail exchanger = mail-in2.example-mx.net+short strips the noise. If this comes back empty, that's your NOERROR-empty-answer case above — you're asking for the wrong record type, or it was never created.
2. Bypass your resolver's cache and ask a specific server
# Linux / macOS
$ dig @1.1.1.1 app.example.com A +noall +answer
app.example.com. 287 IN A 93.184.216.34
# Windows (PowerShell)
PS> Resolve-DnsName -Name app.example.com -Type A -Server 1.1.1.1
Name Type TTL Section IPAddress
---- ---- --- ------- ---------
app.example.com A 287 Answer 93.184.216.34
# Windows (nslookup)
C:> nslookup app.example.com 1.1.1.1
Server: one.one.one.one
Address: 1.1.1.1
Name: app.example.com
Address: 93.184.216.34Compare this against what your default resolver returns. A mismatch here is the single most useful diagnostic in this whole list — it tells you the problem is in caching or resolver configuration, not in the authoritative zone itself.
3. Trace the delegation chain
# Linux / macOS
$ dig +trace app.example.com
example.com. 172800 IN NS ns1.example.com.
example.com. 172800 IN NS ns2.example.com.
app.example.com. 300 IN A 93.184.216.34
# Windows - no native one-shot trace; walk it by hand
C:> nslookup -type=NS example.com
example.com nameserver = ns1.example.com
example.com nameserver = ns2.example.com
C:> nslookup app.example.com ns1.example.com
app.example.com A 93.184.216.34This walks the query from the root down, the same path a resolver takes on a cold cache. If it stalls or times out at a particular hop, that's exactly where the delegation is broken. On Windows the first query asks who is authoritative for the parent zone, and the second asks that nameserver directly. For a real one-shot trace on Windows, install the ISC BIND tools (choco install bind-toolsonly) or run dig from WSL.
4. Force TCP when a response looks truncated
# Linux / macOS
$ dig +tcp example.com TXT
;; Truncated, retrying in TCP mode.
# Windows (PowerShell)
PS> Resolve-DnsName -Name example.com -Type TXT -TcpOnly
Name Type TTL Section Strings
---- ---- --- ------- -------
example.com TXT 3600 Answer {v=spf1 include:_spf.example-mx.net ~all}
# Windows (nslookup)
C:> nslookup
> set vc
> set type=TXT
> example.com
Server: UnKnown
Address: 192.168.1.1
Non-authoritative answer:
example.com text = "v=spf1 include:_spf.example-mx.net ~all"Classic DNS runs over UDP with a small packet ceiling. Large TXT records (SPF, DKIM) or DNSSEC signatures routinely exceed it, forcing a fallback to TCP port 53. If a firewall allows UDP/53 but blocks TCP/53, everything looks fine until someone adds a DKIM key — then that one record type silently fails while everything else keeps working. This is a very common "it was fine yesterday" incident. -TcpOnly is the clean way to force it in PowerShell; nslookup does the same with the interactive set vc ("virtual circuit") command.
5. Flush the local cache before blaming the server
# Windows
C:> ipconfig /flushdns
# Linux (systemd-resolved)
$ resolvectl flush-caches
# macOS
$ sudo dscacheutil -flushcache; sudo killall -HUP mDNSResponderA negative cache entry (a cached NXDOMAIN or SERVFAIL) can outlive the problem that caused it. Always clear it before declaring a fix has failed.
6. Check what the client is actually configured to ask
# Linux (systemd-resolved)
$ resolvectl status eth0
Link 2 (eth0)
Current Scopes: DNS
DefaultRoute setting: yes
Current DNS Server: 10.0.0.2
DNS Servers: 10.0.0.2 10.0.0.3
$ cat /etc/resolv.conf
nameserver 10.0.0.2
nameserver 10.0.0.3
# Windows (PowerShell)
PS> Get-DnsClientServerAddress -InterfaceAlias "Ethernet"
InterfaceAlias InterfaceIndex AddressFamily ServerAddresses
-------------- -------------- ------------- ---------------
Ethernet 12 IPv4 {10.0.0.2, 10.0.0.3}Half of the "DNS is broken" tickets I've handled turned out to be a client pointed at the wrong resolver entirely — a stale DHCP lease, a VPN split-tunnel pushing the wrong server, or a hardcoded resolver left over from a decommissioned box.
Three Incidents That Weren't What They Looked Like
"It works on my laptop but not on the app server"
Split-horizon DNS is the usual suspect here: the same hostname resolves to a different address depending on whether the query originates inside or outside the network. A laptop on the corporate LAN gets the internal answer; a server sitting behind a different perimeter (a VPS, a colo, a cloud VPC) gets the public one — or vice versa. Query both resolvers directly from each machine and compare the actual answers, not just whether the name resolves:
# Linux / macOS
$ dig @10.0.0.2 app.internal.example.com A +short
10.20.4.15
$ dig @1.1.1.1 app.internal.example.com A +short
203.0.113.44
# Windows (PowerShell)
PS> Resolve-DnsName -Name app.internal.example.com -Type A -Server 10.0.0.2 | Select IPAddress
IPAddress
---------
10.20.4.15
PS> Resolve-DnsName -Name app.internal.example.com -Type A -Server 1.1.1.1 | Select IPAddress
IPAddress
---------
203.0.113.44
# Windows (nslookup)
C:> nslookup app.internal.example.com 10.0.0.2
Name: app.internal.example.com
Address: 10.20.4.15
C:> nslookup app.internal.example.com 1.1.1.1
Name: app.internal.example.com
Address: 203.0.113.44Same name, two different, perfectly valid answers — that's split-horizon working exactly as designed. Nine times out of ten both resolvers are "working," they're just answering a different question.
Intermittent SERVFAIL that "fixes itself" a few minutes later
This is almost always DNSSEC. A zone gets a new record added, but the RRSIG signatures over that record set weren't regenerated before the change propagated, or a key rotation missed a step. Resolvers that validate DNSSEC will reject the whole answer as bogus and return SERVFAIL, even though the record itself is technically correct. It "fixes itself" once the signing catches up or the negative cache expires.
# Linux / macOS
$ dig +dnssec example.com A
;; ->>HEADER<<- opcode: QUERY, status: SERVFAIL, id: 4821
;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1
# Windows (PowerShell - nslookup has no DNSSEC detail)
PS> Resolve-DnsName -Name example.com -Type A -DnssecOk
Resolve-DnsName : example.com : DNS server failureLook at the returned RRSIG records and validation flags for a genuine failure rather than assuming it's a transient network blip. That status: SERVFAIL with no answer, on a name that resolves fine from a non-validating resolver, is the signature of a broken signature.
Email suddenly bouncing after a provider migration
Someone updates the MX record and it looks correct within minutes. Mail still bounces for two days. The usual culprit is TTL: the old MX record was cached with a long TTL (24 hours, sometimes higher) by every mail server that has ever queried it, and each one is happily using the stale answer until its own cache expires — completely independent of when you made the change. Before any DNS cutover, drop the TTL on the record you're about to change a day or two in advance, then make the change once the short TTL has actually propagated.
Quick Reference
# Windows client cache: view / clear
C:> ipconfig /displaydns
C:> ipconfig /flushdns
# Linux (systemd-resolved): stats / clear
$ resolvectl statistics
$ resolvectl flush-caches
# Authoritative check, bypassing all caches
$ dig @<nameserver> <name> <type>
C:> nslookup <name> <nameserver>
PS> Resolve-DnsName -Name <name> -Server <nameserver> -Type <type>
# Full delegation path (no native Windows equivalent - use WSL/BIND tools)
$ dig +trace <name>
# DNSSEC validation status
$ dig +dnssec <name>
PS> Resolve-DnsName -Name <name> -Type A -DnssecOk"It's always DNS" is a running joke precisely because the failure mode is rarely the protocol itself — it's caching, delegation, or a validation chain that's one signature out of date. Diagnose the layer, not the symptom.
Comments
Log in to comment
No comments yet. Be the first to comment.