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
Who it is for
Level
Practitioner · 5 steps
On this page
  1. Before you start
  2. Steps
  3. 1. Decide which names you need
  4. 2. Check that CAA allows wildcard issuance
  5. 3. Give the client narrow DNS access
  6. 4. Request the certificate
  7. 5. Install the certificate and test renewal
  8. Check it worked
  9. If something goes wrong
  10. Why this works
  11. Go deeper

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.
  • issue records only: they apply to wildcards too.
  • Any issuewild record: for wildcards, it replaces every issue record, and issuewild ";" 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:

Try it: Hostname matcherOpen the full tool

Runs in your browser. Nothing you paste is sent anywhere.

What do you have?

If something goes wrong

  • No TXT record found at _acme-challenge.example.com, or Incorrect TXT record … found: the CA checked before every authoritative server had the record, or saw an old value. Check each server with dig +short NS example.com, then dig +short TXT _acme-challenge.example.com @<server>, and raise the propagation wait. A request for example.com and *.example.com needs 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: an issue or issuewild record 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-run or acme.sh’s letsencrypt_test server, 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.pem without 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

Early edition. Checked against the standards; an independent expert review is in progress. Found a mistake? Tell us.

Last reviewed Edition 0.9