isitdns? wiki/Tools/When it actually is DNS
Tools

When it actually is DNS

Asks the authority directly, which takes every resolver out of the path.

 dig -4 @coleman.ns.cloudflare.com example.isitdns.net
; <<>> DiG 9.20.11-0ubuntu0.2-Ubuntu <<>> -4 @coleman.ns.cloudflare.com example.isitdns.net
; (3 servers found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 19959
;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; WARNING: recursion requested but not available
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;example.isitdns.net. IN A
;; ANSWER SECTION:
example.isitdns.net. 300 IN A 192.0.2.1
;; Query time: 35 msec
;; SERVER: 172.64.35.64#53(coleman.ns.cloudflare.com) (UDP)
;; WHEN: Mon Sep 14 17:52:09 CDT 2026
;; MSG SIZE rcvd: 64

GO aa on the flags line and the record you expect

NO GO the right answer here and a wrong one from your resolver

what each part of this output means →

1. Stale TTL: the change you made an hour ago hasn't propagated#

Symptom: you updated the record. It still resolves to the old value.

Diagnostic: check the TTL the resolver hands back. Wrong and counting down means your resolver cached the old answer (RFC 1035 §6.1.3).

dig @1.1.1.1 example.isitdns.net | grep -A1 ";; ANSWER"
;; ANSWER SECTION:
example.isitdns.net.  220     IN      A       192.0.2.1

example.isitdns.net publishes a 300 s TTL, so an answer showing 220 was cached 80 seconds ago.

Workaround: query the authoritative server directly. That bypasses every cache in the chain:

dig -4 @coleman.ns.cloudflare.com example.isitdns.net

Real fix: lower the TTL on the record before you plan to change it. Wait one TTL-worth. Then change it. Then raise the TTL back.

2. Negative caching: NXDOMAIN cached longer than you expect#

Symptom: you created the record moments ago. It still returns NXDOMAIN.

The cause: before the record existed, your resolver asked, was told "no such name," and cached that negative answer for the SOA minimum field (RFC 2308, §3 for the exact rule: the lower of the SOA's own TTL and minimum). Cloudflare publishes both the same.

dig @1.1.1.1 SOA isitdns.net
isitdns.net.   1800  IN  SOA  coleman.ns.cloudflare.com. dns.cloudflare.com. \
  2409741473 10000 2400 604800 1800

The last number, 1800, is the negative-cache TTL. A resolver told "no such name" won't ask again for 1800 seconds, even after you create the record.

Workaround: flush the resolver, query a different resolver, or wait it out.

3. Missing AAAA: only happens on IPv6#

Symptom: the site is up. One particular user can't reach it.

The cause: that user's host is IPv6-only, with no DNS64 (RFC 6147). Your A is correct but you haven't published AAAA. Happy Eyeballs (RFC 8305) tries both, but not every app does, and the v6 path can be broken between the user and you even when it exists.

host.baseline.probe.isitdns.net is deliberately A-only for exactly this demo:

dig @1.1.1.1 +short A    host.baseline.probe.isitdns.net
dig @1.1.1.1 +short AAAA host.baseline.probe.isitdns.net
192.0.2.1

A returns 192.0.2.1; AAAA returns nothing (empty answer, NOERROR): the name exists but has no AAAA record. Dual-stack clients are fine here, they fall back to v4.

The real trap is a dual-stack name where AAAA also answers but the v6 path is broken. host.dual.probe.isitdns.net is the contrast: it publishes both, so an IPv6-only client gets an address with no guarantee the path to it works.

dig @1.1.1.1 +short A    host.dual.probe.isitdns.net
dig @1.1.1.1 +short AAAA host.dual.probe.isitdns.net
192.0.2.20
2001:db8::20

4. CNAME at the apex#

Symptom: the zone broke when you added a CNAME on example.com itself.

The cause: if a CNAME exists at a name, no other record may exist there (RFC 1034 §3.6.2, RFC 2181 §10.1). The apex must hold SOA and NS, so a CNAME there is illegal. DNSSEC's own RRSIG and NSEC records are the one carve-out (RFC 4035 §2.5).

; illegal: a real authoritative rejects this
example.com.  300 IN CNAME target.tld.
example.com.  300 IN SOA   ...
example.com.  300 IN NS    ...

Workaround: "CNAME flattening" resolves the target server-side and returns A records at the apex instead. That's a vendor feature, not standard DNS.

5. Missing glue records#

Symptom: you delegated sub.example.com to ns1.sub.example.com and now nothing resolves.

The cause: the parent zone delegates sub to a nameserver that lives inside sub itself, so the resolver needs glue: A/AAAA records the parent includes directly in its referral so the resolver can bootstrap without a circular lookup. See delegations for how referrals work.

Diagnostic:

dig +trace sub.example.com

Look for the referral step in the trace output. If the additional section of the referral response is empty, the glue records are absent.

6. Negative response → wrong rcode#

rcodeNameWhat it means
0NOERRORAnswer follows; an empty answer is no record of that type at this server (the name has none, has only names below it, or the server sent a referral)
2SERVFAILThe resolver gave up: auth unreachable, DNSSEC failed, etc. Read the attached Extended DNS Error before guessing (extended-dns-errors)
3NXDOMAINThe name doesn't exist (and won't, for the SOA minimum window)
5REFUSEDPolicy refusal (RFC 1035 §4.1.1): an authoritative server asked about a zone it doesn't host
9NOTAUTHDynamic-update / TSIG rcode (RFC 2136); rare on plain lookups
dig @1.1.1.1 nonexistent.invalid | grep "status:"
;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 12345

NOERROR with zero answers is not the same as NXDOMAIN. The first means "the name exists but no record of that type." The second means "the name doesn't exist at all." Caches handle them differently: see anatomy-of-a-query for how resolvers interpret each code.

DNSSEC-signed Cloudflare zones almost never say NXDOMAIN. Ask for a name that does not exist in a signed Cloudflare-hosted zone and you get NOERROR with an empty answer instead. Cloudflare synthesizes a covering NSEC at the queried name ("black lies," now standardized as RFC 9824, Compact Denial of Existence in DNSSEC). Prove it yourself:

dig @1.1.1.1 +https +dnssec nonexistent.isitdns.net A
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 64434
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 0, AUTHORITY: 4, ADDITIONAL: 1
nonexistent.isitdns.net. 1800 IN NSEC \000.nonexistent.isitdns.net. RRSIG NSEC TYPE128

status: NOERROR, zero answers, even though nothing is configured there. +https runs the query over DoH (RFC 8484), so a plaintext middlebox rewriting port 53 can't touch it. TYPE128 is the NXNAME meta-type that says the name genuinely does not exist. Unsigned Cloudflare zones are not affected and still return a real NXDOMAIN, which is why the demo above uses the reserved .invalid TLD.

Monitoring or mail logic that keys on NXDOMAIN misfires against signed Cloudflare zones.

7. UDP truncated → fell back to TCP → blocked by firewall#

Symptom: some queries work, some don't. The big ones don't.

The cause: DNS over UDP caps at 512 bytes by default (RFC 1035 §2.3.4); EDNS0 (RFC 6891) lifts it, and 1232 bytes is what most modern resolvers advertise, Cloudflare included. If the response is bigger, the server sets the truncated (TC) flag and the resolver retries over TCP/53. If your firewall drops outbound TCP/53, the retry fails silently.

txt.example.isitdns.net is a 30-byte TXT and its whole response is 94 bytes, too small to ever truncate. Use a large DNSKEY set and a small advertised buffer instead:

dig @1.1.1.1 +ignore +bufsize=512 DNSKEY dnssec.works
dig @1.1.1.1 +tcp DNSKEY dnssec.works

+bufsize=512 advertises a buffer too small for the answer, so the server sets TC and returns a header with no records. +ignore accepts that truncated response instead of retrying, so you can see it. +tcp forces TCP outright, which is what a resolver does once it sees TC.

8. DNSSEC validation failed#

Symptom: SERVFAIL, but the authoritative server is up.

The cause: the resolver tried to validate DNSSEC and the chain didn't verify. Common causes: bad key rollover, no DS matching a DNSKEY (RFC 4035 §5.2), system clock skew (signatures have a validity window), or a CDN serving stale signed data. See dnssec-troubleshooting for a systematic approach.

The Cloudflare DoH API names the exact fault in JSON. It rides HTTPS (RFC 8484), so plaintext port-53 rewriting can't touch it (a network that blocks DoH or inspects TLS still can, see section 9):

curl -s -H 'accept: application/dns-json' \
  'https://cloudflare-dns.com/dns-query?name=dnssec-failed.org&type=A'
{"Status": 2, "AD": false,
 "Comment": ["EDE(9): DNSKEY Missing no SEP matching the DS found for dnssec-failed.org."]}

Status: 2 is SERVFAIL. The Extended DNS Error (EDE) code 9 names the exact fault: no DNSKEY matching the DS at the parent (RFC 8914 §4.10). extended-dns-errors has the full code table and the reasons a failure can arrive with no EDE at all. A resolver only sets the ad flag on a query that itself set AD or DO (RFC 6840 §5.8), so a missing ad flag alone is not proof of failure; the DoH fields above are the unambiguous read.

delv is the BIND validator and gives a pass/fail verdict directly. It requires a full BIND install with crypto support (the macOS system delv at /usr/bin/delv ships without it):

delv @1.1.1.1 dnssec-failed.org
;; resolution failed: failure

1.1.1.1 already SERVFAILed the broken chain, so delv relays failure. Pointed at the chain directly, delv names the specific fault instead, for example ;; broken trust chain.

9. The router/firewall in front of you is rewriting DNS#

Symptom: dig @1.1.1.1 returns an answer 1.1.1.1 shouldn't know, an internal hostname resolves, or the answer matches your local network instead of the public Internet.

The cause: transparent DNS interception. Many routers, firewalls and captive portals rewrite outbound port-53 traffic so every query gets a local answer, whichever resolver you asked. The query never reaches 1.1.1.1; the local box hijacks it on the way out.

How to confirm: ask an address no resolver lives at:

dig @192.0.2.1 example.com

192.0.2.1 is a documentation address (RFC 5737): nothing answers there on the real Internet, so a clean path times out. An answer means something on the path answered for a resolver that does not exist.

Also ask for a name only the public Internet knows:

dig @1.1.1.1 +short example.com

A private (RFC 1918) address back means you've been intercepted too. The two tests catch different boxes: 192.0.2.1 catches an answer with no resolver behind it; 1.1.1.1 will not flag a forwarder that relays to a real resolver and passes its answer back unchanged. Confirm from a host that sits outside the gateway if you can.

Fix: disable transparent DNS interception at the router, or move to an encrypted transport. Port-53 rewriting can only touch plaintext, so a DoT or DoH session rides past it to the resolver you actually asked for:

kdig @1.1.1.1 +tls example.isitdns.net

kdig is from the Knot DNS tools package. +tls uses DNS-over-TLS on port 853 (RFC 7858), which the interception box cannot rewrite. example.isitdns.net resolves to 192.0.2.1 (A) and 2001:db8::1 (AAAA), both documentation addresses, so the answer is easy to verify.

On consumer gear that's usually enough. Enterprise networks can also block port 853 and known DoH endpoints, pin every device to an internal resolver, or terminate and inspect TLS, in which case there is no bypass.

10. The "name resolves but the service is down" trap#

Not DNS. example.isitdns.net publishes both an A (192.0.2.1) and an AAAA (2001:db8::1), both documentation-only and unroutable, which makes the point cleanly: DNS returns a valid answer, and the connection still goes nowhere.

dig +short A example.isitdns.net
192.0.2.1
curl -v --max-time 5 http://example.isitdns.net
*   Trying [2001:db8::1]:80...
*   Trying 192.0.2.1:80...

Trying <IP>... with no following Connected to means the resolve step returned an answer and the connect step failed: the fault is the service or the network path, not DNS. It doesn't rule out a wrong answer, though; a stale or hijacked record produces the same symptom, so confirm the IP is the one you expect before ruling DNS out.

See also#