TL;DR — Quick Summary

Certbot wildcard SSL certificates via DNS-01 challenge. Covers manual and automated DNS plugins, Cloudflare setup, auto-renewal, rate limits, troubleshooting.

Wildcard SSL certificates let a single certificate secure every subdomain under your domain — *.example.com covers api.example.com, app.example.com, staging.example.com, and any other subdomain you create. Rather than issuing individual certificates for each service, you obtain one certificate and deploy it across your entire infrastructure. Let’s Encrypt provides wildcard certificates free of charge, but requires the DNS-01 challenge for validation — a requirement that HTTP-based challenges cannot satisfy.

This guide covers every aspect of obtaining and maintaining wildcard certificates with Certbot: from understanding why DNS-01 is mandatory, through manual validation, automated DNS plugins, Cloudflare deep-dive configuration, auto-renewal with systemd, multi-domain SANs, rate limits, and troubleshooting.

Prerequisites

Before you begin, have the following ready:

  • Ubuntu 22.04 LTS or 24.04 LTS (or any Debian-based Linux) with sudo access.
  • A registered domain with DNS managed by a supported provider (Cloudflare, AWS Route 53, DigitalOcean, Google Cloud DNS, or manual access to your zone).
  • Certbot installed — or you will install it below.
  • An API token for your DNS provider with permission to create and delete DNS records.
  • Port 80 does NOT need to be open for wildcard DNS-01 challenges (unlike HTTP-01 challenges).

Why Wildcard Certificates?

A standard certificate covers one or more specific hostnames: example.com, www.example.com, api.example.com. If you add a new subdomain — metrics.example.com — you must either add it to the SAN list and reissue the certificate, or obtain a new one.

A wildcard certificate (*.example.com) eliminates this problem:

  • One certificate, unlimited subdomains — Any hostname matching *.example.com at a single level is covered automatically.
  • Simplified certificate management — One renewal process, one private key, one deploy hook.
  • Works with internal services — Subdomains that do not have public web servers (internal APIs, management UIs) are covered without needing HTTP access.
  • Cost efficiency — Let’s Encrypt issues these free. Commercial wildcard certificates from traditional CAs typically cost $100–$300/year.

The one limitation: wildcards cover exactly one subdomain level. *.example.com covers api.example.com but not v1.api.example.com. For nested subdomains, add a second wildcard: *.api.example.com.

Understanding ACME and the DNS-01 Challenge

Let’s Encrypt uses the ACME protocol (RFC 8555) to automate certificate issuance. You must prove control of your domain by completing a challenge.

Why HTTP-01 Cannot Validate Wildcards

The HTTP-01 challenge places a token file at http://example.com/.well-known/acme-challenge/<TOKEN>. This works for specific hostnames that have a reachable web server. But *.example.com represents an infinite set of hostnames — most of which may have no HTTP server at all. The ACME server has no way to probe every possible subdomain, so HTTP-01 is explicitly prohibited for wildcard certificates by the ACME specification.

How DNS-01 Works

DNS-01 validates domain control by proving you can write to the DNS zone:

  1. Certbot contacts the ACME server and requests authorization for *.example.com.
  2. The ACME server issues a token and instructs you to publish a TXT record at _acme-challenge.example.com with a specific value derived from the token.
  3. Certbot (or its DNS plugin) creates the TXT record via your DNS provider’s API.
  4. The ACME server queries DNS resolvers to verify the TXT record exists.
  5. Once validated, the certificate is signed and returned.
  6. Certbot (or its plugin) deletes the TXT record.

DNS-01 works even when no web server exists, when port 80 is blocked by a firewall, and when the subdomain has no A record.

Installing Certbot

The snap package is the officially maintained installation method and ensures you always have the latest version.

# Remove any OS-packaged certbot to avoid conflicts
sudo apt remove certbot -y 2>/dev/null || true

# Install Certbot via snap
sudo snap install --classic certbot

# Symlink so certbot is in PATH
sudo ln -s /snap/bin/certbot /usr/bin/certbot

# Verify
certbot --version

If your distribution does not support snaps, use pip in a virtual environment:

sudo apt install python3-pip python3-venv -y
python3 -m venv /opt/certbot
/opt/certbot/bin/pip install certbot
sudo ln -s /opt/certbot/bin/certbot /usr/local/bin/certbot

Manual DNS Validation

For a one-time certificate or when testing, manual DNS validation requires no API tokens. You create the TXT record yourself.

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

Certbot will output something like:

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

Once deployed, press Enter to continue...

Log in to your DNS provider, add the TXT record, then verify propagation before pressing Enter:

dig +short TXT _acme-challenge.example.com
# Should return: "gfj9Xq8R3mT2nKp1vLwY5sAqZdBcF7eH"

# Or query a public resolver
dig @8.8.8.8 +short TXT _acme-challenge.example.com

Wait until the value appears (typically 30–120 seconds, but can take up to 10 minutes depending on your provider’s TTL). Then press Enter.

Note: Manual validation does not support automatic renewal. Certbot will prompt you to recreate the TXT record manually every 60–89 days. For production use, set up an automated DNS plugin.

Automated DNS Plugins

DNS plugins eliminate manual steps by calling your DNS provider’s API to create and delete the _acme-challenge TXT record automatically during both initial issuance and every subsequent renewal.

PluginProviderInstall Command
certbot-dns-cloudflareCloudflaresudo snap install certbot-dns-cloudflare
certbot-dns-route53AWS Route 53sudo snap install certbot-dns-route53
certbot-dns-googleGoogle Cloud DNSsudo snap install certbot-dns-google
certbot-dns-digitaloceanDigitalOceansudo snap install certbot-dns-digitalocean
certbot-dns-linodeLinode/Akamaisudo snap install certbot-dns-linode
certbot-dns-hetznerHetzner DNSpip install certbot-dns-hetzner

Each plugin follows the same pattern:

  1. Install the plugin.
  2. Create a credentials file with your API token.
  3. Set file permissions to 600 (only root can read it).
  4. Run certbot certonly with the plugin’s --dns-<provider> flag and --dns-<provider>-credentials path.

Cloudflare Plugin Deep Dive

Cloudflare is one of the most common DNS providers and has excellent Certbot integration. Here is a complete walkthrough.

Step 1: Create a Cloudflare API Token

Log in to the Cloudflare dashboard → My ProfileAPI TokensCreate Token.

Use the “Edit zone DNS” template, then configure:

  • Permissions: ZoneDNSEdit
  • Zone Resources: IncludeSpecific zone → select your domain (or All zones if managing multiple domains)
  • IP Address Filtering: Optionally restrict to your server’s IP for extra security
  • TTL: Set an expiration date if you want token rotation enforced

Copy the token value — it is only shown once.

Step 2: Create the Credentials File

sudo mkdir -p /etc/letsencrypt/cloudflare

sudo tee /etc/letsencrypt/cloudflare/credentials.ini > /dev/null <<'EOF'
dns_cloudflare_api_token = YOUR_CLOUDFLARE_API_TOKEN_HERE
EOF

# Restrict to root read-only
sudo chmod 600 /etc/letsencrypt/cloudflare/credentials.ini
sudo chown root:root /etc/letsencrypt/cloudflare/credentials.ini

Security note: Never use the legacy dns_cloudflare_email + dns_cloudflare_api_key (Global API Key) method. The scoped API token limits blast radius if the credentials file is ever exposed.

Step 3: Install the Plugin

sudo snap install certbot-dns-cloudflare

# Connect the snap plugin to the certbot snap
sudo snap set certbot trust-plugin-with-root=ok
sudo snap connect certbot:plugin certbot-dns-cloudflare

Step 4: Obtain the Certificate

sudo certbot certonly \
  --dns-cloudflare \
  --dns-cloudflare-credentials /etc/letsencrypt/cloudflare/credentials.ini \
  --dns-cloudflare-propagation-seconds 60 \
  -d example.com \
  -d "*.example.com" \
  --email admin@example.com \
  --agree-tos \
  --non-interactive

The --dns-cloudflare-propagation-seconds 60 flag tells Certbot to wait 60 seconds after creating the TXT record before asking Let’s Encrypt to validate. Cloudflare’s API propagation is usually near-instant, but this buffer prevents transient failures.

Step 5: Verify the Certificate

sudo certbot certificates

Expected output:

Found the following certs:
  Certificate Name: example.com
    Serial Number: 3a1f...
    Key Type: ECDSA
    Domains: example.com *.example.com
    Expiry Date: 2026-06-20 00:00:00+00:00 (VALID: 89 days)
    Certificate Path: /etc/letsencrypt/live/example.com/fullchain.pem
    Private Key Path: /etc/letsencrypt/live/example.com/privkey.pem

Certificate File Locations

After issuance, certificate files are stored under /etc/letsencrypt/. The live/ directory contains symlinks to the most recent versions:

FilePathPurpose
Full chain/etc/letsencrypt/live/example.com/fullchain.pemCertificate + intermediate CA (use this in Nginx/Apache)
Private key/etc/letsencrypt/live/example.com/privkey.pemPrivate key — protect carefully
Chain only/etc/letsencrypt/live/example.com/chain.pemIntermediate CA chain only
Cert only/etc/letsencrypt/live/example.com/cert.pemDomain certificate without chain

Always reference the live/ path in your server configuration. The actual files are in /etc/letsencrypt/archive/example.com/ and are rotated on each renewal. The symlinks in live/ are updated automatically — your web server config never needs to change.

Configure Nginx to use the wildcard certificate:

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    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-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
    ssl_prefer_server_ciphers off;

    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
}

Auto-Renewal with systemd

Certbot automatically installs a systemd timer when installed via snap or apt.

Verify the Timer

sudo systemctl status certbot.timer
sudo systemctl list-timers certbot.timer

The timer triggers twice daily at random times to spread load on Let’s Encrypt servers. Certbot only requests a new certificate when the existing one has 30 or fewer days remaining.

Add a Deploy Hook for Nginx

A deploy hook runs after a certificate is successfully renewed. Use it to reload your web server so it picks up the new certificate:

# One-time deploy hook
sudo certbot renew --deploy-hook "systemctl reload nginx"

# Permanent: write to renewal configuration
sudo bash -c 'cat >> /etc/letsencrypt/renewal/example.com.conf <<EOF

[renewalparams]
deploy_hook = systemctl reload nginx
EOF'

For HAProxy, which reads certificates differently:

# Combine fullchain and privkey into a single PEM for HAProxy
cat /etc/letsencrypt/live/example.com/fullchain.pem \
    /etc/letsencrypt/live/example.com/privkey.pem \
    > /etc/haproxy/certs/example.com.pem
systemctl reload haproxy

Create a script at /etc/letsencrypt/renewal-hooks/deploy/reload-haproxy.sh and make it executable:

#!/bin/bash
cat /etc/letsencrypt/live/example.com/fullchain.pem \
    /etc/letsencrypt/live/example.com/privkey.pem \
    > /etc/haproxy/certs/example.com.pem
systemctl reload haproxy
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-haproxy.sh

Test Renewal

Always test with a dry run before relying on automated renewal:

sudo certbot renew --dry-run

A successful dry run confirms the DNS plugin credentials are valid, the API token has the correct permissions, and the renewal pipeline will work when the certificate nears expiry.

Multi-Domain Wildcards

A single certificate can include multiple wildcard SANs. This is useful when managing staging, production, and internal environments with different base domains.

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

Certbot will create a certificate with four SANs covering both domains and all their single-level subdomains. Both domains must be managed in Cloudflare (or you must configure separate credentials for each DNS provider used).

For nested subdomains, add explicit wildcards:

-d "*.api.example.com" \
-d "*.internal.example.com"

Note that *.example.com does NOT cover *.api.example.com. Each level requires a separate wildcard entry.

Rate Limits

Let’s Encrypt enforces rate limits to prevent abuse. Understand these before testing extensively:

LimitValueWindow
Certificates per registered domain507 days
Duplicate certificates57 days
Failed validations51 hour (per account per hostname)
New orders3003 hours
Accounts per IP103 hours

The most commonly hit limit during development is 5 duplicate certificates per week. A duplicate certificate covers exactly the same set of domains as an existing certificate.

Testing with Staging

The Let’s Encrypt staging environment issues real-format certificates but from an untrusted CA. Use it during development to avoid consuming rate limit quota:

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

Staging certificates display a “Fake LE Root X1” issuer in browsers. Once your configuration works correctly, delete the staging certificate and obtain a production one:

sudo certbot delete --cert-name example.com
sudo certbot certonly \
  --dns-cloudflare \
  --dns-cloudflare-credentials /etc/letsencrypt/cloudflare/credentials.ini \
  -d example.com \
  -d "*.example.com"

Use --dry-run on existing certificates to test renewal without actually issuing a new certificate:

sudo certbot renew --dry-run --cert-name example.com

Certbot vs Alternatives

ToolWildcard SupportAuto-DNSLet’s EncryptOther CAsNotes
CertbotYes (DNS plugins)Plugin-basedYesLimitedOfficial EFF client, widest plugin ecosystem
acme.shYesBuilt-in (150+ providers)YesYesBash-only, no dependencies, highly portable
CaddyYes (native)AutomaticYesYesZero-config; DNS module must be compiled in
TraefikYes (native)AutomaticYesLimitedIdeal for container environments
Commercial CAYesNo (manual)NoYes$100–$300/year, OV/EV options, no automation

For most Linux server administrators already comfortable with Nginx or Apache, Certbot with a DNS plugin is the most straightforward path. If you manage many domains across many servers, acme.sh’s zero-dependency Bash approach scales better. For container-native environments, Traefik’s automatic HTTPS with DNS challenge removes all manual steps.

Troubleshooting Common Errors

CAA Record Blocking Issuance

Symptom: Error: CAA record for example.com prevents issuance

CAA (Certification Authority Authorization) records restrict which CAs may issue certificates for your domain. If your zone has a CAA record that does not include letsencrypt.org, issuance will fail.

Check your CAA records:

dig +short CAA example.com

If no Let’s Encrypt entry exists, add one:

example.com.  CAA 0 issue "letsencrypt.org"
example.com.  CAA 0 issuewild "letsencrypt.org"

The issuewild tag is specifically required for wildcard certificate authorization.

DNS Propagation Delays

Symptom: DNS problem: NXDOMAIN looking up TXT for _acme-challenge.example.com

The ACME server queried DNS before your TXT record propagated. Increase the propagation wait:

--dns-cloudflare-propagation-seconds 120

For providers with slow propagation, you may need 300 seconds or more. Verify the TXT record is visible from a public resolver before Certbot validates:

dig @8.8.8.8 +short TXT _acme-challenge.example.com
dig @1.1.1.1 +short TXT _acme-challenge.example.com

Rate Limit Hit

Symptom: Error creating new order :: too many certificates already issued for exact set of domains

You have issued 5 duplicate certificates for the same domain set within 7 days. Options:

  • Wait for the rate limit window to expire (check expiry at crt.sh).
  • Add or remove a domain from the SAN list to make it a non-duplicate.
  • Use --staging for further testing.

Check issuance history:

curl -s "https://crt.sh/?q=example.com&output=json" | python3 -m json.tool | grep -E '"not_before"|"name_value"' | head -20

Credentials File Permission Error

Symptom: Unsafe permissions on credentials configuration file

The plugin refuses to use the credentials file if its permissions are too permissive:

sudo chmod 600 /etc/letsencrypt/cloudflare/credentials.ini
sudo chown root:root /etc/letsencrypt/cloudflare/credentials.ini

Renewal Fails with API Token Error

Symptom: Error: 9103 - Authentication error (Cloudflare) or similar API errors

The API token may have expired or been revoked. Verify it:

curl -s -X GET "https://api.cloudflare.com/client/v4/user/tokens/verify" \
  -H "Authorization: Bearer YOUR_TOKEN" | python3 -m json.tool

Generate a new token in the Cloudflare dashboard and update the credentials file.

Summary

Wildcard SSL certificates from Let’s Encrypt give you the flexibility to secure unlimited subdomains under a single certificate at no cost. The key points to remember:

  • Wildcards require DNS-01 challenge — HTTP-01 cannot validate *.example.com.
  • Manual DNS validation works for one-time issuance but does not support auto-renewal.
  • DNS plugins (certbot-dns-cloudflare, certbot-dns-route53, etc.) enable fully automated renewal.
  • The Cloudflare API token must have Zone:DNS:Edit permission; credentials file must be chmod 600.
  • Certificate files live at /etc/letsencrypt/live/ — always use fullchain.pem in your web server config.
  • Deploy hooks reload Nginx, HAProxy, or other services after each renewal.
  • Rate limits are strict — use --staging for testing and --dry-run for renewal validation.
  • CAA records must include letsencrypt.org with issuewild for wildcard authorization.