ACME clients and automation
How to get a wildcard certificate with Certbot and Cloudflare DNS
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 600on 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 anissuewildrecord that excludes it; see CAA record prevents issuance. - too many certificates already issued: use
--dry-runor--test-certwhile 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.