ACME clients and automation

How to get a wildcard certificate with Certbot and Cloudflare DNS

By · Last verified

Certbot's Cloudflare DNS plugin gets a Let's Encrypt wildcard certificate for example.com and *.example.com by creating DNS-01 TXT records with a scoped Cloudflare API token. Renewal reuses the saved settings, and a deploy hook reloads the web server.

The steps apply to Linux servers with Certbot 4.1 or later installed from snap (or pip) and DNS hosted on Cloudflare.

Prerequisites

DNS-01 needs no inbound ports, only a Cloudflare API token for the zone.

  • A Linux server with systemd, snapd and sudo access.
  • The domain's nameservers on Cloudflare (the zone shows as Active).
  • A Cloudflare API token created under My Profile → API Tokens with the permission Zone → DNS → Edit, limited to example.com.
  • Outbound HTTPS to Let's Encrypt and api.cloudflare.com.

Step 1: Install Certbot and the Cloudflare plugin

The Cloudflare plugin is a separate snap that Certbot loads once you allow plugins to run as root:

sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/local/bin/certbot
sudo snap set certbot trust-plugin-with-root=ok
sudo snap install certbot-dns-cloudflare
certbot plugins | grep -A1 dns-cloudflare

With a pip install in /opt/certbot, run sudo /opt/certbot/bin/pip install certbot-dns-cloudflare instead.

Step 2: Store the API token in a credentials file

Certbot reads the token from an INI file that only root can read:

sudo mkdir -p /root/.secrets/certbot
sudo tee /root/.secrets/certbot/cloudflare.ini > /dev/null <<'EOF'
dns_cloudflare_api_token = your-cloudflare-api-token
EOF
sudo chmod 600 /root/.secrets/certbot/cloudflare.ini

Certbot prints a warning if other users on the system can read the file.

Step 3: Issue the wildcard certificate

Request the apex and the wildcard in one certificate:

sudo certbot certonly \
  --dns-cloudflare \
  --dns-cloudflare-credentials /root/.secrets/certbot/cloudflare.ini \
  -d example.com -d '*.example.com'

Quote '*.example.com' so the shell does not expand it. Including example.com matters because the wildcard does not cover the bare domain. The files land in /etc/letsencrypt/live/example.com/.

Step 4: Use the certificate and add a reload hook

Point the web server at fullchain.pem and privkey.pem, for example in Nginx:

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

Because certonly does not touch the server configuration, add a deploy hook so renewals reload it:

sudo certbot reconfigure --cert-name example.com --deploy-hook "systemctl reload nginx"

Alternatively, -i nginx on the issue command lets the Nginx installer edit and reload Nginx for you.

Verify

The apex and every subdomain should present the same certificate, with example.com and *.example.com in its SAN list. Check them with the SSL checker, or from a terminal:

curl ssl.sh/example.com
curl ssl.sh/api.example.com

On the server:

sudo certbot certificates
openssl x509 -in /etc/letsencrypt/live/example.com/cert.pem -noout -ext subjectAltName

Automatic renewal

The plugin name, credentials path and propagation delay are saved in /etc/letsencrypt/renewal/example.com.conf, so the snap timer renews the wildcard twice a day as needed, following Let's Encrypt's ARI window on Certbot 4.1 and later. Test it:

sudo certbot renew --dry-run
systemctl list-timers | grep certbot

Keep the token valid: if you rotate or delete it, update the credentials file or renewals will fail silently until expiry. Add the domain to ssl.sh monitoring (in development) because Let's Encrypt no longer emails before expiry. For hook details, see Certbot renewal hooks.

Troubleshooting

  • Error determining zone_id or 9109: Invalid access token: the token is wrong, expired or not scoped to this zone; create a new token with Zone → DNS → Edit.
  • Unsafe permissions on credentials configuration file: run sudo chmod 600 on the ini file.
  • DNS problem: NXDOMAIN looking up TXT: propagation was too slow; add --dns-cloudflare-propagation-seconds 60.
  • CAA record prevents issuance: a CAA record without letsencrypt.org, or an issuewild record that excludes it; see CAA record prevents issuance.
  • too many certificates already issued: use --dry-run or --test-cert while testing; see Let's Encrypt rate limits.
  • Subdomain shows a name mismatch: names two levels deep (a.b.example.com) are not covered by *.example.com; see NET::ERR_CERT_COMMON_NAME_INVALID.

FAQ

Do I need to turn off the Cloudflare proxy (orange cloud)?

No. DNS-01 only checks a TXT record, so proxied A/AAAA records are irrelevant. You only need the proxy off for HTTP-01, and only when Cloudflare cannot pass the challenge request to your origin.

Should I use a Let's Encrypt certificate or a Cloudflare Origin CA certificate on the origin?

Both work with SSL mode Full (strict). A Let's Encrypt certificate is publicly trusted, so the origin still works if you disable the proxy. An Origin CA certificate is trusted only by Cloudflare but can last up to 15 years.

How long should propagation-seconds be?

The plugin's default is 10 seconds, which is enough for Cloudflare in most cases. If validation fails with a missing TXT record, retry with --dns-cloudflare-propagation-seconds 60; the value is saved for renewals.

Sources

  1. certbot-dns-cloudflare documentation
  2. Certbot instructions (snap)
  3. Certbot User Guide
  4. Let's Encrypt: Challenge Types