Let's Encrypt Wildcard Certificates with Certbot and DNS-01

Blog / Server Management · October 19, 2022 · Updated June 10, 2026 · 9 min read
Let's Encrypt Wildcard Certificates with Certbot and DNS-01

A wildcard certificate such as *.example.com secures every subdomain — app.example.com, api.example.com, blog.example.com — with a single Let's Encrypt certificate. The catch: a wildcard can only be issued through the DNS-01 challenge. The HTTP-01 and TLS-ALPN-01 challenges that Certbot normally uses cannot produce a wildcard, because they prove control of one hostname, not a whole namespace. This guide covers DNS-01 end to end with Certbot 2.x: the manual TXT-record method, fully automated issuance and renewal through a DNS provider plugin, and how to wire the wildcard into Nginx.

If you only need certificates for a handful of named hosts and you are setting up TLS on Nginx from scratch, start with our general Configure SSL with Let's Encrypt and Nginx guide — it walks through installing Certbot, the --nginx plugin, TLS 1.2/1.3 hardening and systemd auto-renewal. This post assumes Certbot is already installed and focuses specifically on the wildcard workflow.

What a wildcard certificate covers (and what it doesn't)

A wildcard is convenient but its scope is narrow and easy to get wrong:

  • It matches exactly one label. *.example.com covers app.example.com and api.example.com, but not the apex example.com itself, and not deeper names like a.b.example.com. For multi-level coverage you would issue a separate *.b.example.com.
  • The apex is not included. To secure both the bare domain and its subdomains, request both names on the same certificate: -d example.com -d '*.example.com'.
  • It's still a DV certificate. Let's Encrypt only does Domain Validation; there is no wildcard "OV/EV" tier. It's free and valid for 90 days, like any other Let's Encrypt cert.
  • One private key for many hosts. A wildcard trades convenience for blast radius — every subdomain shares the same key. For high-isolation services, individual certificates (or per-service keys) are often the safer choice.

Why only DNS-01 can issue a wildcard

ACME challenges differ in where you prove control. HTTP-01 places a token at http://<host>/.well-known/acme-challenge/..., which only demonstrates control of that single host on port 80. A wildcard would require proving control of an unbounded set of hostnames, so the CA insists on a proof at the DNS zone level instead: a TXT record at _acme-challenge.example.com. Controlling the zone is what authorises *.example.com.

Challenge What it proves Wildcard? Automation
HTTP-01 Serves a token over port 80 on one host No Easy (built in)
DNS-01 Writes a TXT record in the domain's DNS zone Yes (*.example.com) Easy with a provider plugin; manual otherwise
TLS-ALPN-01 Completes a special TLS handshake on port 443 No Easy, but needs port 443 free

The practical takeaway: wildcard = DNS-01, always. The only real decision is whether you create the TXT record by hand (one-off) or let a Certbot DNS plugin manage it (automated, renewable).

Option A — Manual DNS-01 (one-off issuance)

The manual method asks you to create the validation TXT record yourself. It's fine for a quick first certificate or a provider with no plugin, but be aware up front: manual DNS-01 cannot auto-renew — Certbot will pause and wait for a human every 90 days. Quote the wildcard so your shell doesn't glob it, and list the apex separately:

# Issue *.example.com AND the apex example.com on one cert
sudo certbot certonly --manual --preferred-challenges dns \
  -d 'example.com' -d '*.example.com' \
  --agree-tos -m admin@example.com

Certbot prints one or more TXT values and pauses:

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

with the following value:
gfj83hd...exampletokenvalue...8sk2

Press Enter to Continue

Add that record at your DNS host, then verify it has propagated before pressing Enter — checking too early is the most common cause of Failed authorization procedure. Query an authoritative/public resolver directly:

# Confirm the TXT record is live (use any public resolver)
dig +short TXT _acme-challenge.example.com @1.1.1.1

# host(1) works too:
host -t txt _acme-challenge.example.com

When the value matches, press Enter. On success the files land in /etc/letsencrypt/live/example.com/ (fullchain.pem and privkey.pem).

Note: Requesting both the apex and the wildcard means Certbot may ask for two TXT records under the same name _acme-challenge.example.com. DNS allows multiple TXT values on one name — add both, don't overwrite the first.

Option B — Automated DNS-01 with a DNS plugin (recommended)

For anything you want to renew unattended, install the Certbot DNS plugin for your provider and give it API credentials. Certbot then creates and removes the TXT record automatically on every issuance and renewal — no human in the loop. Plugins exist for Cloudflare, AWS Route 53, Google Cloud DNS, DigitalOcean, and many more.

Install the plugin the same way you installed Certbot. With the snap:

# snap Certbot: install + trust the DNS plugin
sudo snap install certbot-dns-cloudflare
sudo snap set certbot trust-plugin-with-root=ok
sudo snap connect certbot:plugin certbot-dns-cloudflare

# Debian/Ubuntu apt alternative:
# sudo apt install python3-certbot-dns-cloudflare

Create a credentials file with a scoped API token (Cloudflare: a token limited to Zone › DNS › Edit for just this zone — never a global API key). Lock the file down so only root can read it:

sudo install -m 0600 /dev/null /etc/letsencrypt/cloudflare.ini
sudo tee /etc/letsencrypt/cloudflare.ini >/dev/null <<'EOF'
# Cloudflare API token scoped to Zone:DNS:Edit
dns_cloudflare_api_token = your-scoped-token-here
EOF
sudo chmod 600 /etc/letsencrypt/cloudflare.ini

Now issue the wildcard non-interactively. --dns-cloudflare-propagation-seconds tells Certbot how long to wait for the record to propagate before asking the CA to validate (raise it if validation fails intermittently):

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

Other providers follow the same pattern — swap the plugin and credential flags:

Provider Plugin Credential flag
Cloudflare certbot-dns-cloudflare --dns-cloudflare-credentials
AWS Route 53 certbot-dns-route53 IAM role/keys (no creds file)
Google Cloud DNS certbot-dns-google --dns-google-credentials
DigitalOcean certbot-dns-digitalocean --dns-digitalocean-credentials

For Route 53, prefer an EC2 instance profile or an IAM user scoped to route53:ChangeResourceRecordSets and route53:GetChange on the specific hosted zone, rather than broad credentials.

Least-privilege: CNAME delegation and acme-dns

Handing a TLS automation host a token that can edit your whole DNS zone is more power than it needs. Two patterns shrink that risk:

  • CNAME delegation — point _acme-challenge.example.com (via a one-time CNAME) at a separate zone or service that only holds ACME validation records. The plugin updates that delegated target; your production zone stays untouched.
  • acme-dns — a tiny purpose-built DNS server that accepts only _acme-challenge updates over a simple API. You delegate validation to it once, and the credentials it issues can do nothing except answer challenges.

Both let you automate wildcard renewal without granting full zone-edit rights to the certificate host — a meaningful hardening step on shared or internet-facing infrastructure.

Auto-renewal for wildcards

Certbot stores the plugin and flags you used in a renewal config under /etc/letsencrypt/renewal/, so renewal needs no extra arguments — it replays the same DNS-01 automation. Modern Certbot ships a systemd timer (or the snap's built-in timer) that runs twice daily and renews anything within 30 days of expiry; do not add a cron line. Confirm the timer and test the full DNS round-trip without spending rate limits:

# Is the renewal timer active?
systemctl list-timers | grep certbot

# Dry run: exercises the real DNS-01 plugin flow, no live cert change
sudo certbot renew --dry-run

If you manage the Nginx config by hand (rather than via the --nginx plugin), add a deploy hook so Nginx reloads and serves the fresh certificate after each renewal:

sudo certbot renew \
  --deploy-hook "systemctl reload nginx" \
  --dry-run

Remember the asymmetry: a certificate first issued with --manual will not renew on its own. If you started with Option A, re-issue it once with a DNS plugin (Option B) so the saved renewal config is automation-capable.

Use the wildcard certificate in Nginx

One wildcard cert can back every subdomain, so multiple server blocks reference the same fullchain.pem/privkey.pem. Here app and api share the wildcard, and a default block catches everything else:

# Reusable TLS settings (Mozilla "intermediate" profile)
# /etc/nginx/snippets/wildcard-example-com.conf
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_prefer_server_ciphers off;

# /etc/nginx/sites-available/example.com
server {
    listen 443 ssl;
    listen [::]:443 ssl;
    http2 on;                      # Nginx 1.25.1+ syntax
    server_name app.example.com;
    include snippets/wildcard-example-com.conf;
    # ... proxy_pass / root for the app ...
}

server {
    listen 443 ssl;
    listen [::]:443 ssl;
    http2 on;
    server_name api.example.com;
    include snippets/wildcard-example-com.conf;
    # ... proxy_pass for the API ...
}

# HTTP -> HTTPS for the whole zone
server {
    listen 80;
    listen [::]:80;
    server_name .example.com;      # apex + any subdomain
    return 301 https://$host$request_uri;
}

Validate and reload:

sudo nginx -t && sudo systemctl reload nginx

Where this fits in a real environment

A single wildcard on one box is straightforward; production estates add load balancers and CDNs that terminate TLS, internal services that aren't reachable on port 80 (exactly where DNS-01 shines), secrets management for the DNS API tokens, and renewal monitoring so a silent failure never becomes an outage.

MicroPyramid has spent 12+ years and 50+ delivered projects building and operating this kind of infrastructure. We often terminate public TLS at an AWS Application Load Balancer or CloudFront with ACM while keeping Let's Encrypt wildcards (issued via DNS-01) for internal, origin and non-public services — as part of our AWS consulting services and cloud migration services. When the certificate fronts a Python application, our Django development services team wires the HTTPS redirects, HSTS and secure-cookie handling into the app layer as well.

Frequently Asked Questions

Why can't HTTP-01 issue a wildcard certificate?

HTTP-01 proves control by serving a token at http://<host>/.well-known/acme-challenge/..., which only demonstrates control of that one hostname on port 80. A wildcard like *.example.com represents an unbounded set of hostnames, so Let's Encrypt requires proof at the DNS-zone level instead — a TXT record at _acme-challenge.example.com. Only the DNS-01 challenge provides that proof, which is why it is the only challenge that can issue wildcards.

Does a wildcard certificate cover the apex (root) domain?

No. *.example.com matches subdomains such as app.example.com but not the bare example.com. To secure both, request them together on one certificate: -d example.com -d '*.example.com'. Note also that the wildcard matches only a single label, so a.b.example.com is not covered either.

How do I auto-renew a wildcard certificate?

Use a Certbot DNS plugin (for example certbot-dns-cloudflare or certbot-dns-route53) with an API credentials file locked to chmod 600. Certbot then creates and removes the validation TXT record automatically, so the systemd renewal timer can renew unattended. Verify it with sudo certbot renew --dry-run. A certificate issued with --manual cannot auto-renew — re-issue it once with a plugin to make renewal hands-free.

Can I get a wildcard with the manual DNS-01 method?

Yes — sudo certbot certonly --manual --preferred-challenges dns -d 'example.com' -d '*.example.com' works, and Certbot pauses for you to add the TXT record. It's fine for a one-off, but it won't renew automatically: every 90 days Certbot would again wait for a human. For anything long-lived, use a DNS plugin instead.

Wildcard certificate vs a multi-domain (SAN) certificate — which should I use?

A wildcard covers every subdomain of one name with a single entry (*.example.com), which is ideal when subdomains are numerous or created dynamically. A SAN/multi-domain certificate lists specific names (-d a.example.com -d b.example.com -d other.com) and can span different apex domains, but you must reissue it whenever a host is added. Use a wildcard for many same-domain subdomains; use SAN when the set is small, known, or spans multiple domains. Both still use DNS-01 if any listed name is a wildcard.

Why does validation fail right after I add the TXT record?

Almost always DNS propagation timing. The CA queries the record before your change has propagated, so validation fails with Failed authorization procedure. Confirm the value is live with dig +short TXT _acme-challenge.example.com before continuing, and with a plugin raise --dns-<provider>-propagation-seconds (e.g. to 60) so Certbot waits longer before asking the CA to check.

Share this article