Wildcard DNS Records: The Footgun That Keeps Firing

A wildcard DNS record reads like 'match everything below this name.' It doesn't. It never touches names that already exist — and it dies quietly the moment you add one record deep in the tree.

You set *.example.com to point at your load balancer two years ago. Every subdomain anyone invents — app, staging, whatever marketing dreamed up this week — just resolves. No ticket, no DNS change, it works. Then one afternoon someone adds a single TXT record at _acme-challenge.api.example.com to pass a certificate challenge, and dashboard.api.example.com stops resolving. Nobody touched the dashboard. Nobody touched the wildcard. It just stopped.

That’s not a bug in your DNS provider. That’s wildcards working exactly as specified, and the spec does not say what most people think it says.

The mental model is wrong

Almost everyone reads *.example.com as “match everything under example.com.” That’s the footgun, right there, in your head. A wildcard is not a subtree catch-all. It’s a much narrower, stranger thing: a template the server uses to synthesize an answer for a name that does not already exist.

The original wildcard rules came from RFC 1034 §4.3.3 back in 1987, and they were ambiguous enough that implementations disagreed for two decades. RFC 4592, published in 2006, exists for one reason: to nail down what wildcards actually do, because the 1987 text was vague enough to be dangerous. The title is literally “The Role of Wildcards in the Domain Name System.” When a protocol gets a whole RFC twenty years later just to clarify three paragraphs, that tells you how much trouble those paragraphs caused.

Three rules do most of the damage.

Rule one: the asterisk is only special on the left

*.example.com is a wildcard. api.*.example.com is not — the asterisk there is a literal label, a name with an actual * character in it. A label only behaves as a wildcard when it’s the leftmost label of the owner name. Put it anywhere else and DNS treats it as the three ASCII bytes it is.

People reach for this when they want app.*.example.com to match app.anything.example.com. It doesn’t. There’s no such mechanism. Wildcards expand the leftmost position and only the leftmost position.

Rule two: a wildcard never overrides a name that exists

This is the big one. A wildcard only kicks in when the queried name has no closer match in the zone. The technical term is the closest encloser — the deepest existing ancestor of the name you asked for. The wildcard that can answer is * directly below that closest encloser, and nowhere else.

Walk through the opening story with that rule. Before anyone added the TXT record, the zone had example.com and *.example.com and not much else. Query dashboard.api.example.com? The deepest existing ancestor is example.com. The wildcard sitting right below it — *.example.com — synthesizes the answer. Works fine. (And yes, this matches across two labels, dashboard.api — wildcards happily synthesize for names many labels deep, as long as nothing in between exists. The “wildcards only match one label” claim you’ll find in blog posts is just wrong.)

Now add _acme-challenge.api.example.com. You created a record three levels down, but you also did something you didn’t intend: you made api.example.com exist. It owns no records of its own — it’s what RFC 4592 calls an empty non-terminal, a name that exists purely because something lives beneath it. And existence is binary. As far as DNS is concerned, api.example.com is now a real node in the tree.

So re-run the query for dashboard.api.example.com. The deepest existing ancestor is no longer example.com — it’s api.example.com. The only wildcard that could answer now would be *.api.example.com, which you never created. *.example.com is one level too high to reach. The server returns NXDOMAIN. Your dashboard is gone, and the change that killed it was a certificate record on a completely different subdomain.

This is why “it worked yesterday” is such a common refrain with wildcards. They don’t fail when you touch them. They fail when you touch something near them.

Rule three: a wildcard only answers for the type you asked

A wildcard owner has its own record types, and it synthesizes only those. If you publish *.example.com A 203.0.113.10 and a client asks for the AAAA (IPv6) record of random.example.com, it does not get an error and it does not get the A record. It gets NODATA — the name “exists” by virtue of the wildcard, but there’s no AAAA there. Same story for MX, TXT, anything. The wildcard is not a blanket “yes”; it’s a blanket “here are exactly these record types and nothing else.”

And *.example.com does not match example.com itself. The apex is a name that already exists, so rule two applies — the wildcard never covers its own parent. If you want the bare domain to resolve, you give it its own records. This trips people up constantly: the wildcard covers every imaginary child and pointedly ignores the one name they actually typed into the browser.

Where this actually bites

The empty-non-terminal trap is the headline, and it shows up most often with the underscore-prefixed records modern infrastructure loves: _acme-challenge for certificates, _dmarc and _domainkey for mail, _<service>._tcp SRV records. Every one of those quietly materializes an empty non-terminal at its parent, and if a wildcard was carrying that parent’s subtree, it stops. The person adding a TLS challenge record and the person who owns the wildcard are usually not the same person, which is how this becomes a multi-hour incident instead of a one-line fix.

Wildcard MX records are their own category of regret. *.example.com MX 10 mail.example.com means every nonexistent subdomain accepts mail. Spammers love this — they send to sales@anything-they-invent.example.com and your server dutifully takes it. A catch-all that you meant as a convenience becomes a backscatter and spam magnet.

DNSSEC adds the final twist. A signed zone has to prove a wildcard synthesis happened, using NSEC or NSEC3 records that demonstrate both that the exact name didn’t exist and that the wildcard did. Get the signing wrong — or run a resolver that validates strictly against a server that synthesizes sloppily — and the wildcard answer fails validation. Now your wildcard works on unvalidated resolvers and breaks on validating ones, which is the worst possible way for anything to fail: intermittently, and only for the careful clients.

So what do you actually do

Wildcards are not the enemy. I use them. The fix is to stop thinking of *.example.com as “everything below example.com” and start thinking of it as “a synthesizer for names that don’t otherwise exist, one level down, for these specific record types.” Once that’s the model in your head, the failures stop being surprises.

Concretely: if you run a wildcard and you also publish records deep in that subtree, know that you’ve created empty non-terminals and that the wildcard no longer reaches past them. If a branch needs both specific records and wildcard fallback, you need a wildcard at that branch too — *.api.example.com alongside the records under api. And before you blame the resolver, the cache, or the provider, check whether someone added a quiet little underscore record last Tuesday.

The wildcard didn’t change. The tree did. That’s almost always the answer.

Continue the conversation

← Back to Blog