How-to guide · Automate renewal
How to get a wildcard certificate with DNS-01
In one sentence
You will have a publicly trusted certificate for *.example.com and example.com, issued through the ACME DNS-01 challenge with a narrowly scoped DNS key, and renewing by itself.
- Task
- Automate renewal
- Platforms
- Linux
- Windows
- Level
- Practitioner · 5 steps
On this page
Before you start
- A domain whose DNS you control, hosted by a provider with an API, or a DNS zone you can delegate to one.
- An ACME client on a machine you trust: certbot with a DNS plugin or acme.sh on Linux, or win-acme on Windows.
- Root or administrator rights on that machine, and about 20 minutes.
- The Let’s Encrypt staging environment for your first attempts, so that mistakes do not count against real rate limits.
Why this matters: with ACME, a wildcard certificate can only be validated through DNS, not through your website. From 15 March 2029, a CA may reuse a domain check for only 10 days, so nearly every renewal will need a fresh DNS challenge. It has to work without a person.
Steps
1. Decide which names you need
A wildcard covers exactly one label, and never the bare domain:
| Name | Covered by *.example.com? |
|---|---|
www.example.com, api.example.com |
Yes |
example.com |
No: request it as a separate name |
a.b.example.com |
No: it needs *.b.example.com |
So most people request two names in one certificate: example.com and *.example.com. Both go in the Subject Alternative Name list.
Think about where the key will live. Every server that holds a wildcard key can impersonate every name it covers. Use a wildcard for one platform, such as a load balancer or ingress, rather than copying the key to many servers.
2. Check that CAA allows wildcard issuance
A CAA record can forbid wildcards even when normal certificates are allowed. Look at your domain (with dig on Linux, macOS or WSL):
dig CAA example.com +short
- No output: CAA does not restrict anyone.
issuerecords only: they apply to wildcards too.- Any
issuewildrecord: for wildcards, it replaces everyissuerecord, andissuewild ";"forbids wildcards from all CAs.
A real example, debian.org, in October 2026:
128 issue "letsencrypt.org;validationmethods=dns-01;accounturi=https://acme-v02.api.letsencrypt.org/acme/acct/346607"
0 iodef "mailto:dsa@debian.org"
128 issuewild ";"
That domain allows normal certificates from one Let’s Encrypt account through DNS-01, and no wildcards at all. If your own records block what you need, change them first: see How to set CAA records.
3. Give the client narrow DNS access
The ACME client must create a TXT record at _acme-challenge.example.com for each challenge. Limit what its credentials can do.
Option A: a scoped API token. Most DNS providers can issue a token that may only edit DNS records in one zone. For Cloudflare, the certbot plugin documentation asks for “Zone:DNS:Edit permissions for only the zones you need certificates for”. Never use an account-wide key.
Option B: delegate the challenge. Point _acme-challenge at a separate zone that holds nothing else, and give the client a token for that zone only. A stolen token then cannot change your website’s records:
_acme-challenge.example.com. 3600 IN CNAME _acme-challenge.acme-validation.example.net.
Let’s Encrypt explicitly supports this: “you can use CNAME records or NS records to delegate answering the challenge to other DNS zones” (challenge types). Large sites do it. GitHub’s real record for github.io:
dig +noall +answer TXT _acme-challenge.github.io
_acme-challenge.github.io. 3481 IN CNAME github.io.acme.github.net.
Run the client on a dedicated machine or CI job if you can, not on the public web server. Let’s Encrypt warns that “putting your full DNS API credentials on your web server significantly increases the impact if that web server is hacked.”
4. Request the certificate
Quote the wildcard name ('*.example.com') in every command. Unquoted, the shell may expand the * into file names from the current folder.
certbot with a DNS plugin (Linux)
Install certbot and the plugin for your DNS provider. With the snap packages that certbot recommends, using Cloudflare as the example:
sudo snap install --classic certbot
sudo snap set certbot trust-plugin-with-root=ok
sudo snap install certbot-dns-cloudflare
Other official plugins include certbot-dns-route53, certbot-dns-google, certbot-dns-digitalocean, certbot-dns-ovh and certbot-dns-rfc2136 for your own BIND-style server. Store the token in a file that only root can read:
sudo mkdir -p /root/.secrets/certbot
sudo nano /root/.secrets/certbot/cloudflare.ini
sudo chmod 600 /root/.secrets/certbot/cloudflare.ini
# /root/.secrets/certbot/cloudflare.ini
dns_cloudflare_api_token = <your scoped token>
Test against staging with --dry-run, which obtains a test certificate and throws it away:
sudo certbot certonly --dns-cloudflare \
--dns-cloudflare-credentials /root/.secrets/certbot/cloudflare.ini \
--dns-cloudflare-propagation-seconds 60 \
-d example.com -d '*.example.com' \
--dry-run
When the dry run succeeds, run the same command without --dry-run. certbot saves the files in /etc/letsencrypt/live/example.com/ and remembers the plugin and credentials file for renewals.
acme.sh with a delegated zone (any Unix shell)
acme.sh’s default CA is not Let’s Encrypt, so name the CA explicitly. With the CNAME from step 3 in place, --challenge-alias writes the TXT records in the validation zone (DNS alias mode):
export CF_Token="<token for acme-validation.example.net only>"
export CF_Zone_ID="<zone ID of acme-validation.example.net>"
acme.sh --issue --server letsencrypt_test --dns dns_cf \
-d example.com -d '*.example.com' \
--challenge-alias acme-validation.example.net
letsencrypt_test is the staging CA. When it works, run the command again with --server letsencrypt for the real certificate. (Do not combine --server letsencrypt with --staging: an explicit server wins, and you would be talking to production.) Then let acme.sh copy the files and reload the server on every renewal:
acme.sh --install-cert -d example.com \
--key-file /etc/ssl/private/example.com.key \
--fullchain-file /etc/ssl/certs/example.com.fullchain.pem \
--reloadcmd "systemctl reload nginx"
Windows
win-acme supports DNS validation through plugins for common DNS providers and scripts. See How to automate certificates on Windows with win-acme.
5. Install the certificate and test renewal
Point your server at the full chain and the key: fullchain.pem and privkey.pem for certbot. The install guides for nginx and Apache show the configuration. Then prove that renewal will work without you:
sudo certbot renew --dry-run
systemctl list-timers | grep -i certbot
Add a deploy hook so the web server loads each renewed certificate, for example --deploy-hook "systemctl reload nginx".
Check it worked
Ask the live server for its certificate, once for the bare domain and once for a subdomain:
for h in example.com www.example.com; do
openssl s_client -connect $h:443 -servername $h -verify_hostname $h </dev/null 2>/dev/null \
| openssl x509 -noout -subject -issuer -ext subjectAltName -enddate
done
This is what a correct wildcard-plus-apex certificate looks like: GitHub Pages’ real certificate from Let’s Encrypt, fetched in October 2026:
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
notAfter=Dec 29 23:44:45 2026 GMT
Each wildcard sits next to its bare domain. Test which names your certificate really covers:
If something goes wrong
No TXT record found at _acme-challenge.example.com, orIncorrect TXT record … found: the CA checked before every authoritative server had the record, or saw an old value. Check each server withdig +short NS example.com, thendig +short TXT _acme-challenge.example.com @<server>, and raise the propagation wait. A request forexample.comand*.example.comneeds two TXT values at the same name at once, so a DNS tool that replaces instead of adds fails one of them.CAA record for example.com prevents issuance: anissueorissuewildrecord does not allow your CA. Fix it as in step 2.- Too many failed attempts: Let’s Encrypt allows up to 5 failed validations per name, per account, per hour. Debug with certbot’s
--dry-runor acme.sh’sletsencrypt_testserver, never in a loop against production. - The bare domain or a deeper name shows a name error: the certificate lacks that name. See NET::ERR_CERT_COMMON_NAME_INVALID and ERR_TLS_CERT_ALTNAME_INVALID.
- Browsers work but curl or Java fail: the server sends
cert.pemwithout the intermediate. Use the full chain (unable to get local issuer certificate). - Renewal fails months later: the DNS token expired or was rotated. Monitor expiry independently, as in How to check when a certificate expires.
Why this works
A CA may only issue a certificate after you prove control of every name in it (domain validation). For a wildcard, the Baseline Requirements rule out the website-based and TLS-ALPN methods, because *.example.com covers names that do not exist yet. What remains are DNS-based methods and contact-based ones, such as email to the domain’s contacts; in ACME that means a DNS challenge, usually DNS-01. The ACME DNS-01 challenge turns that proof into a TXT record that software can publish and remove, and the CA checks it from several network locations. The one-label rule for wildcards is explained in Names and jobs.
Go deeper
- RFC 8555 section 8.4: the DNS-01 challenge and how the TXT value is computed.
- Let’s Encrypt: challenge types: DNS-01 trade-offs, propagation and delegation.
- CA/Browser Forum Baseline Requirements, section 3.2.2.4: which validation methods may be used for wildcards.
- RFC 8659 section 4.3: how
issuewildoverridesissue. - certbot: DNS plugins and certbot-dns-cloudflare: plugin list, credentials and propagation settings.
- acme.sh: DNS API: the variables for each DNS provider.
Early edition. Checked against the standards; an independent expert review is in progress. Found a mistake? Tell us.