A typical case: you flip the CNAME for marketing.example.com from Webflow to a new self-hosted landing page, and the DNS console confirms the change. Five minutes later a colleague says the page still shows the old design on their laptop. You run dig +short marketing.example.com and get the new IP. Their laptop asks a different resolver, and that resolver may still hold the old record in its cache. Whose cache is wrong?
The honest answer is “nobody’s, the change just hasn’t reached every recursive resolver yet” — but you cannot prove that from one dig against one resolver. You need to ask several public recursives, fast, and compare. Historically that meant a CLI loop, a paid network monitoring tool, or a multi-region checker site. The browser was no help: web pages cannot open UDP port 53 sockets and therefore cannot speak classic DNS. That changed when RFC 8484 standardised DNS-over-HTTPS in 2018 and Cloudflare and Google shipped public DoH endpoints that accept cross-origin requests. A fetch() call can now ask for any record type in one HTTPS request, and the JSON endpoints answer in plain JSON.
ZeroTool’s DNS Lookup wraps those endpoints. It is the same fetch() you would write yourself, with record cards grouped by type, a dig-style raw view, and parallel queries for the eight most-asked types when you pick ALL. Below is the operational guide: how DoH actually works, what each record type tells you in practice, and the five gotchas that bite anyone debugging DNS from the wrong vantage point.
DNS-over-HTTPS in one paragraph
A traditional DNS query is a binary packet over UDP/53 (or TCP/53 when truncated). The browser does not expose a socket API for either. DoH solves this by encoding the same query as an HTTPS request. Two encodings exist: RFC 8484 binary (POST with application/dns-message body, base64url-encoded inside GET ?dns=) and the earlier JSON profile pioneered by Google and adopted by Cloudflare (GET ?name=...&type=... returning JSON). The JSON profile is not in any RFC. Cloudflare’s documentation says it follows Google’s schema, so the fields match, with the small differences covered below:
{
"Status": 0,
"TC": false,
"RD": true,
"RA": true,
"AD": false,
"CD": false,
"Question": [{ "name": "zerotool.dev", "type": 1 }],
"Answer": [
{ "name": "zerotool.dev", "type": 1, "TTL": 300, "data": "172.67.170.64" },
{ "name": "zerotool.dev", "type": 1, "TTL": 300, "data": "104.21.95.91" }
]
}
Status is the standard DNS RCODE (RFC 1035 §4.1.1, extended by RFC 6895). AD is the DNSSEC “Authenticated Data” flag: both providers document it as true only when every record in the answer was validated. RFC 6840 §5.8 says a resolver should set it only when the query sets the DO or AD bit, and without do=1 Cloudflare’s AD flag varies (see below). TC is the truncation flag; Cloudflare’s documentation says it is almost always false over DoH because the resolver supports the maximum response size.
The tool sends do=1 (DNSSEC OK) and cd=0 (Checking Disabled = 0). cd=0 asks the resolver to validate DNSSEC; do=1 asks it to include the signatures. Without do=1, Cloudflare’s AD flag is not reliable. On 2026-09-30 we sent each query 30 times without it: example.com A, AAAA and NS came back "AD": false every time, and MX came back true every time. An earlier run of 20 example.com A queries gave 10 true and 10 false. With do=1, and from Google either way, every answer had true. To rely on AD, send do=1. The opposite, cd=1, tells the resolver to skip validation — useful for forensic inspection of a broken zone but not what you want for normal lookups.
The ten record types, and what they tell you
The tool exposes ten types. Each answers a specific operational question. Pick the wrong one and you spend hours chasing a phantom misconfiguration.
| Type | Code | Asks | Common ops failure when missing or wrong |
|---|---|---|---|
A | 1 | ”What IPv4 address serves this name?” | Site loads on dev’s machine, 404 on someone else’s — stale A vs new A. |
AAAA | 28 | ”What IPv6 address serves this name?” | An AAAA that still points at an old host breaks the site for clients that prefer IPv6, while testers on IPv4-only networks see nothing wrong. |
CNAME | 5 | ”What canonical name does this alias point at?” | CNAME pointing at a deleted Heroku app, or pointing at the wrong tenant of a SaaS. |
MX | 15 | ”Which servers accept mail for this domain?” | All inbound email bounces with “no MX record found” or routes to the wrong provider after a migration. |
TXT | 16 | ”What policy strings are published?” | SPF says soft-fail, DMARC reports show legitimate mail flagged as spam, domain verification stuck at “pending”. |
NS | 2 | ”Who is authoritative for this zone?” | Registrar transfer left NS pointing at the old provider; updates do not propagate at all. |
SOA | 6 | ”Who is the primary server, and how often should secondaries refresh?” | Authoritative zone serving stale data because secondaries cached past the SOA refresh window. |
CAA | 257 | ”Which Certificate Authorities may issue for this domain?” | A CA that is not listed refuses to issue, so a renewal fails after a move to a new CA or CDN. |
PTR | 12 | ”What name does this IP claim to be?” | Mail bounces because the sending IP’s PTR does not match the HELO domain; reputation services flag the sender. |
SRV | 33 | ”Where does service _xmpp-client._tcp (etc.) live?” | XMPP, SIP, LDAP, or Matrix federation cannot find the host because SRV is missing or has the wrong port. |
The tool’s ALL mode fans out parallel Promise.all requests for the first eight types simultaneously, which is the right default for the most common operational question: “show me everything about this zone.” PTR and SRV are excluded from ALL because PTR requires you to type the in-addr.arpa form (e.g. 34.216.184.93.in-addr.arpa) rather than a domain name, and SRV requires a specific underscore-prefixed name (_xmpp-client._tcp.example.com). Both make sense as targeted single-type queries rather than as part of a sweep.
Why your dig and the tool can disagree
The most common surprise: dig on a developer laptop and DoH from a browser produce different answers for the same name. There are four boring reasons and one interesting one.
Local recursive cache. Your laptop, your router, or your corporate forwarder caches DNS for the duration of the TTL on each record. A TTL of 86400 (24h) on a CNAME means a change at the authoritative server can take up to 24 hours to reach your laptop. The DoH call asks Cloudflare or Google, whose caches are independent of yours. Run the lookup once with Cloudflare and once with Google and compare the answers — if Cloudflare returns the new value and Google returns the old, you are watching propagation in progress.
Split-horizon DNS. Corporate networks, VPNs, and Active Directory environments routinely run an internal authoritative resolver that returns different answers for the same name. internal-tools.example.com may resolve to 10.42.0.5 for anyone behind the firewall and NXDOMAIN for anyone outside. The DoH call sees the outside view. If the tool returns NXDOMAIN for a name that works on your work machine, the zone is split-horizon and the public DNS does not know about that name.
Geo-routed answers. Cloudflare, Akamai, AWS Global Accelerator, and most CDNs return different A/AAAA records depending on which edge POP receives the query. The IP you see in the tool is the one Cloudflare or Google’s recursive saw from their network position, not yours. For “is this IP correct?” questions, the tool is the right answer for a generic public viewer; for “is this IP correct from my POP?” you need to query from that POP.
EDNS Client Subnet (RFC 7871). Some authoritative servers return more accurate geo-routed answers when the recursive resolver forwards a hint about the client’s subnet. Google Public DNS “normally sends approximate network information (usually zeroing out the last part of your IPv4 address)”; Cloudflare’s FAQ says 1.1.1.1 does not send ECS. The same geo-routed name can therefore resolve to different IPs through the two, and Google’s answer depends on where the query comes from. Google’s JSON API lets you set the subnet with the edns_client_subnet parameter (the tool does not send it). On 2026-09-30, www.qq.com returned 121.14.77.201 and 121.14.77.221 for 114.114.114.0/24, 43.159.109.55 for 8.8.8.0/24, and 43.168.224.173 for 139.130.4.0/24. Cloudflare ignored the same parameter and returned 43.159.109.55.
The interesting one: stale negative caches. RFC 2308 specifies that NXDOMAIN responses are cacheable. Because the missing record has no TTL of its own, the negative-cache time is the smaller of the SOA record’s TTL and its MINIMUM field (RFC 2308 §5). If a zone was misconfigured for an hour and your local recursive cached the NXDOMAIN, the tool can return the correct answer while every local query keeps failing until that time runs out. The fix is to wait — or to lower the SOA MINIMUM before a zone change.
Reading the summary pills
Five pills sit above the records view. Each compresses one operational signal:
- NOERROR / NXDOMAIN / SERVFAIL — the RCODE. NOERROR means the query was processed correctly; the zero records that may accompany it mean “this name exists but has no records of that type”. NXDOMAIN means the name does not exist anywhere in the zone. SERVFAIL means the recursive resolver could not complete the query — often DNSSEC validation failed, or the authoritative servers returned an error.
- N records — count of records in the Answer section. When NOERROR comes back with 0 records, the chosen type is the wrong question for this name. Check NS for the zone you actually want.
- DNSSEC ✓ / no DNSSEC / DNSSEC failed — the AD flag. A “no DNSSEC” pill means the zone is unsigned or a CNAME in the answer leads into an unsigned zone; a signature that fails validation returns SERVFAIL instead. When the SERVFAIL carries a DNSSEC Extended DNS Error (codes 1, 2 and 5 to 12 in RFC 8914), the pill reads “DNSSEC failed”. Any other SERVFAIL hides the pill, because the resolver sent no answer to judge. The
com,organddevTLDs are signed (each has a DS record in the root zone), but a domain under them is signed only if its owner turns DNSSEC on:example.comis,zerotool.devis not. - Resolver — which DoH endpoint answered. Useful when comparing Cloudflare and Google side by side.
- Latency — round-trip from
fetch()start to JSON parse. The first lookup includes the HTTPS connection setup to the resolver, so it is slower than the ones after it.
Writing the same query yourself
The tool exists because clicking beats scripting for one-off lookups. For automation, the DoH JSON profile is short enough to drop into any language with fetch. Here are three reference implementations, each useful in a different context.
The browser / Node 18+ version is two lines once you have a domain:
const url = `https://cloudflare-dns.com/dns-query?name=${encodeURIComponent('zerotool.dev')}&type=MX&do=1&cd=0`;
const res = await fetch(url, { headers: { accept: 'application/dns-json' } });
const json = await res.json();
console.log(json.Answer); // [{ name, type, TTL, data }, ...]
Add AbortSignal.timeout(5000) if you want a timeout, and a try/catch if the network might fail. Both Cloudflare and Google DoH return JSON shapes that match the one above, so you can switch providers by swapping the URL.
The Python version uses httpx or urllib. With the standard library only:
import json
import urllib.request
def doh(name: str, qtype: str, resolver: str = "cloudflare"):
if resolver == "google":
url = f"https://dns.google/resolve?name={name}&type={qtype}&cd=0"
headers = {}
else:
url = f"https://cloudflare-dns.com/dns-query?name={name}&type={qtype}&cd=0"
headers = {"accept": "application/dns-json"}
req = urllib.request.Request(url, headers=headers)
with urllib.request.urlopen(req, timeout=5) as resp:
return json.loads(resp.read())
print(doh("zerotool.dev", "CAA"))
For shell scripts and CI steps, curl plus jq is the canonical pairing. The following one-liner pulls every MX target for a domain, sorted by priority:
curl -s -H 'accept: application/dns-json' \
'https://cloudflare-dns.com/dns-query?name=cloudflare.com&type=MX' \
| jq -r '.Answer[] | "\(.data)"' | sort
Wrap it in a for loop and you have your own propagation tracker without leaving the terminal. Wrap it in a Bash function and put the function in ~/.bashrc and you have replaced half of dig for ad-hoc work.
TXT segmentation — the 255-byte rule
Long SPF, DKIM, and DMARC records routinely overflow the 255-byte limit that RFC 1035 §3.3.14 imposes on each individual string inside a TXT record. The DNS protocol allows multiple strings per TXT record; the convention for SPF (RFC 7208 §3.3) is to concatenate them with no separator to form the logical record. The two JSON APIs present the strings differently: Cloudflare keeps each string in quotes and separates strings with a space, as dig prints them, while Google joins them into one value without quotes. Cloudflare’s form:
{
"name": "google.com",
"type": 16,
"TTL": 300,
"data": "\"v=spf1 include:_spf.google.com ~all\""
}
For a longer record you get two adjacent strings:
{
"data": "\"v=DMARC1; p=quarantine; rua=mailto:dmarc-reports@example.com; \" \"fo=1; aspf=r; adkim=r\""
}
The tool renders the value exactly as the resolver delivered it. To get the logical value from Cloudflare’s form, strip the outer quotes and the inter-segment quote-space-quote (" "); SPF (RFC 7208 §3.3) and DKIM (RFC 6376 §3.6.2.2) both say to join the strings with nothing in between. Google’s form is already joined. For the DKIM key at cf2024-1._domainkey.zerotool.dev, Cloudflare returned 425 characters with one " " join after the first 255-character string, and Google returned the same key as 420 characters.
CAA — the one record that breaks TLS issuance
CAA (RFC 8659) tells public Certificate Authorities which of them are permitted to issue for a domain. A compliant CA must check CAA before issuing, and a CA that no issue record names must not issue (RFC 8659 §3). Forget to update CAA when you move to a CDN or CA that uses another issuer, and the next certificate renewal fails. No other DNS query shows the restriction — only a CAA lookup tells you it is in place.
A zone that allows Let’s Encrypt and Google Trust Services (pki.goog) and blocks wildcard certificates looks like:
example.com. 0 issue "letsencrypt.org"
example.com. 0 issue "pki.goog"
example.com. 0 issuewild ";"
example.com. 0 iodef "mailto:security@example.com"
The issuewild ";" denies all wildcard issuance. The iodef tells CAs where to mail a report if they receive a request from an unauthorised issuer. The tool’s CAA query reports each record with its tag (issue, issuewild, iodef) and value, which lets you confirm the policy before triggering an issuance.
Pitfalls
MX with priority 0 and . target. RFC 7505 defines a “Null MX” record — 0 . — meaning “this domain accepts no email, full stop”. If your tool returns 0 . for a domain you expected to handle mail, the registrar or DNS provider published the placeholder explicitly. Updating it requires editing the zone, not a propagation wait.
CNAME at the apex is illegal. A name that has a CNAME cannot hold any other record (RFC 1034 §3.6.2, RFC 1912 §2.4), and the zone apex (example.com) must hold SOA and NS. Cloudflare (CNAME flattening) and Route 53 (alias records) offer records that look like a CNAME at the apex but are answered as A/AAAA at query time. A DoH query for the apex of such a zone returns the flattened A/AAAA, not the CNAME. This is correct behaviour but can confuse someone expecting to see the CNAME they configured in the dashboard.
Private resolvers and Pi-hole. On a network running Pi-hole, NextDNS, AdGuard Home, or any DNS-based content filter, the normal lookups of your browser and OS go to that filter, which may rewrite or block responses. The tool’s query travels over HTTPS to the public resolver, so the filter does not see the name you look up; the browser still resolves cloudflare-dns.com or dns.google through local DNS, though, and a filter can block those two names. This is why the tool can return the real answer when your laptop’s normal lookup fails. Sometimes that helps (debugging a misconfigured Pi-hole rule) and sometimes it misleads (the tool reports a domain works while your network blocks it).
EDNS Client Subnet and CDN testing. When testing geo-routing for a CDN, the IP returned by DoH reflects Cloudflare’s or Google’s view, not yours. To test from specific regions, use a multi-region checker such as DNS Checker, or run dig on a cloud VM in that region. ZeroTool’s tool answers “what does this public resolver return?”, not “what would my user in São Paulo see?”.
DNSSEC failures look like server failures. If a zone signs its DNSSEC chain incorrectly, both Cloudflare and Google return SERVFAIL with AD=false, the same code a resolver returns when it cannot reach the zone at all. The resolver notes under the records tell the two apart: for dnssec-failed.org, Cloudflare’s note is EDE(9): DNSKEY Missing ... (Extended DNS Error 9, RFC 8914). Google sends a sentence that starts “DNSSEC validation failure” plus an extended_dns_errors field with code 9, which the tool shows as EDE(9): No DNSKEY matches DS RRs of dnssec-failed.org. With either resolver the pill reads “DNSSEC failed”. The tool always sends cd=0, the correct default for everyday lookups; to see the unvalidated answer use dig +cd, and for a full analysis of the chain use DNSViz.
Where this fits among DNS lookup tools
Each tool below answers a different question.
DNS Checker checks records “against a selected list of DNS servers in multiple regions worldwide” from its own servers and draws a propagation map. It answers “has this change reached every region yet?”.
Google Admin Toolbox Dig sends the query to its own backend (/apps/dig/lookup). In a test on 2026-09-29 it showed “Record not found!” for dnssec-failed.org, with rcode SERVFAIL only in its raw view, and its flags line for the signed example.com read QR RD RA, without AD.
dig and kdig are the CLI reference implementations, the right answer for scripting and for queries that need authoritative-server targeting (dig @ns1.example.com).
ZeroTool’s tool fills the in-between case: a developer who wants a quick lookup from the browser, on a machine where installing dig would mean adding a Homebrew package or a WSL distro, and who wants the public resolver’s own answer rather than one relayed by a lookup site’s server. You can switch between Cloudflare and Google and compare, see the AD flag and the resolver’s notes, and copy the raw JSON. It is not the right tool for propagation maps or authoritative-only queries; for those, the tools above are better.
Further reading
Internal:
- HTTP Header Analyzer — once DNS resolves, what the server returns.
- SSL Certificate Decoder — confirm the certificate served at the resolved IP matches the hostname.
- URL Parser — break a URL into protocol, host, port, path, query before resolving the host.
- IP Subnet Calculator — once you have A/AAAA, derive subnet structure for firewall rules.
External:
- RFC 8484 — DNS Queries over HTTPS — the DoH standard.
- Cloudflare DoH documentation — endpoint, parameters, JSON profile.
- Google Public DNS DoH — Google’s documentation for
dns.google/resolve. - RFC 1035 — Domain Names — the original DNS specification; still the most useful reference for record formats.
- RFC 7208 — Sender Policy Framework — SPF, with the TXT segmentation rules.
- RFC 8659 — CAA — Certification Authority Authorization.
- RFC 6891 — EDNS(0) — the extension mechanism that EDNS Client Subnet rides on.