Let’s Encrypt Wildcard Certificates with DNS Challenge

Getting a wildcard certificate from Let’s Encrypt is a bit more involved than a regular single-domain cert. You can’t use an HTTP challenge for wildcards — Let’s Encrypt requires DNS validation, which means you need to create a TXT record to prove you own the domain. That adds complexity, but once it’s set up and automated, it works really well.

Here I’ll walk through both the manual process (useful for understanding what’s happening) and then the automated approach that I actually use in production.

Why Wildcards

A wildcard certificate covers *.example.com — meaning any single-level subdomain: www.example.com, api.example.com, app.example.com, staging.example.com. One certificate, all subdomains.

The downside: wildcards don’t cover the apex domain (example.com itself) or multi-level subdomains (deep.sub.example.com). You usually need to include both example.com and *.example.com explicitly in the certificate.

Manual DNS Challenge (Understanding the Flow)

Before automating, I find it helpful to go through the manual process once:

certbot certonly \
  --manual \
  --preferred-challenges dns \
  -d example.com \
  -d '*.example.com'

Certbot will pause and show something like:

Please deploy a DNS TXT record under the name
_acme-challenge.example.com with the following value:

abcdef123456789...

Once this is deployed, press the Enter key to continue.

Go to your DNS provider, create a TXT record:

  • Name: _acme-challenge.example.com
  • Value: the long string from certbot

Wait for propagation (30–60 seconds usually, sometimes more), then press Enter. Certbot verifies the record, issues the certificate.

This is fine for one-time use, but renewals every 90 days require the same process manually. Not practical. Automate it.

Automated DNS Challenge with Certbot Plugins

If your DNS provider is Cloudflare, there’s a Certbot plugin:

pip install certbot-dns-cloudflare
# or
apt install python3-certbot-dns-cloudflare

Create a credentials file at /etc/letsencrypt/cloudflare.ini:

dns_cloudflare_api_token = your-cloudflare-api-token
chmod 600 /etc/letsencrypt/cloudflare.ini

Generate your Cloudflare API token at dash.cloudflare.com → Profile → API Tokens. Use the “Edit zone DNS” template and scope it to your specific zone — don’t give it more permissions than needed.

Issue the certificate:

certbot certonly \
  --dns-cloudflare \
  --dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini \
  -d example.com \
  -d '*.example.com' \
  --email you@example.com \
  --agree-tos

Renewals happen automatically via Certbot’s renewal timer. No manual intervention.

Certbot has plugins for many providers: Route53 (certbot-dns-route53), DigitalOcean (certbot-dns-digitalocean), Cloudflare, Google Cloud DNS, and others. The pattern is the same for all of them.

Automated DNS Challenge with acme.sh

acme.sh has DNS API support built in with no separate plugin installation. For Cloudflare:

export CF_Token="your-cloudflare-api-token"
export CF_Account_ID="your-account-id"

acme.sh --issue \
  -d example.com \
  -d '*.example.com' \
  --dns dns_cf

After the first run, acme.sh stores the credentials and reuses them for automatic renewals. I find acme.sh’s DNS API support slightly more convenient than Certbot’s plugin system.

Certificate Files and Where They Go

After issuance, Certbot stores certificates at:

/etc/letsencrypt/live/example.com/
├── cert.pem        # Your certificate
├── chain.pem       # Intermediate chain
├── fullchain.pem   # cert.pem + chain.pem combined
└── privkey.pem     # Private key

For Nginx, use fullchain.pem and privkey.pem:

server {
    listen 443 ssl;
    server_name example.com *.example.com;

    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512;
}

For Apache:

SSLCertificateFile /etc/letsencrypt/live/example.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem

Handling Multiple Subdomains in One Certificate

You can list explicit subdomains in addition to the wildcard:

certbot certonly \
  --dns-cloudflare \
  --dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini \
  -d example.com \
  -d '*.example.com' \
  -d '*.api.example.com'

Note that *.api.example.com is a second-level wildcard from the apex — it would cover v1.api.example.com, v2.api.example.com, etc. This requires a second _acme-challenge.api.example.com TXT record.

DNS Propagation and the –dns-cloudflare-propagation-seconds Flag

Sometimes DNS changes don’t propagate fast enough before Let’s Encrypt checks the TXT record. The Certbot Cloudflare plugin has a flag to wait longer:

certbot certonly \
  --dns-cloudflare \
  --dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini \
  --dns-cloudflare-propagation-seconds 60 \
  -d example.com -d '*.example.com'

Default is 10 seconds. For Cloudflare’s infrastructure, 10–30 seconds is usually fine. For other providers with slower propagation, increase this to 60–120 seconds.

Verifying the Certificate

After issuance, check what the cert covers:

openssl x509 -noout -text -in /etc/letsencrypt/live/example.com/cert.pem | grep -A1 "Subject Alternative"

You should see both example.com and *.example.com in the SAN list.

Or check the live certificate:

echo | openssl s_client -connect www.example.com:443 -servername www.example.com 2>/dev/null | openssl x509 -noout -text | grep "DNS:"

Renewal Testing

Test that renewal works before you actually need it:

certbot renew --dry-run

This simulates the renewal process without issuing a new certificate. If it fails, you find out now rather than 90 days from now when the cert is actually about to expire.

I run this after any DNS or server configuration changes to confirm nothing broke the renewal process. It’s a quick check that takes 10 seconds and has saved me from expired certificates more than once.

Rate Limits

Let’s Encrypt has rate limits that catch people off guard during testing. The main ones:

  • 5 failed validations per hour per domain
  • 50 certificates per registered domain per week
  • 5 duplicate certificates per week

For wildcards, if you’re testing the DNS challenge setup, use the staging server:

certbot certonly \
  --dns-cloudflare \
  --dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini \
  --staging \
  -d example.com -d '*.example.com'

Staging certificates aren’t trusted by browsers, but they let you verify the flow without burning production rate limits. Once the staging test works, rerun without --staging.

For more on certificate file formats and how to use them with different servers, the SSL certificate formats guide explains PEM, DER, and related formats in detail.

Scroll to Top