Let's Encrypt issues a wildcard certificate for *.example.com only through the DNS-01 challenge: your ACME client publishes a TXT record at _acme-challenge.example.com with a token it receives. For automatic renewal the client needs write access to DNS, either through your DNS provider's API or by delegating _acme-challenge with a CNAME to a zone it controls.
For a handful of subdomains you don’t need a wildcard. certbot can put www, app and api on one certificate via HTTP-01, no DNS work involved. A wildcard pays off when subdomains appear on their own: preview environments per branch, a tenant per customer at customer.example.com, or internal hosts that aren’t reachable over HTTP from outside.
What a wildcard certificate covers
A certificate for *.example.com is valid for any name exactly one level below example.com. Nothing more.
| Name | Covered by *.example.com? |
|---|---|
www.example.com | Yes |
pr-142.example.com | Yes |
example.com | No, add it separately |
api.staging.example.com | No, needs *.staging.example.com |
That’s why a typical wildcard certificate carries two names: example.com and *.example.com.
Why only DNS-01 works
HTTP-01 proves you control a web server. A wildcard requires proof that you control the zone, and only DNS can show that. Let’s Encrypt hands your client a token, and the client publishes a value derived from it:
_acme-challenge.example.com. 60 IN TXT "gfj9Xq...Rg85nM"
_acme-challenge.example.com. 60 IN TXT "Xn2kLp...a7Pq0w"
Two values for two names. The most common manual mistake is to add the first one, see certbot display the second, and overwrite the first. Then one of the two validations fails.
Wildcard certificate with certbot, the manual way
Manual mode is good for understanding and testing:
certbot certonly --manual --preferred-challenges dns --test-cert \
-d example.com -d '*.example.com'
The quotes stop the shell from expanding the asterisk. certbot shows two TXT values in turn and waits. Add both in your DNS panel (at betadomain, in the customer area with the domain’s DNS records) and check the authoritative server before pressing Enter:
dig NS example.com +short
dig @ns1.example-dns.net _acme-challenge.example.com TXT +short
"gfj9Xq...Rg85nM"
"Xn2kLp...a7Pq0w"
A TTL of 60 seconds makes sense for these records, since they change at every renewal.
The catch: a certificate issued this way doesn’t renew on its own. certbot renew refuses because the manual plugin needs an auth hook to run unattended. For production you need automation.
Automating renewal: DNS plugin or CNAME delegation
There are two clean options.
Option 1: a plugin for your DNS provider’s API
certbot has official plugins for some DNS services, packaged on Debian as python3-certbot-dns-cloudflare, python3-certbot-dns-rfc2136 and others. The RFC 2136 plugin is useful if you run your own BIND or Knot server that accepts dynamic updates signed with a TSIG key:
certbot certonly --dns-rfc2136 \
--dns-rfc2136-credentials /etc/letsencrypt/rfc2136.ini \
-d example.com -d '*.example.com'
Clients such as acme.sh and lego support many more providers. Check their documentation for yours.
Option 2: CNAME delegation to a challenge zone
If your DNS provider has no supported API, or you don’t want a key with full zone access sitting on a web server, delegate only the challenge name. Let’s Encrypt follows CNAMEs during DNS-01 validation:
_acme-challenge.example.com. 3600 IN CNAME d420c923-bbd7.auth.example.net.
auth.example.net is a zone served by a small purpose-built DNS server such as acme-dns. The ACME client writes only there, and nothing automated ever touches your main zone. You create the CNAME once by hand. acme.sh supports this with --dns dns_acmedns.
This is the setup I’d pick. The machine requesting certificates has exactly one permission: setting a TXT record under a random name.
Wildcards with Caddy and Traefik
Caddy also needs DNS-01 for wildcards and a DNS module that isn’t in the standard build. Build it with xcaddy:
xcaddy build --with github.com/caddy-dns/cloudflare
*.example.com, example.com {
tls {
dns cloudflare {env.CF_API_TOKEN}
}
reverse_proxy localhost:3000
}
Traefik uses a certificate resolver with dnsChallenge and takes the wildcard from router labels:
labels:
- "traefik.http.routers.app.rule=HostRegexp(`^.+\\.example\\.com$`)"
- "traefik.http.routers.app.tls.certresolver=le-dns"
- "traefik.http.routers.app.tls.domains[0].main=example.com"
- "traefik.http.routers.app.tls.domains[0].sans=*.example.com"
How both proxies route several domains is covered in reverse proxy for multiple domains.
Wildcard DNS and wildcard certificate go together
The certificate is useless if pr-142.example.com doesn’t resolve. Add a wildcard A record and, if IPv6 works, the matching AAAA:
* 3600 IN A 192.0.2.10
* 3600 IN AAAA 2001:db8:10::1
A DNS wildcard only answers for names that have no records at all, so _acme-challenge with its TXT or CNAME is unaffected.
A side effect: your hostnames stay out of CT logs
Every publicly trusted certificate is logged in Certificate Transparency logs that anyone can search. Request a certificate for staging-client-a.example.com and that name is public forever. A wildcard reveals only *.example.com. It’s not a security control, since subdomains leak in other ways, but it keeps internal project names out of public lists.
One key for everything
The downside rarely gets mentioned. The private key is valid for every subdomain. If it sits on ten servers, compromising one lets an attacker impersonate any subdomain. Deploy wildcards only on the reverse proxy that serves those subdomains, not on every backend.
For lifetimes and rate limits, read the Let’s Encrypt certificate guide. Short TTLs on challenge records take effect quickly, but cached negative answers can still linger, and DNS propagation time helps when debugging that.
Set up the CNAME for _acme-challenge or the DNS plugin and run a --test-cert issuance. If preview environments deserve their own domain, you can search for one at betadomain. A .dev domain is €22.99 a year, and the developer docs describe the domain API and MCP server if you want to automate registrations.
Step by step
- Decide how the client writes DNSCheck whether your ACME client has a plugin for your DNS provider's API. If not, plan a CNAME delegation of _acme-challenge to a zone you can automate.
- Test manuallyRun certbot certonly --manual --preferred-challenges dns --test-cert -d example.com -d '*.example.com' and add both TXT values by hand.
- Verify the TXT recordsBefore confirming, run dig @nameserver _acme-challenge.example.com TXT +short and check that both values are returned.
- Automate issuanceIssue the real certificate with a DNS plugin or acme-dns delegation so renewal needs no manual steps.
- Test renewalRun certbot renew --dry-run. If it fails with the manual plugin, the automation is missing.
Found your domain? You can check and register it directly at betadomain – yearly price from €2.99.
FAQ
Does *.example.com also cover example.com?
No. A wildcard covers exactly one label below the domain, such as www.example.com or app.example.com. Add the bare domain as a second name: -d example.com -d '*.example.com'.
Does *.example.com cover a.b.example.com?
No. The asterisk replaces a single label. Deeper levels need their own wildcard, such as *.b.example.com, or individual names.
Can I get a wildcard certificate with HTTP-01?
No. Let's Encrypt only allows DNS-01 for wildcard names. Being able to serve a file from one web server doesn't prove control over every possible subdomain.
Why do I need two TXT records with the same name?
When the certificate covers both example.com and *.example.com, both names are validated and both use _acme-challenge.example.com. The two values must exist at the same time as two TXT records. Replacing the first with the second breaks one validation.