DNS stopped answering "where"
The textbook definition treats DNS as a pure function: same question, same answer, always. Run the same domain through a plain resolver and an ad-blocking one and you get opposite answers in the same second. Five live queries, lamest to strangest, trace where the opinion comes from — and a script against twenty-one well-known domains turns "one outage takes the web down with it" into a checkable number.
- Same domain, two resolvers, opposite answers — reproduced live
- A real bot-mitigation response, header and all: cf-mitigated: challenge
- 12 of 21 well-known domains share a Cloudflare or AWS nameserver
- An ENS name that still resolves through an ordinary AWS gateway
Ask a resolver for doubleclick.net and you get a real answer — a Google IP address, routable, reachable, correct by every definition DNS has ever used.
Ask a different resolver the same question, at the same moment, and you get 0.0.0.0. An address that resolves to nothing, on purpose.
$ dig @1.1.1.1 doubleclick.net A +short
142.251.142.110
$ dig @94.140.14.14 doubleclick.net A +short
0.0.0.0
Same domain, same protocol, same second. Two machines disagreeing about a fact.
RFC 1035 describes DNS as a lookup: a name goes in, an address comes out, and the answer does not depend on who asked.1 For most of the protocol’s history that description was close enough to true that nobody needed the exception. It stopped being true sometime in the last decade, and the industry kept using the old sentence anyway.
The second resolver above is not malfunctioning. It is AdGuard’s public DNS, and 0.0.0.0 is not an error — it is a decision, encoded as an address. Somewhere between “look up a name” and “return an address,” DNS picked up an opinion. This piece traces where that opinion comes from, running the same kind of query at five different points on a scale from the most boring version of DNS to the strangest, and ends with a number: how many ordinary, well-known sites already share the outcome of that opinion whether they chose to or not.
One keystroke, four companies
The mental model most engineers still carry is 1987’s: one machine asks another machine a question over UDP, gets one answer back, done. That model survives exactly as long as nobody traces a real lookup.
$ dig +trace danstativa.dev A
The root servers hand off to whoever runs .dev — Charleston Road Registry, a Google subsidiary, because Google operates .dev as a closed, DNSSEC-required top-level domain:
dev. 172800 IN NS ns-tld1.charlestonroadregistry.com.
dev. 172800 IN NS ns-tld2.charlestonroadregistry.com.
dev. 172800 IN NS ns-tld3.charlestonroadregistry.com.
Charleston Road Registry hands off to this site’s actual authoritative nameservers — which are not run by this site’s owner:
danstativa.dev. 10800 IN NS alex.ns.cloudflare.com.
danstativa.dev. 10800 IN NS ursula.ns.cloudflare.com.
And Cloudflare hands back the address a browser actually connects to — not an origin server, an anycast address that could be answered from a data center on any continent:
danstativa.dev. 300 IN A 188.114.97.8
Along the way, the trace tried IPv6 first and failed — network unreachable — and quietly fell back to IPv4 without asking anyone. That fallback is not edited out here, because it is the same point in miniature: a routing decision got made, silently, before a human noticed a keystroke had happened.
Four organizations touched one page load: whoever ran the query, the root system, Google — as .dev’s registry — and Cloudflare, as this domain’s nameserver and its edge. None of them is the person who registered the domain and pays for it every year. That is not a complaint about any one of the four. It is a description of what “type a name, press enter” now costs, on a site small enough that its entire authoritative zone fits in two lines.
A lookup used to return a fact. Somewhere along the way, it started returning whoever answered first.
From a fact to a gate to a different chain entirely
Every answer a resolver hands back today is one of five things, and lining them up from the most boring to the strangest makes the pattern legible.
A fact nobody disputes. The textbook case, still true for most queries:
$ dig example.com A
;; ANSWER SECTION:
example.com. 253 IN A 172.66.147.243
example.com. 253 IN A 104.20.23.154
;; Query time: 0 msec
;; SERVER: 127.0.0.53#53(127.0.0.53) (UDP)
One UDP packet out, one back, an address nobody argues with. This is what “DNS” means in the RFC, and it is still what most queries look like.
A fact assembled by four organizations. The danstativa.dev trace above — same protocol, same UDP, but the “one answer” turned out to be a handoff chain with opinions embedded in it (which registry, which nameserver, which edge).
A fact turned into an opinion. The doubleclick.net divergence from the opening — plus the data point it left out. AdGuard’s family filter, one tier stricter than its default, gives the same null answer for the same reason:
$ dig @94.140.14.15 doubleclick.net A +short
0.0.0.0
Two separate policies, same domain, same verdict. The protocol carries the opinion exactly as faithfully as it carries a fact — there is no wire-level difference between an address and a policy dressed as one.
The question wrapped in a lock. DNS-over-HTTPS answers the same question, encrypted, over port 443 instead of port 53:
$ curl -H 'accept: application/dns-json' \
'https://cloudflare-dns.com/dns-query?name=example.com&type=A'
{"Status":0,"Answer":[
{"name":"example.com","type":1,"TTL":255,"data":"104.20.23.154"},
{"name":"example.com","type":1,"TTL":255,"data":"172.66.147.243"}
]}
Same two addresses as the plain query. What changed is cost, and it is measurable: five plain UDP lookups against five distinct domains averaged 4.6 ms; five DoH lookups against five different domains, each opening a fresh TLS connection, averaged 27.1 ms — roughly six times slower, almost all of it the TLS handshake.2 Privacy is not free. It costs about six times the round trip, measured here, not asserted — and a browser that reuses the TLS connection across many queries pays that cost once per session rather than once per lookup, which is the honest reason this number is worse than what a real browsing session actually experiences.
The answer withheld until you prove something. Resolution can succeed completely and the page still refuses to render:
$ curl -sI -A "curl/8" https://www.discogs.com
HTTP/2 403
cf-mitigated: challenge
content-length: 5385
The DNS answer was fine. What comes after it is a judgment call — human or not — made by whichever mitigation vendor sits in front of the site, and the party making that call is neither the site owner nor the visitor.

Resolution already finished. Whether the page loads is a separate decision, made by whoever runs the rope line. Photo by Jakub Żerdzicki on Unsplash.
The name isn’t even DNS’s anymore, and it still ends up in the same place. ENS lets a .eth name replace an IP lookup with a smart-contract read, off-chain of every registry above. In practice, a browser still needs a gateway to turn .eth into something fetchable:
$ dig +short NS eth.limo
ns-48.awsdns-06.com.
ns-814.awsdns-37.net.
ns-1689.awsdns-19.co.uk.
ns-1382.awsdns-44.org.
$ dig +short A eth.limo
35.71.142.77
52.223.52.2
$ curl -sI https://vitalik.eth.limo
HTTP/2 200
x-ipfs-path: /ipns/vitalik.eth
cache-control: max-age=300, must-revalidate
The gateway that makes a decentralized name usable is itself an ordinary domain, on AWS Route 53, with AWS IPs. The content behind it is genuinely content-addressed, on IPFS — that part is real. But the lookup that gets you there did not escape the centralized graph. It just added one more hop through it.

The package is content-addressed. The belt that carries it to your door is not. Photo by Hyundai Motor Group on Unsplash.
How many sites share your single point of failure
Five examples make a pattern feel real. They do not make it checkable. This does:
for d in cloudflare.com shopify.com openai.com github.com stackoverflow.com \
wikipedia.org mozilla.org apple.com bbc.co.uk nytimes.com amazon.com \
netflix.com spotify.com airbnb.com reddit.com twitch.tv notion.so \
vercel.com anthropic.com linkedin.com discord.com; do
ns=$(dig +short NS "$d")
if echo "$ns" | grep -qi cloudflare; then op="Cloudflare"
elif echo "$ns" | grep -qi awsdns; then op="AWS Route 53"
elif echo "$ns" | grep -qi azure-dns; then op="Azure DNS"
elif echo "$ns" | grep -qi akam; then op="Akamai"
else op="self-hosted / other"
fi
echo "$d -> $op"
done
Twenty-one recognizable domains, checked on 2026-08-23 with dig 9.20.24:
cloudflare.com -> Cloudflare stackoverflow.com -> Cloudflare
shopify.com -> self-hosted wikipedia.org -> self-hosted
openai.com -> Azure DNS mozilla.org -> Akamai
github.com -> AWS Route 53 apple.com -> self-hosted
bbc.co.uk -> self-hosted nytimes.com -> AWS Route 53
amazon.com -> AWS Route 53 netflix.com -> AWS Route 53
spotify.com -> self-hosted airbnb.com -> AWS Route 53
reddit.com -> AWS Route 53 twitch.tv -> AWS Route 53
notion.so -> Cloudflare vercel.com -> self-hosted
anthropic.com -> Cloudflare linkedin.com -> Azure DNS
discord.com -> Cloudflare
Twelve of twenty-one — 57% — have Cloudflare or AWS Route 53 somewhere in their nameserver set. Add danstativa.dev from the trace above and it is thirteen of twenty-two, in the Cloudflare bucket.

Thirteen sites, one pole. Nobody voted on the wiring. Photo by Dina Lydia on Unsplash.
The estimator behind that number needs a caveat, stated plainly rather than left implicit. This counts what the NS records say, which is who controls the zone, not where traffic actually flows — a domain can point at Cloudflare and still serve most requests from an origin server nobody else touches. It also is not a random sample: these are twenty-one domains any reader would recognize by name, which skews toward large, well-funded organizations that can afford a multi-vendor DNS setup precisely because they are large. A random sample of the long tail — the sites built on whatever the hosting panel defaulted to — would very plausibly show a higher concentration than 57%, not a lower one, because that is exactly the population with no budget to diversify.
What centralization bought, and who pays for it
The strongest version of the opposing case has to be stated before this piece disagrees with it, and it is genuinely strong: centralized DNS and centralized bot mitigation are why plaintext DNS spoofing stopped being a live threat for most users, and why a site with no security budget can survive scraping and credential-stuffing traffic that used to just take it offline for free. Nobody who has run a small site through a real DDoS attack is going to accept “just self-host your nameservers” as a serious alternative. The 2016 attack on Dyn — which knocked a long list of unrelated sites offline by targeting one shared DNS provider — is the reason a lot of that infrastructure consolidated in the first place, not in spite of the outage risk but because of it: fewer, bigger, better-defended providers survive attacks that smaller ones do not.
That argument is correct, and it does not answer the question this piece is actually asking, which is not “should DNS be centralized” but “who pays when the centralizing choice turns into a policy decision instead of a technical one.”
Two answers, both named as parties rather than gestured at.
The first is whoever gets challenged. The cf-mitigated: challenge response above was built to filter bots, and it does filter some. But solving a JavaScript challenge is not free for either side of the request — a well-funded scraping operation buys that solve at scale, from a paid CAPTCHA-solving service, cheaper per request the more requests it makes. A person on a slow connection, an older phone, a screen reader, or a Tor exit node pays the same challenge at full retail, with no volume discount available. The friction was built to filter bots. It filters by budget instead, and the bots have one.
The second is whoever shares a nameserver with someone they never chose to depend on. Thirteen of the twenty-two sites checked above — including this one — have no say in a Cloudflare or AWS outage that takes their DNS down alongside everyone else in the same bucket. That is the honest cost of the resilience the steelman above is right about: resilience against attack and correlated failure against outage are the same centralization, priced two different ways.
Closing
None of this is an argument for reverting to 1987’s plaintext, ungoverned lookup, or for refusing Cloudflare. A site with no security budget genuinely needs what centralized mitigation provides, and the alternative to a fast DoH handshake is not a faster one — it’s a slower, unencrypted one.
What changes is smaller than a migration. It is one command, run against a domain the reader actually owns:
$ dig +short NS yoursite.com
Whatever that returns is a trust decision that was made for most sites by default, not on purpose — usually at signup, usually without anyone reading past the checkbox. Running that command does not fix anything. It just moves the moment someone learns what their site depends on from the day of an outage to right now, which is the only version of that lesson that’s still useful.
References
- IETF, RFC 1035 — Domain Names: Implementation and Specification (1987)
- IETF, RFC 8484 — DNS Queries over HTTPS (DoH) (2018)
- ENS, eth.limo — a public gateway for
.ethnames - Cloudflare, 1.1.1.1 public DNS resolver
- AdGuard, AdGuard DNS
Footnotes
-
RFC 1035, Domain Names — Implementation and Specification (1987), §1: DNS is specified as a distributed database returning consistent answers to identical queries; nothing in the specification conditions the answer on the identity of the asker. RFC 8484 (2018) later specifies DNS-over-HTTPS as a transport change, not a semantic one — the policy layer described here sits above both. ↩
-
DoH timing measured via
curl -w '%{time_total}'against five distinct domains (bbc.com,apple.com,reddit.com,openai.com,debian.org), each a cold TLS connection with no session reuse — this measures worst-case per-query cost, not the amortized cost a browser sees across a session that keeps one HTTP/2 connection open. Plain UDP timing read fromdig’s ownQuery timefield against five different domains (wikipedia.org,mozilla.org,github.com,stackoverflow.com,nytimes.com), chosen to avoid resolver-side caching from a repeated identical query. ↩