IAnswerable

Get a Wildcard SSL Certificate with Let's Encrypt

21 Nov 2025

A regular Let’s Encrypt certificate covers one name per SAN entry. A wildcard covers *.example.com in one cert, which means every subdomain you spin up — app, api, staging, grafana — just works without a new issuance. The catch: you can only get one via the DNS-01 challenge. HTTP-01 doesn’t support wildcards at all.

Why DNS-01 is required

HTTP-01 proves you control a domain by serving a file at a specific path on that domain. That works for app.example.com but can’t prove you control every possible subdomain of example.com. DNS-01 proves it differently: Let’s Encrypt asks you to create a TXT record at _acme-challenge.example.com, checks it exists, and issues the cert. One DNS record, one proof, covers the whole wildcard.

This means your ACME client needs API access to your DNS provider, not just a webserver. Here’s the exchange that happens on every issuance or renewal:

sequenceDiagram
    participant C as Certbot
    participant D as DNS Provider
    participant L as Let's Encrypt
    C->>D: Create TXT record at _acme-challenge
    D-->>C: Record created
    C->>L: Request validation
    L->>D: Query TXT record
    D-->>L: Return TXT value
    L-->>C: Certificate issued
  

1. Get certbot with a DNS plugin

If you’re on Cloudflare DNS, install the matching plugin:

sudo apt install certbot python3-certbot-dns-cloudflare

Other providers (Route53, DigitalOcean, Google Cloud DNS) have their own certbot-dns-* plugins — same pattern, different flags.

2. Create a scoped API token

Don’t use your global Cloudflare API key. Create a token scoped to Zone:DNS:Edit for just the zone you need. If that token leaks, the blast radius is “someone can edit your DNS,” not “someone can do anything on your Cloudflare account.”

Save it to a file certbot can read, with tight permissions:

sudo mkdir -p /etc/letsencrypt/secrets
sudo tee /etc/letsencrypt/secrets/cloudflare.ini <<'EOF'
dns_cloudflare_api_token = your_token_here
EOF
sudo chmod 600 /etc/letsencrypt/secrets/cloudflare.ini

3. Issue the certificate

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

Note both -d flags: the wildcard *.example.com does not cover the bare example.com. You need both explicitly if you want the root domain covered too.

--dns-cloudflare-propagation-seconds 30 matters more than it looks. Certbot creates the TXT record, then waits before asking Let’s Encrypt to verify it. If your DNS provider is slow to propagate and you don’t wait long enough, verification fails with a DNS lookup error even though the record is correct — it just hasn’t spread to the resolvers Let’s Encrypt checks yet. 30 seconds is a safe default for Cloudflare; some providers need more.

4. Renewal is automatic, but verify it

Certbot’s systemd timer or cron job re-runs the same DNS-01 flow on renewal, using the same credentials file. Confirm the timer exists:

systemctl list-timers | grep certbot

And do a dry run to catch config problems before they become an expired-cert incident:

sudo certbot renew --dry-run

Installing it where it’s actually used

certonly just issues the cert to /etc/letsencrypt/live/example.com/. It doesn’t install anything. If you’re terminating TLS in Nginx, point it there manually:

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

If you’re using this cert across multiple services — Nginx, a mail server, an internal load balancer — a wildcard means you issue once and reference the same files everywhere, instead of running separate HTTP-01 issuances per subdomain that can each fail independently.

The gotcha that costs people an afternoon

_acme-challenge TXT records can accumulate if a previous attempt failed and left a stale record behind. Some DNS-01 failures are actually “there are two conflicting TXT records at that name and validation is checking the wrong one.” If a DNS-01 challenge fails for no visible reason, check your zone for leftover _acme-challenge TXT entries and delete them before retrying.