A wildcard record makes DNS answer for names nobody created. That is all it does. It doesn’t give those names a certificate, and it doesn’t tell your application which of them are real. We tested a random, never-registered label on four hosting platforms and on our own zone to show where each layer stops.
Three layers, three separate decisions
| Layer | Question it answers | What a wildcard record changes |
|---|---|---|
| DNS | Does anything.example.com resolve? |
Yes, for undefined names at any depth below the closest existing name |
| TLS | Does a trusted certificate name this host? | Nothing. A wildcard certificate is a separate object, and it covers exactly one label |
| Application | Is this host a real project, tenant, or preview? | Nothing. The router decides, and it should refuse names it doesn’t know |
Platform namespaces are built on wildcards
Live capture, 26 September 2026. A random label we never registered, under four platform domains, through our ISP’s default resolver in South Korea (23:37 KST):
$ dig +nocmd andguide-wildcard-test-7f3k.github.io A +noall +answer
andguide-wildcard-test-7f3k.github.io. 3600 IN A 185.199.111.153
andguide-wildcard-test-7f3k.github.io. 3600 IN A 185.199.109.153
andguide-wildcard-test-7f3k.github.io. 3600 IN A 185.199.108.153
andguide-wildcard-test-7f3k.github.io. 3600 IN A 185.199.110.153
$ dig +nocmd andguide-wildcard-test-7f3k.pages.dev A +noall +answer
$ dig +nocmd andguide-wildcard-test-7f3k.vercel.app A +noall +answer
andguide-wildcard-test-7f3k.vercel.app. 300 IN A 216.198.79.131
andguide-wildcard-test-7f3k.vercel.app. 300 IN A 64.29.17.131
$ dig +nocmd andguide-wildcard-test-7f3k.netlify.app A +noall +answer
andguide-wildcard-test-7f3k.netlify.app. 120 IN A 52.74.6.109
andguide-wildcard-test-7f3k.netlify.app. 120 IN A 13.215.239.219
Live capture, 26 September 2026. The empty pages.dev answer, looked at more closely, one label and two labels deep (23:54 and 23:55 KST):
$ dig +nocmd andguide-wildcard-test-7f3k.pages.dev A +noall +comments +authority
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 51813
;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; AUTHORITY SECTION:
pages.dev. 60 IN SOA adi.ns.cloudflare.com. dns.cloudflare.com. 2415920232 10000 2400 604800 60
$ dig +nocmd deep.andguide-wildcard-test-7f3k.pages.dev A +noall +comments | grep status
;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 7839
Live capture, 26 September 2026. The other three platforms, two labels deep (23:38 KST):
$ dig +nocmd deep.andguide-wildcard-test-7f3k.github.io A +noall +answer
deep.andguide-wildcard-test-7f3k.github.io. 3600 IN A 185.199.110.153
deep.andguide-wildcard-test-7f3k.github.io. 3600 IN A 185.199.108.153
deep.andguide-wildcard-test-7f3k.github.io. 3600 IN A 185.199.109.153
deep.andguide-wildcard-test-7f3k.github.io. 3600 IN A 185.199.111.153
$ dig +nocmd deep.andguide-wildcard-test-7f3k.vercel.app A +noall +answer
deep.andguide-wildcard-test-7f3k.vercel.app. 300 IN A 216.198.79.195
deep.andguide-wildcard-test-7f3k.vercel.app. 300 IN A 64.29.17.195
$ dig +nocmd deep.andguide-wildcard-test-7f3k.netlify.app A +noall +answer
deep.andguide-wildcard-test-7f3k.netlify.app. 120 IN A 13.215.239.219
deep.andguide-wildcard-test-7f3k.netlify.app. 120 IN A 52.74.6.109
| Namespace | Random label | Two labels deep | TTL (seconds) |
|---|---|---|---|
github.io |
Four GitHub Pages addresses | The same four addresses | 3600 |
vercel.app |
Two addresses | Two addresses | 300 |
netlify.app |
Two addresses | The same two addresses | 120 |
pages.dev |
NXDOMAIN | NXDOMAIN | 60, as a negative answer |
Two things stand out:
- Three of the four platforms answer for a name nobody registered, and for a name one label deeper still. That is standard wildcard behavior: when no name between the query and the wildcard exists, the wildcard at the closest existing ancestor answers, however many labels are missing.
pages.devpublishes no wildcard. Unknown names get NXDOMAIN, cacheable for at most 60 seconds according to the SOA record, and each Pages project name is delegated to its own nameservers instead; the CNAME flattening guide shows that delegation for our ownand-guide.pages.dev.
DNS matches any depth; a certificate matches one label
Live capture, 26 September 2026. The certificate GitHub presents for the unregistered one-label name, and an HTTPS request to the name one label deeper (23:38 KST, OpenSSL 3.6.3):
$ openssl s_client -connect andguide-wildcard-test-7f3k.github.io:443 -servername andguide-wildcard-test-7f3k.github.io </dev/null 2>/dev/null | openssl x509 -noout -subject -issuer -ext subjectAltName
subject=CN=*.github.io
issuer=C=US, O=Let's Encrypt, CN=YR1
X509v3 Subject Alternative Name:
DNS:*.github.com, DNS:*.github.io, DNS:*.githubusercontent.com, DNS:github.com, DNS:github.io, DNS:githubusercontent.com
$ curl -sSI https://deep.andguide-wildcard-test-7f3k.github.io/
curl: (60) SSL: no alternative certificate subject name matches target host name 'deep.andguide-wildcard-test-7f3k.github.io'
...
Live capture, 26 September 2026. How each wildcard platform answers an unclaimed name over HTTPS, filtered to the status line and a few headers (23:56 KST):
$ curl -sI https://andguide-wildcard-test-7f3k.github.io/ | grep -iE "^(HTTP|server|x-vercel-error)"
HTTP/2 404
server: GitHub.com
$ curl -sI https://andguide-wildcard-test-7f3k.vercel.app/ | grep -iE "^(HTTP|server|x-vercel-error)"
HTTP/2 404
server: Vercel
x-vercel-error: DEPLOYMENT_NOT_FOUND
$ curl -sI https://andguide-wildcard-test-7f3k.netlify.app/ | grep -iE "^(HTTP|server|x-vercel-error)"
HTTP/2 404
server: Netlify
Reading the two captures together:
- The certificate names
*.github.io. Under RFC 9525, a wildcard in a certificate must be the entire left-most label and matches exactly one label. It coversandguide-wildcard-test-7f3k.github.ioand notdeep.andguide-wildcard-test-7f3k.github.io. - For the deeper name, DNS answered and curl still stopped with exit code 60 before any HTTP exchange; a browser would show a certificate error at the same point.
- For the one-label name, the certificate matched and GitHub answered
404. The platform looks the host up in its list of real sites and refuses the rest. Vercel says so explicitly withx-vercel-error: DEPLOYMENT_NOT_FOUND, and Netlify returns a plain404. This is what failing closed looks like.
Live capture, 26 September 2026. The certificates on the other two platforms follow the same one-label pattern (23:38 KST):
$ openssl s_client -connect andguide-wildcard-test-7f3k.vercel.app:443 -servername andguide-wildcard-test-7f3k.vercel.app </dev/null 2>/dev/null | openssl x509 -noout -subject -issuer -ext subjectAltName
subject=CN=*.vercel.app
issuer=C=US, O=Google Trust Services, CN=WR1
X509v3 Subject Alternative Name:
DNS:*.vercel.app
$ openssl s_client -connect andguide-wildcard-test-7f3k.netlify.app:443 -servername andguide-wildcard-test-7f3k.netlify.app </dev/null 2>/dev/null | openssl x509 -noout -subject -issuer -ext subjectAltName
subject=C=US, ST=California, L=San Francisco, O=Netlify, Inc, CN=*.netlify.app
issuer=C=US, O=DigiCert Inc, CN=DigiCert Global G2 TLS RSA SHA256 2020 CA1
X509v3 Subject Alternative Name:
DNS:*.netlify.app, DNS:netlify.app
Our own zone: a deliberate wildcard beside explicit names
and.guide uses a wildcard on purpose: *.and.guide is a wildcard pointing at our self-hosted deployment server for our own apps, while the apex and www have explicit records served by Cloudflare. That makes our zone a compact demonstration of the matching rules.
Live capture, 26 September 2026. Our authoritative server, asked about a random label, its AAAA record, a name one label deeper, the explicit www record, and a name under www (23:39 and 23:43 KST):
$ dig +nocmd @roan.ns.cloudflare.com andguide-wildcard-test-7f3k.and.guide A +norec +noall +answer
andguide-wildcard-test-7f3k.and.guide. 300 IN A 115.68.101.36
$ dig +nocmd @roan.ns.cloudflare.com andguide-wildcard-test-7f3k.and.guide AAAA +norec +noall +answer
$ dig +nocmd @roan.ns.cloudflare.com deep.andguide-wildcard-test-7f3k.and.guide A +norec +noall +answer
deep.andguide-wildcard-test-7f3k.and.guide. 300 IN A 115.68.101.36
$ dig +nocmd @roan.ns.cloudflare.com www.and.guide A +norec +noall +answer
www.and.guide. 300 IN A 172.67.138.236
www.and.guide. 300 IN A 104.21.70.196
$ dig +nocmd @roan.ns.cloudflare.com andguide-test.www.and.guide A +norec +noall +comments +answer +authority
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 55419
;; flags: qr aa; QUERY: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; AUTHORITY SECTION:
and.guide. 1800 IN SOA frida.ns.cloudflare.com. dns.cloudflare.com. 2414193944 10000 2400 604800 1800
Four details matter:
*.and.guideis an A record pointing at 115.68.101.36, and it is DNS-only: a proxied record would answer with Cloudflare’s addresses instead. Random names get it at any depth.- The wildcard holds only an A record, so the AAAA query comes back empty (NODATA). A wildcard supplies only the record types it owns.
www.and.guidehas its own records, so the wildcard never touches it. An exact match always wins.- The name under
wwwis NXDOMAIN, not the wildcard’s address. Becausewww.and.guideexists, it is the closest existing ancestor of that query, and there is no*.www.and.guide. RFC 4592 defines this, and Cloudflare documents the same behavior for names below a label that has its own record.
Live capture, 26 September 2026. The deployment server, asked for a name it doesn’t serve, over HTTP and HTTPS (23:54 KST):
$ curl -sI http://andguide-wildcard-test-7f3k.and.guide/
HTTP/1.1 404 Not Found
Content-Type: text/plain; charset=utf-8
X-Content-Type-Options: nosniff
Date: Sat, 26 Sep 2026 14:54:42 GMT
Content-Length: 19
$ curl -s http://andguide-wildcard-test-7f3k.and.guide/
404 page not found
$ curl -sSI https://andguide-wildcard-test-7f3k.and.guide/
curl: (60) SSL certificate problem: unable to get local issuer certificate
More details here: https://curl.se/docs/sslcerts.html
...
What happened:
- Over HTTP, the server refuses the unknown host with a plain
404. DNS sent the request there; the application layer decided the name doesn’t exist. That is the design working as intended. - Over HTTPS, curl could not verify the certificate the server presented for this name and stopped with exit code 60. Wildcard DNS brought the request to the server, but no certificate for arbitrary names came with it. The failure is safe, because the client stops before any HTTP exchange, but a browser shows it as a full-page warning.
Live capture, 26 September 2026. The zone does have a certificate that covers *.and.guide: the one Cloudflare serves for www.and.guide (23:41 KST):
$ openssl s_client -connect www.and.guide:443 -servername www.and.guide </dev/null 2>/dev/null | openssl x509 -noout -subject -issuer -dates -ext subjectAltName
subject=CN=and.guide
issuer=C=US, O=Let's Encrypt, CN=YE2
notBefore=Sep 6 14:25:26 2026 GMT
notAfter=Dec 5 14:25:25 2026 GMT
X509v3 Subject Alternative Name:
DNS:*.and.guide, DNS:and.guide
It cannot help the wildcard. Cloudflare serves its edge certificates only for proxied records, and *.and.guide is DNS-only, so clients connect straight to the deployment server. A certificate only helps on the machine that terminates TLS.
How wildcard matching actually works
RFC 4592 defines matching in terms of the closest encloser: the longest ancestor of the queried name that exists in the zone. Only the wildcard directly below it, *.<closest encloser>, can answer, and only when the queried name itself doesn’t exist. The RFC’s own examples make this concrete. With *.example. in the zone, foo.bar.example. is answered from the wildcard because bar.example. doesn’t exist, while host1.example., which exists, never is.
Three rules fall out of that:
- An exact record at the queried name always wins.
- Any existing name blocks the wildcard beneath it. That includes an “empty non-terminal”, a name that exists only because something below it does, such as
_tcp.host1.example.when only a service record under it is defined. - A wildcard supplies only the types it holds. A missing type is NODATA, not a fallback to another record.
Cloudflare documents *.example.com as multi-level by default and notes one provider-specific wrinkle. For a parent name that exists only implicitly, such as abc.example.com when only 123.abc.example.com has a record, its standard nameservers still apply the wildcard to that parent, while its advanced nameservers follow RFC 4592 and do not. Test on the nameservers you actually use.
For generated names, a narrow wildcard communicates intent better than a zone-wide one:
preview.example.com. 300 IN A 192.0.2.10
*.preview.example.com. 300 IN CNAME preview-router.example.net.
docs.example.com. 300 IN CNAME docs-host.example.net.
Generated names stay under preview, the preview landing page has its own record, and docs never falls into the catch-all.
Certificates for wildcard names
- Let’s Encrypt issues wildcard certificates only through the DNS-01 challenge; HTTP-01 and TLS-ALPN-01 cannot validate a wildcard. DNS-01 means publishing a TXT record at
_acme-challenge.<name>, so renewals need automation through your DNS provider’s API. - Let’s Encrypt itself flags DNS API credentials stored on the web server as a risk. Use narrowly scoped credentials, or validate from a separate machine and copy the certificate over. The
_acme-challengename can also be delegated with a CNAME or NS record to a zone used only for validation. - A wildcard doesn’t cover its own parent:
*.preview.example.comdoes not coverpreview.example.com, so put both names on the certificate if you serve both. - Deeper naming schemes need matching certificates.
*.preview.example.comwon’t coverbranch.team.preview.example.com; you need*.team.preview.example.com, per-host certificates, or a flatter naming scheme.*.*.example.comis not an option, because RFC 9525 allows one wildcard only, as the entire left-most label. - If your zone publishes CAA records, the CAA guide explains when a separate
issuewildrule matters.
Make the router fail closed
DNS will deliver requests for every name, so the router has to know which ones are real:
const suffix = ".preview.example.com";
const activePreviews = new Set(["main", "release-2026-09", "pr-482"]);
function routePreview(requestHost) {
const host = requestHost.toLowerCase().replace(/:\d+$/, "");
if (!host.endsWith(suffix)) return notFound();
// One label only: deeper names have no valid certificate anyway.
const label = host.slice(0, -suffix.length);
if (!/^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/.test(label)) return notFound();
if (!activePreviews.has(label)) return notFound();
return servePreview(label);
}
function notFound() {
return new Response("Not found", { status: 404 });
}
Return 404 for names that were never provisioned rather than a default page or a server error. A preview that has been deliberately retired can return 410 Gone while cleanup catches up. Every platform we tested, and our own deployment server, already does the 404 part.
When a wildcard is the wrong tool
- A handful of known names. Explicit records are easier to read, and each one has an obvious owner. Names that carry authority, such as
login,billing, orstatus, deserve explicit records rather than a catch-all. - Anything pointing at a shared platform. GitHub’s documentation strongly recommends against wildcard records for Pages sites, because they expose you to takeover even after you verify your domain: verification stops others from claiming
a.example.com, but a wildcard also answers forb.a.example.com. The captures above show why depth matters: a wildcard answers at every level. Point a wildcard only at an endpoint you control that decides for itself which hosts exist, as our deployment server does. The dangling DNS and takeover guide covers the wider risk. - Names nobody will clean up. A wildcard hides which names are in use. Keep the list of active names in the router’s source of truth, and review it alongside your subdomain inventory.
Before you pick a naming depth, the hostname checker shows which wildcard certificates would cover a given hostname.