The ordinary HTTP challenge for Let's Encrypt does not support Wildcard certificates (*.example.com). A Wildcard needs the DNS-01 challenge — validation through a TXT record in DNS.
How the DNS-01 Challenge Works
- Certbot requests a certificate from Let's Encrypt
- Let's Encrypt issues a random token
- Certbot asks you to add a TXT record:
_acme-challenge.example.com - Let's Encrypt verifies the TXT record over DNS
- The certificate is issued
Manually with certbot
# Request a Wildcard certificate
certbot certonly --manual --preferred-challenges dns -d "*.example.com" -d "example.com"
# Certbot will show the TXT record to add:
# _acme-challenge.example.com. 300 IN TXT "TOKEN"
# Add the record to DNS, then press Enter
# The certificate will be in /etc/letsencrypt/live/example.com/
Automatically with acme.sh + Cloudflare API
># Install acme.sh
curl https://get.acme.sh | sh
# Configure the Cloudflare API
export CF_Token="YOUR_CLOUDFLARE_API_TOKEN"
export CF_Account_ID="YOUR_ACCOUNT_ID"
# Issue the Wildcard certificate
~/.acme.sh/acme.sh --issue --dns dns_cf -d "*.example.com" -d "example.com" --keylength ec-256
# Install it into Nginx
~/.acme.sh/acme.sh --install-cert -d "*.example.com" --key-file /etc/nginx/ssl/example.key --fullchain-file /etc/nginx/ssl/example.crt --reloadcmd "systemctl reload nginx"
DNS Providers Supported by acme.sh
acme.sh supports automatic renewal through the API of more than 100 DNS providers: Cloudflare, Route 53, DigitalOcean, Namecheap, Google Cloud DNS and others.
An Alternative to acme.sh: certbot with the certbot-dns-cloudflare Plugin
If certbot feels more familiar than acme.sh, DNS-01 for Cloudflare is also available through an official plugin — no need to hand-write a script with curl calls to the API.
# Install the plugin
apt install python3-certbot-dns-cloudflare -y
# Token file, permissions must be 600
echo "dns_cloudflare_api_token = YOUR_TOKEN" > /root/.secrets/cloudflare.ini
chmod 600 /root/.secrets/cloudflare.ini
# Issue the Wildcard certificate
certbot certonly --dns-cloudflare \
--dns-cloudflare-credentials /root/.secrets/cloudflare.ini \
-d "*.example.com" -d "example.com"
certbot adds and removes the TXT record through the API on its own, waits for propagation, and can renew the certificate via cron without any extra scripts on top of acme.sh.
Common DNS-01 Challenge Errors
| Message | Cause |
|---|---|
| DNS problem: NXDOMAIN looking up TXT | The TXT record was never added, or it was added in the zone of a different domain or subdomain |
| DNS problem: query timed out | The zone's TTL is too high, or the local resolver has not seen the change yet — wait for the record to propagate |
| CF_Token: command not found | The CF_Token and CF_Account_ID environment variables were not exported in the current session — a missing export, or the script ran from cron without them |
| Multiple validation errors: unauthorized | An old TXT record from a previous attempt was never removed; DNS returned several _acme-challenge records at once, and Let's Encrypt only checks one |
| Verify error with a correct TXT record | The record has not yet propagated to all authoritative NS servers — acme.sh did not wait long enough; increase --dnssleep |
When DNS Challenge Is Not the Right Tool
- You only need a certificate for one specific subdomain, not a wildcard — the plain HTTP-01 challenge via certbot --nginx is simpler and needs no DNS API access
- DNS is managed by a registrar with no API, or the API is unsupported by acme.sh — manually updating the TXT record before every renewal is impractical and risks missing the deadline
- The certificate must be issued instantly, within seconds — DNS-01 always waits for the record to propagate, at least tens of seconds and often minutes
- You need a certificate for a bare IP address — Let's Encrypt does not issue certificates for IPs through either the HTTP or the DNS challenge
Verifying the Result
Before pressing Enter in certbot or waiting on acme.sh, confirm the record is actually visible to public DNS — it saves time on repeated attempts. Renewal and auto-renewal details are covered in the articles on configuring certbot for a wildcard and on Let's Encrypt auto-renewal.
# Confirm the TXT record is actually visible to public DNS
dig +short TXT _acme-challenge.example.com @8.8.8.8
# Check the expiration date of an already issued certificate
openssl x509 -in /etc/letsencrypt/live/example.com/cert.pem -noout -enddate