Don't trust the browser: diagnosing DNS vs. connection failures

When a server appears down or sluggish, the first question is often: is this a DNS problem? Browser error messages seem like they should answer that, but they rarely do. Firefox, for instance, shows "Hmm. We're having trouble finding that site" when DNS resolution fails, yet it shows the nearly identical "Unable to connect" when the name resolves but the TCP connection fails. These prompts are too similar to be diagnostically useful, so the reliable approach is to drop to the command line.

Start with the error from your own code

If you are writing a program, its runtime will usually produce a clearer signal than a browser will. These messages rarely say "DNS" explicitly, but phrases like "unknown host," "name or service not found," or "getaddrinfo" are strong indicators. The exact wording varies by language and library. The same failed lookup in Python, Ruby, Java, and Node yields different messages, so if you see an unfamiliar one, search for it to confirm it means name resolution failed. A quick check like this in Python is often the first confirmation that you're dealing with DNS:

import requests
r = requests.get('http://examplezzz.com')
socket.gaierror: [Errno -2] Name or service not known

Confirm independently with dig

Even after a clear error message from your code, it pays to verify with dig. When setting up a new subdomain, a failed response like this means your nameserver is not answering for the name:

$ dig bananas.wizardzines.com
(empty response)

A successful lookup will instead show the record details and an ANSWER section, like this:

$ dig wizardzines.com
wizardzines.com.	283	IN	A	172.64.80.1

Query more than one nameserver

DNS servers don't always agree, since they cache different things at different times. When investigating, compare answers from your local resolver with a public one such as 1.1.1.1, 8.8.8.8, or 9.9.9.9 using dig @8.8.8.8 domain.com. A common gotcha is checking a domain in the browser before the DNS record exists. Your ISP may then cache that absence and keep returning an empty answer until the negative cache TTL expires. In that scenario, a dig against 8.8.8.8 may show the record is live, while your local server still returns nothing. To estimate when the negative cache clears, check the SOA record with dig SOA wizardzines.com; a TTL of 3600 seconds means about an hour of waiting.

Watch the queries go by

Sometimes the best way to find the culprit is to observe the DNS traffic directly with sudo tcpdump -i any port 53. Alternatively, Wireshark offers a GUI view, or the author’s own dnspeep tool provides friendlier, DNS-only output.

Real-world debugging stories illustrate why packet capture matters. In one case, websites took over ten seconds to load. Watching tcpdump showed the browser's DNS queries timing out and retrying after five-second gaps before a response finally arrived:

$ sudo tcpdump -n -i any port 53
12:05:01.125021 wlp3s0 Out IP 192.168.1.181.56164 > 192.168.1.1.53: 11760+ [1au] A? ask.metafilter.com. (59)
12:05:06.191382 wlp3s0 Out IP 192.168.1.181.56164 > 192.168.1.1.53: 11760+ [1au] A? ask.metafilter.com. (59)
12:05:11.145056 wlp3s0 Out IP 192.168.1.181.56164 > 192.168.1.1.53: 11760+ [1au] A? ask.metafilter.com. (59)
12:05:11.746358 wlp3s0 In  IP 192.168.1.1.53 > 192.168.1.181.56164: 11760 2/0/1 CNAME metafilter.com., A 54.244.168.112 (91)

The pattern points to a resolver that was dropping queries, which was resolved by restarting the router.

In another incident, nginx on a server was failing to redirect to a site. Running tcpdump on the server revealed the sequence: nginx asked for an A record, then an AAAA record; the DNS server answered NXDOMAIN for the A query but returned a successful AAAA answer with an IPv6 address:

$ tcpdump -i any port 53
17:16:04.216161 IP6 fly-local-6pn.55356 > fdaa::3.53: 46219+ A? myservice.internal. (42)
17:16:04.216197 IP6 fly-local-6pn.55356 > fdaa::3.53: 11993+ AAAA? myservice.internal. (42)
17:16:04.216946 IP6 fdaa::3.53 > fly-local-6pn.55356: 46219 NXDomain- 0/0/0 (42)
17:16:04.217063 IP6 fly-local-6pn.43938 > fdaa::3.53: 32351+ PTR? 3.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.a.a.d.f.ip6.arpa. (90)
17:16:04.218378 IP6 fdaa::3.53 > fly-local-6pn.55356: 11993- 1/0/0 AAAA fdaa:0:bff:a7b:aa2:d426:1ab:2 (70)
17:16:04.461646 IP6 fdaa::3.53 > fly-local-6pn.43938: 32351 NXDomain 0/1/0 (154)

The NXDOMAIN for the A record made nginx treat the domain as nonexistent, ignoring the AAAA response that followed. Per the DNS spec, that A query should have received NOERROR with an empty answer. This was a bug on the DNS server side, and such behavior is nearly impossible to diagnose without seeing the raw queries the application is making.

A tricky case: problems without visible failures

A DNS problem can exist even when every query succeeds. This can happen when an application does its own caching: it resolves a name once, then stops asking for updates. If the IP address changes days later, the application keeps using the stale address and starts throwing errors that look unrelated to DNS. The tell is not failed queries but missing queries — the application should be re-resolving the name but isn't. These cases are subtle because the runtime errors rarely point back to name resolution as the cause.