This piece started with me walking straight into a trap of my own making. I was looking at the blog's DNS records, typed dig out of habit, and asked for a name I knew for certain did not exist. An answer came back. Two IP addresses, neatly signed, with a 300-second TTL.
I tried again. I mashed the keyboard. Another answer came back.
That evening I learned that my own zone will not tell me "there is no such name". And it isn't one setting doing this: two separate mechanisms, one set up by me and one that became a standard in September 2025, work hand in hand. Each makes sense on its own. Together they make the question "does this name resolve?" meaningless.
Here is the awkward part: halfway through this article a single dig flag demolished my own thesis. The "does not exist" answer does leave my zone — it just never reaches me. Where it disappears turned out to be the most instructive part.
Below I measure both. Step by step: how I tell from the outside which of my subdomains actually exist, what the signature hides, and how long a "does not exist" lives under this arrangement. Every command is dig, and the laboratory is my own domain.
The lab and the measurement setup
Everything was run on 19 October 2026 with DiG 9.10.6 on macOS 26.6.2. I queried three public resolvers (1.1.1.1, 8.8.8.8, 9.9.9.9) and my zone's authoritative server (janet.ns.cloudflare.com) separately — separately matters, because halfway through this article there is a place where the two do not say the same thing.
The zone's SOA record is this:
mustafaerbay.com.tr. 1800 IN SOA janet.ns.cloudflare.com. dns.cloudflare.com.
2416168327 10000 2400 604800 1800
That last field — 1800 — is the SOA's MINIMUM value. We will come back to it; it governs how long negative answers live.
Measurement 1: eight names, seven missing
I picked eight subdomain names that seem plausible for the blog: www, blog, en, api, mail, cdn, ops, feed. The choice is not arbitrary — on the site /en/ really is the English section, /feed.json really is the feed, and the admin dashboard lives under /admin/ops. All of them are paths, not subdomains — but expecting them as subdomains one day is natural. All eight were queried, and all eight answered with an A record.
Then I put a nonexistent label underneath each of them and asked again: x-1234.www, x-5678.blog, and so on. All of these went to 1.1.1.1, without +dnssec — two details that turn out to change the answer later on. The results:
| Name queried | RCODE | ANSWER | Reading |
|---|---|---|---|
x.www.mustafaerbay.com.tr |
NOERROR | 0 |
www is a real node |
x.blog.… |
NOERROR | 2 | wildcard synthesis, blog missing |
x.en.… |
NOERROR | 2 | wildcard synthesis, en missing |
x.api.… |
NOERROR | 2 | wildcard synthesis, api missing |
x.mail.… |
NOERROR | 2 | wildcard synthesis, mail missing |
x.cdn.… |
NOERROR | 2 | wildcard synthesis, cdn missing |
x.ops.… |
NOERROR | 2 | wildcard synthesis, ops missing |
x.feed.… |
NOERROR | 2 | wildcard synthesis, feed missing |
Seven of the eight names do not exist in the zone. They never did. The only reason they answer is a * record sitting at the top of the zone.
That is easy to confirm, since I can query the wildcard directly:
dig +short '*.mustafaerbay.com.tr' A @1.1.1.1
# 104.21.34.64
# 172.67.199.18
Which leads to this: if a monitoring check asked "does ops.mustafaerbay.com.tr resolve?", that check would stay green forever. The green means not that the name exists, but that in this zone no name can fail to exist. The probe itself works perfectly; the question it asks has no referent.
The mechanism behind the table: the closest encloser
The interesting part is that the wildcard cannot reach x.www. That is exactly what turns the table into a measurement instrument.
The rules for wildcards live in RFC 4592. When a query arrives, the server first finds the "closest encloser": the existing node in the zone tree that shares the most labels with the query name. The document defines it as "the node in the zone's tree of existing domain names that has the most labels matching the query name", and then draws a firm line: a wildcard can synthesise only if it sits immediately below the closest encloser. If an intervening name is known to exist, no synthesis happens.
For x.blog the closest encloser is the zone apex, because there is no blog node. So *.mustafaerbay.com.tr kicks in and the answer is synthesised. For x.www the closest encloser is www, because that node really does exist; there is no *.www record, so nothing can be synthesised and the server is forced to say "no".
That is the recipe exactly: I find out whether a label is a node in the zone tree by putting a nonexistent name underneath it and asking. If an answer comes back, the label is absent and the wildcard is doing the talking.
The other direction needs care. Silence tells you the label is a node; it does not tell you the label owns records. RFC 4592 §2.2.1 lists exactly these cases among its "will not be synthesised" examples: an SRV query for _telnet._tcp.host1.example. is not synthesised "because _tcp.host1.example. exists (without data)"; an A query for host.subdel.example. is not synthesised "because subdel.example. exists (and is a zone cut)". So when the probe stays silent the label is one of three things: a genuine record owner, an empty non-terminal that owns nothing but has descendants that do (§2.2.3: "A node may exist with no RRSets only if it has descendants that do"), or a delegation point. A way to tell the three apart comes later.
One more caveat: add a deeper wildcard such as *.www and the probe will answer for www too, and the method will declare www absent. The probe reads the zone's present shape, not its history.
The wildcard does not cover every type
The second measurement separates things even more sharply. I took a single nonexistent name and asked for it in seven different types:
| Type | RCODE | ANSWER |
|---|---|---|
| A | NOERROR | 2 |
| AAAA | NOERROR | 2 |
| HTTPS (TYPE65) | NOERROR | 1 |
| SVCB (TYPE64) | NOERROR | 0 |
| TXT | NOERROR | 0 |
| MX | NOERROR | 0 |
| CNAME | NOERROR | 0 |
| SRV | NOERROR | 0 |
| CAA | NOERROR | 0 |
The wildcard carries three types: A, AAAA and HTTPS. The last one matters, because modern browsers ask for the HTTPS record before connecting; a nonexistent name coming back with a record full of alpn and ipv6hint guarantees the "the browser reaches the site" line before I even write it.
The measuring tool lays a trap of its own here. DiG 9.10.6 (2017) does not know the name HTTPS and quietly asks two separate questions:
$ dig +noall +question tip65-10397.mustafaerbay.com.tr HTTPS @janet.ns.cloudflare.com
;tip65-10397.mustafaerbay.com.tr. IN A
;HTTPS. IN A
So the answer you get for HTTPS may belong to a completely different question. The table has to be built with TYPE65.
The remaining types come back empty — but note that the RCODE is still NOERROR. This is what RFC 2308 calls NODATA, defined in the document's own words as "a pseudo RCODE which indicates that the name is valid, for the given class, but are no records of the given type". It has no response code of its own; it is expressed by NOERROR plus an empty answer section.
In other words, the moment the wildcard synthesises, the name counts as existing, and every other type automatically falls through to NODATA. RFC 4592 describes an uncovered type as "no error, but no data". The practical consequence: a server looking for an SPF record and a browser resolving a web address see two different worlds for the same nonexistent name. The browser reaches the site; the mail server comes back empty-handed.
The second mechanism: where the wildcard cannot reach
So what answer comes back for x.www, which the wildcard cannot reach? Let us look at it raw, this time straight from the authoritative server:
dig +dnssec +noall +comments +authority @janet.ns.cloudflare.com \
ham-13032.www.mustafaerbay.com.tr A
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 46655
;; flags: qr aa rd; QUERY: 1, ANSWER: 0, AUTHORITY: 4, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags: do; udp: 1232
;; AUTHORITY SECTION:
mustafaerbay.com.tr. 1800 IN SOA janet.ns.cloudflare.com. dns.cloudflare.com. [...]
ham-13032.www.mustafaerbay.com.tr. 1800 IN NSEC
\000.ham-13032.www.mustafaerbay.com.tr. RRSIG NSEC TYPE128
mustafaerbay.com.tr. 1800 IN RRSIG SOA 13 3 1800 [...]
ham-13032.www.mustafaerbay.com.tr. 1800 IN RRSIG NSEC 13 5 1800 [...]
There are three odd things here, and all three are deliberate.
First, the RCODE is NOERROR, not NXDOMAIN. Second, the NSEC record sits on exactly the name I asked for — the server is saying "this name exists, it just has no A record". Third, the NSEC's next-name field holds no real neighbour; it is my own query name with a single zero octet prefixed: \000..
This technique is what Cloudflare described years ago as "black lies". Their reasoning is stated plainly in their own write-up: because they generate answers on the fly they do not see the whole zone, and "we do not need to search the database or precompute dynamic answers". Putting the NSEC directly on the missing name also removes the need for a second NSEC covering the wildcard.
What TYPE128 is: RFC 9824
The thing dig prints as TYPE128 is my favourite detail here. On the Mac's dig 9.10.6 the type's name is not yet in the dictionary, so it is printed by number.
That number's name is NXNAME, and it became a standard in September 2025 with RFC 9824, Compact Denial of Existence in DNSSEC. The document defines the type as "a synthetic RR type to signal the presence of a nonexistent name" — a meta-type that carries no data and cannot be queried. By putting this bit in the NSEC's type list, the server says "this name really does not exist" — not with an RCODE, but with a signed bit.
The RFC's NSEC example is precisely what came back from my zone:
a.example.com. 300 IN NSEC \000.a.example.com. RRSIG NSEC NXNAME
The interesting part is on the resolver side — and this is where my first reading was wrong.
Cut the standard's sentence in half and it seems to say "a resolver may rewrite the response code to NXDOMAIN". Here it is in full: "A validating resolver that understands the NXNAME signal from an authoritative server could modify the response code from NOERROR to NXDOMAIN in responses they return to downstream queriers that have not set the DO bit in their requests." The rewrite is envisaged for queries that do not set the DO bit. My first measurement used +dnssec, so DO was set — meaning it measured the absence of a behaviour the RFC never expected in the first place.
There is also §5.1, "Signaled Response Code Restoration". The second most significant bit of the EDNS flags field is the Compact Answers OK (CO) flag, and the document is explicit about it: "When this flag is sent in a query by a resolver, it indicates that the resolver will accept a NODATA response with a signed NXNAME for a nonexistent name, together with the response code field set to NXDOMAIN (3)." In dig terms that is +ednsflags=0x4000.
So the correct table has three rows. The same nonexistent name, three flag combinations, four servers:
| query | authoritative | 1.1.1.1 |
8.8.8.8 |
9.9.9.9 |
|---|---|---|---|---|
| no DO (plain query) | NXDOMAIN | NOERROR | NOERROR | NOERROR |
DO set (+dnssec) |
NOERROR + NXNAME | NOERROR | NOERROR | NOERROR |
DO + CO (+ednsflags=0x4000) |
NXDOMAIN + NXNAME | NOERROR | NOERROR | NOERROR |
This table tells a better story than my first one. My zone does say NXDOMAIN — both to a plain query and to one carrying the CO flag. Only in the middle row, a DNSSEC-enabled query without CO, does it turn into NODATA. That row is the entire point of Compact Denial of Existence: making signed proof of absence cheap.
Where it disappears is the resolver layer. All three passed the NXNAME bit through unchanged, and all three — DO set or not, CO set or not — left the RCODE at NOERROR. So in October 2026 the practical truth for my zone is this: NXDOMAIN leaves the authoritative server and never reaches me.
The RFC does not hide this outcome. Its security section warns that for tools which recognise nonexistent names from the RCODE, "a NOERROR (rather than NXDOMAIN) code in that field will impact such tools", and it points to the right path: the DNS header is not signed, so the RCODE cannot be authenticated; the trustworthy signal is the signed NXNAME bit. It tells tool authors as much, noting they "could be enhanced to recognize the (signed) NXNAME signal".
One more detail: RFC 9824's header says "Updates: 4034, 4035". We will see shortly why that matters.
The signature hides the wildcard
At one point I thought I had found a cheap shortcut. In DNSSEC, signatures normally give wildcard expansion away. RFC 4035 §5.3.4 puts it like this: "If the number of labels in an RRset's owner name is greater than the Labels field of the covering RRSIG RR, then the RRset and its covering RRSIG RR were created as a result of wildcard expansion." So, I thought, I can look at the signature and tell whether an answer was synthesised.
I put three records side by side: the zone apex, the genuine www, and a made-up name synthesised by the wildcard.
mustafaerbay.com.tr. 300 IN RRSIG A 13 3 300 ... <- 3 labels, 3
www.mustafaerbay.com.tr. 300 IN RRSIG A 13 4 300 ... <- 4 labels, 4
sentez-2206.mustafa...tr. 300 IN RRSIG A 13 4 300 ... <- 4 labels, 4
In all three the Labels field equals the name's label count. The synthesised record is indistinguishable from the real www record at the signature level.
My first instinct was to call this a Cloudflare quirk — a server signing on the fly signs the queried name as-is, and the wildcard vanishes. That was wrong. This is not vendor behaviour but behaviour written into the standard. RFC 9824 §3.3, "Responses for Wildcard Matches": "the answer RRset will have an RRSIG record demonstrating an exact match (i.e., the label count in the RRSIG RDATA will be equal to the number of labels in the query name minus the root label)."
The two documents look like they contradict each other, but they do not; the "Updates: 4034, 4035" in RFC 9824's header is there for exactly this. The newer document changes this corner of the older one.
The practical upshot: the textbook signature method does not work in a zone running Compact Answers. The method that does work is the crude probe from a section earlier: put a nonexistent label underneath the name and see whether an answer comes back. Not elegant, but it reads the zone's tree from the outside.
How long a "does not exist" lives
The last measurement is the negative cache. Since there is no record to carry a negative answer's TTL, the lifetime rides on the SOA record in the authority section. RFC 2308 defines it as "taken from the minimum of the SOA.MINIMUM field and SOA's TTL". In my zone both are 1800, so thirty minutes.
My first attempt was weak: one name, seventy seconds, a single sample per time point. 8.8.8.8 dropped from 1800 to 1759, the other two stayed at 1800, and I wrote "one caches, two are unclear". All three are anycast, so every query can land on a different cache node; a single sample cannot separate decay from node scatter.
The proper way: pick a fresh name, query each resolver five times at four time points, and report the minimum and maximum together. Scatter then shows up as the width of the range.
| t (s) | 1.1.1.1 |
8.8.8.8 |
9.9.9.9 |
pure-decay expectation |
|---|---|---|---|---|
| 0 | 1800–1800 | 1800–1800 | 1800–1800 | 1800 |
| 60 | 1800–1800 | 1740–1800 | 1740–1800 | 1740 |
| 120 | 1800–1800 | 1680–1740 | 1680–1740 | 1680 |
| 180 | 1741–1800 | 1620–1620 | 1620–1620 | 1620 |
This table shows two things at once. At the third minute 8.8.8.8 and 9.9.9.9 said exactly 1620 in all five samples — 1800 minus 180, to the second. The negative answer really is cached and the counter really is running. The ranges at the points in between, like 1740–1800, are the scatter itself: some queries landed on a node that already had the answer, others on a node hearing it for the first time.
1.1.1.1 behaved differently; for the first two minutes it kept handing back fresh nodes, and only at the third minute did its lower bound drop to 1741. So it caches too — my queries just kept missing for longer. That "unclear" result in my first table was not the absence of a cache; it was the absence of enough samples.
The thirty minutes is worth a word too. RFC 2308's sentence — "values of one to three hours have been found to work well and would make sensible a default" — is not a recommendation for a zone's SOA MINIMUM. Its subject is the resolver, and its topic is the upper limit a resolver should place on how long it keeps negative answers. So my 1800 seconds is not "a setting below the recommended range"; it is a value that never gets clipped, because it already sits under the resolvers' own one-to-three-hour ceilings. When I create a name in my zone, a stale "does not exist" lives at most half an hour.
Except that in my zone that wrong answer is never produced in the first place, because the wildcard says "yes" to everything. RFC 9824's security section spells out the price, and the wording matters: "this method does not allow for aggressive negative caching among resolvers". What is meant is not negative caching in general but RFC 8198's aggressive use of NSEC — a resolver inferring, from one signed NSEC it already holds, that other names are absent too. Under Compact Answers each answer is specific to a single name, so that inference is impossible, and every pseudorandom subdomain query travels all the way to the authoritative server.
What this costs me
Four concrete things:
Monitoring checks lie. Asked through a public resolver, the question "does this name resolve?" is always "yes" in my zone. To know whether a subdomain is actually live I have to look at the HTTP response, or the certificate, or put my DNS question to the authoritative server with the CO flag.
Typos land silently on the site. If I shared an address like blgo.mustafaerbay.com.tr, the wildcard would route it to Cloudflare too. The visitor sees a 404 and I see nothing, because at the DNS layer no error ever happened.
The type difference is confusing. Every name exists for A, AAAA and HTTPS; no name exists for TXT and MX. Any check that leans on TXT — SPF, DKIM, DMARC — gets no benefit from the wildcard, and I can easily picture one of my own scripts treating "has an A record but no TXT" as a fault and eating two evenings.
Aggressive negative caching is off. Because of Compact Answers my resolvers cannot make the RFC 8198 inference, so every query for a nonexistent name travels all the way to the authoritative server. I will never feel this on my own; someone raining random subdomains on my zone would make me feel it.
The first one is the most annoying. A probe turning green does not mean what it measures is true; it may only mean that probe can never turn red. Black boxes get through their worst nights without telling anyone, but if a probe is asking the wrong question you will not even learn the night was bad.
Reading your own zone in ten minutes
In order, each a single line:
-
Is there a wildcard:
dig +short '*.example.com' A. If an answer comes back, the zone holds a*record and every "does it resolve?" question you ask from now on is meaningless. - Which types does the wildcard cover: ask for a nonexistent name as A, AAAA, TXT, MX and CAA. Every type that comes back empty is NODATA for that name.
- Which labels are nodes: put a nonexistent name underneath the label you are unsure about and ask. If an answer comes back, the label is absent.
-
Node or empty non-terminal: run
dig +dnssec <label>.<zone> CAAand read the type list of the NSEC that comes back. RFC 9824 §3.2: "For a query for an ENT, the NSEC Type Bit Maps field will only contain RRSIG and NSEC." So a list holding onlyRRSIG NSECmeans the label is an empty non-terminal — something exists beneath it, nothing on it.NXNAMEmeans the name does not exist at all. A populated list means a genuine record owner. If you work with service labels like_acme-challenge,_dmarcor_tcp, skipping this step means misreading the probe. -
Negative TTL: fetch the SOA with
dig +noall +authority ..., then look at the MINIMUM field and the SOA's own TTL; whichever is smaller is your negative caching period. -
Does NXDOMAIN actually arrive: add
+dnssecand check whether the NSEC's type list containsTYPE128orNXNAME. If it does, stop trusting the RCODE — and do not decide from a resolver alone, ask the authoritative server separately. - Fix your tooling: anything that recognises a nonexistent name from the RCODE — scripts, monitoring, security filters — should learn to read the NXNAME bit too. RFC 9824 recommends this itself.
The concrete form of that last item falls out of the measurements too. In a zone sitting under a wildcard, the cheapest check that can still ask "does this name really exist?" is to ask the authoritative server with the CO flag set:
dig +dnssec +ednsflags=0x4000 +noall +comments \
@janet.ns.cloudflare.com yourname.yourzone A | grep status:
As the three-row matrix showed, this query brings the RCODE back. Every path through a resolver swallows it; ask the authoritative server directly and the answer is still there.
There is no single right answer to what you should do about a wildcard. Mine stays for a reason: I can bring up a new subdomain without touching DNS. The price of that convenience is that DNS no longer tells me about the absence of anything. The difference between a price paid knowingly and one paid unawares is really what this article is about.
How I arranged these observations on the resolver side is in Unbound for regional DNS cache and forwarder separation; the enterprise side of a validating resolver layer is in a separate validating DNSSEC resolver layer.
Closing
I asked for eight names, seven did not exist, and all of them answered. One mechanism does this at my request, the other by an RFC's deliberate design. Neither is broken; both behave exactly as documented.
But what this article actually taught me sat outside the tables. I began with "my zone does not say NXDOMAIN"; one flag was missing, and my zone said NXDOMAIN perfectly well. What was wrong was not the measurement's result but its scope: I had tried one of a flag's two values and mistaken the answer for the whole zone.
The real lesson follows from that: "does not exist" is not a measurement result but an agreement. It changes with who you ask and which flags you carry. If you do not know which layer is answering which question, the "yes" replies you collect will only keep you busy. The way to understand a zone is not to list what exists, but to measure how absence is expressed — and who has stopped expressing it.
Top comments (0)