CAA Records for Let's Encrypt: Wildcard Certificates, Testing, and Fixes

Updated on Oct 11, 2026
Mathew M
12 MINS READ
Table of Contents
CAA Records for Let's Encrypt

Anyone can ask a public certificate authority (CA) for a certificate for your domain, and by default every public CA is allowed to answer. A CAA record in DNS lets you say which CAs may issue for your domain. In this guide, we will set up CAA Records for Let's Encrypt, issue normal and wildcard certificates, test how inheritance works, and break issuance on purpose to see the error.

Requirements

Before you start to configure CAA records for Let's Encrypt, make sure you have the items below ready: 

  • A domain name you control.
  • A DNS provider that lets you add CAA records.
  • An Ubuntu 26.04 LTS server or 24.04 with a public IP and ports 80 and 443 open.
  • A user with sudo access.
  • An API token from your DNS provider for the wildcard test. We use Cloudflare as the example.

Also, you must update the server and install the required tools:

Bash
sudo apt update && sudo apt upgrade -ysudo apt install bind9-dnsutils nginx curl openssl snapd -y

Once you are done, proceed to the following steps to complete the setup.

Set Up Certbot as Your ACME Client

Certbot is the tool that asks Let's Encrypt for your certificates and renews them for you. In this step, you must install it with Snap, which keeps it up to date.

Remove any old Certbot from apt, then install it with the snap:

Bash
sudo apt remove certbot -ysudo snap install --classic certbotsudo ln -sf /snap/bin/certbot /usr/bin/certbotcertbot --version

Add CAA Records for Let's Encrypt

Now it is time to tell the world that only Let's Encrypt can issue certificates for your domain (example.com). In this step, you must add three CAA records at your DNS provider, including issue, issuewild, and iodef. Use a low TTL for now so any changes you make show up quickly while you test.

Name Type Flags Tag Value
@ CAA 0 issue letsencrypt.org
@ CAA 0 issuewild letsencrypt.org
@ CAA 0 iodef mailto:security@example.com

If your DNS panel has only one box for the whole record, you can use this format:

Bash
example.com.  300  IN  CAA  0 issue "letsencrypt.org"example.com.  300  IN  CAA  0 issuewild "letsencrypt.org"example.com.  300  IN  CAA  0 iodef "mailto:security@example.com"

Some DNS panels want only letsencrypt.org in the value box, with no quotes. Do not add https:// or any extra text. A wrong name will block issuance.

If you use more than one CA, add one more issue line for each CA. For example: 0 issue "digicert.com".

If you never want wildcard certificates, use this line instead of the issuewild line above:

Bash
example.com.  300  IN  CAA  0 issuewild ";"

A single semicolon means "no CA is allowed."

Verify CAA Resolution with dig

Your CAA records are now in DNS, but you need to check that the world can see them. You can use the dig command to look up your records. First, ask your normal resolver:

Bash
dig +noall +answer CAA example.com

In the output, you should see all three records:

Bash
example.com.   300  IN  CAA  0 issue "letsencrypt.org"example.com.   300  IN  CAA  0 issuewild "letsencrypt.org"example.com.   300  IN  CAA  0 iodef "mailto:security@example.com"

Then, ask a public resolver and your own authoritative name server. The CA asks your authoritative servers, so they matter most:

Bash
dig +noall +answer CAA example.com @1.1.1.1dig +short NS example.comdig +noall +answer CAA example.com @ns1.your-dns-provider.com

Replace ns1.your-dns-provider.com with one name from the NS output. All answers should match. If you get no answer, wait for the TTL and try again.

Also, you must check the status of the reply with the command below:

Bash
dig CAA example.com | grep status

The status should be NOERROR. A SERVFAIL status is a problem. Let's Encrypt cannot issue a certificate when the CAA lookup fails because it cannot tell whether a record is hidden by the error.

Check DNSSEC for CAA Records

If you turned on DNSSEC, your CAA answers are signed with the rest of the zone. A broken DNSSEC setup is the most common cause of SERVFAIL for CAA. Check it with:

Bash
dig CAA example.com +dnssec @1.1.1.1

Look for the ad flag in the header line. It means the resolver validated the answer. If you have not set up DNSSEC yet, you can follow our DNSSEC and WAF configuration guide.

Test Issuance with HTTP-01 on Staging

Let's Encrypt staging has much higher limits and issues certificates that browsers do not trust. Always test here.

First, make sure Nginx is running and serves the default folder /var/www/html:

Bash
sudo systemctl enable --now nginxsudo ufw allow 'Nginx Full' 2>/dev/null || true

Then, request a test certificate. We use a separate certificate name so it does not mix with the real one later:

Bash
sudo certbot certonly --webroot -w /var/www/html \  --staging \  --cert-name test-http \  -d example.com -d www.example.com \  -m admin@example.com --agree-tos --no-eff-email

Both names must point to this server in DNS. When it works, Certbot saves the files in /etc/letsencrypt/live/test-http/. Check the issuer with:

Bash
sudo openssl x509 -in /etc/letsencrypt/live/test-http/fullchain.pem -noout -issuer -subject -dates

The issuer name contains STAGING. That proves the CA checked your CAA records and issued.

Test a Wildcard Certificate with DNS-01

Let's Encrypt only issues wildcard certificates with the DNS-01 challenge. Certbot needs a DNS plugin for this. You must pick the plugin for your DNS provider.

This guide uses Cloudflare. Install the plugin with the commands below:

Bash
sudo snap set certbot trust-plugin-with-root=oksudo snap install certbot-dns-cloudflare

Create an API token in Cloudflare with the Zone: DNS: Edit permission for your zone. Then, create the credentials file:

Bash
sudo mkdir -p /etc/letsencryptsudo tee /etc/letsencrypt/cloudflare.ini > /dev/null <<'EOF'dns_cloudflare_api_token = YOUR_CLOUDFLARE_API_TOKENEOFsudo chmod 600 /etc/letsencrypt/cloudflare.ini

Next, request a test wildcard certificate with the following command:

Bash
sudo certbot certonly --dns-cloudflare \  --dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini \  --dns-cloudflare-propagation-seconds 60 \  --staging \  --cert-name test-wild \  -d example.com -d "*.example.com" \  -m admin@example.com --agree-tos --no-eff-email

Add both example.com and *.example.com. The wildcard does not cover the main domain by itself. Now check the names inside the certificate:

Bash
sudo openssl x509 -in /etc/letsencrypt/live/test-wild/fullchain.pem -noout -ext subjectAltName

How CAA Inheritance Works

The CA checks the name you ask for. If that name has no CAA record, it moves up one level and repeats until it finds a CAA record. It stops at the first one it finds.

For www.app.example.com, the CA checks in this order:

  • www.app.example.com
  • app.example.com
  • example.com

You can see this with dig. The subdomain has no record of its own, so the answer is empty. That is normal:

Bash
dig +noall +answer CAA www.app.example.comdig +noall +answer CAA example.com

The CA ignores the empty answer and uses the record on example.com.

Override CAA on a Subdomain

A subdomain can have its own CAA rule. The CA always uses the closest record, so a subdomain record replaces the main domain rule for that name only. In this step, you can add a different rule for api.example.com. Add this record at your DNS provider:

Bash
api.example.com.  300  IN  CAA  0 issue "digicert.com"

Then, use the following dig command to check it:

Bash
dig +noall +answer CAA api.example.com

Now only DigiCert can issue for api.example.com, and Let's Encrypt is blocked there. The rest of the domain stays on Let's Encrypt. If you still want Let's Encrypt on that subdomain, add both issue lines. Remove the test record when you are done.

How CAA Works with CNAME Records

CAA checks follow CNAME records like any DNS lookup. If blog.example.com is a CNAME to host.other-provider.net, the CA reads the CAA record at host.other-provider.net. A name with a CNAME cannot hold other records, so you cannot add CAA on that same name. You must put the allow rule on the target or on a higher level of the original name.

Break CAA on Purpose: See the ACME Error

At this point, you can see what happens when a CAA record is too strict. You must change your records so that only DigiCert is allowed, then try to get a certificate from Let's Encrypt. The request will fail, and you will see the error that Certbot shows.

First, remove the Let's Encrypt issue line and add:

Bash
example.com.  300  IN  CAA  0 issue "digicert.com"

Wait for the TTL, about 5 minutes, then confirm with dig command:

Bash
dig +noall +answer CAA example.com @1.1.1.1

Then, you must run a dry run. A dry run uses staging and does not save anything:

Bash
sudo certbot certonly --webroot -w /var/www/html --dry-run \  -d example.com -d www.example.com \  -m admin@example.com --agree-tos

You must see that the request fails. The CA error looks like this: Detail: CAA record for example.com prevents issuance.

In the ACME error, the type is urn:ietf:params:acme:error:caa. The message may appear when Certbot checks the domain, or later when it finishes the order. See the full log if you need it:

Bash
sudo tail -n 50 /var/log/letsencrypt/letsencrypt.log

Renewals also fail the same way. Run this, and you will see the same error for each certificate:

Bash
sudo certbot renew --dry-run

Wildcard-Only Failure

Another common mistake is a block on wildcards only. Put the Let's Encrypt issue record back, and change issuewild to a semicolon:

Bash
example.com.  300  IN  CAA  0 issue "letsencrypt.org"example.com.  300  IN  CAA  0 issuewild ";"

Normal certificates work, but the wildcard fails:

Bash
sudo certbot certonly --dns-cloudflare \  --dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini \  --dns-cloudflare-propagation-seconds 60 \  --dry-run \  -d example.com -d "*.example.com" \  -m admin@example.com --agree-tos

The wildcard name is refused because issuewild overrides issue for wildcards.

Common Causes

Most CAA errors come from a few small mistakes. Check the list below when Certbot says a CAA record blocks issuance:

  • The wrong CA name, like letsencrypt.com instead of letsencrypt.org.
  • Extra text in the value, like https://letsencrypt.org.
  • Another CAA record on a subdomain that does not list Let's Encrypt.
  • An old CAA record from your hosting provider or DNS template.
  • SERVFAIL or a timeout from your DNS server, often caused by broken DNSSEC or a firewall that drops unknown query types.

Fix the CAA Error and Run the Test Again

Now let's fix the records and make sure everything works again. You can put the correct CAA records back, wait for the old TTL to pass, and run the tests again. Both dry runs should pass. 

First, set the final records, with a normal TTL of 3600:

Bash
example.com.  3600  IN  CAA  0 issue "letsencrypt.org"example.com.  3600  IN  CAA  0 issuewild "letsencrypt.org"example.com.  3600  IN  CAA  0 iodef "mailto:security@example.com"

Wait for the old TTL to pass, then check with dig and repeat both dry runs. They should now pass:

Bash
dig +noall +answer CAA example.com @1.1.1.1sudo certbot renew --dry-run

Optional: Lock CAA to Your ACME Account

This step is optional, but it adds strong protection. You can lock your CAA records to your own ACME account. Then, only your account can get certificates for your domain, even from Let's Encrypt. Do this last, after your real certificate works.

First, issue the real certificate from the next section. Then get your account URL:

Bash
sudo certbot show_account

The account URL looks like https://acme-v02.api.letsencrypt.org/acme/acct/1234567890. Change the issue and issuewild records to this form:

Bash
example.com.  3600  IN  CAA  0 issue "letsencrypt.org;accounturi=https://acme-v02.api.letsencrypt.org/acme/acct/1234567890"example.com.  3600  IN  CAA  0 issuewild "letsencrypt.org;accounturi=https://acme-v02.api.letsencrypt.org/acme/acct/1234567890"

Staging uses different accounts, so staging tests will fail after this change. Always test first, and keep a note of the account number.

Also, you can limit the challenge type with ;validationmethods=dns-01. Let's Encrypt supports http-01, dns-01, and tls-alpn-01 here.

Issue the Real Wildcard Certificate

Once your tests have passed, you can get the real certificate. First, you must delete the staging test certificates:

Bash
sudo certbot delete --cert-name test-httpsudo certbot delete --cert-name test-wild

Then, request the real certificate. This is the same command as the wildcard test, without --staging:

Bash
sudo certbot certonly --dns-cloudflare \  --dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini \  --dns-cloudflare-propagation-seconds 60 \  --cert-name example.com \  -d example.com -d "*.example.com" \  -m admin@example.com --agree-tos --no-eff-email

Finally, check that it was issued with the following command:

Bash
sudo certbot certificates

Use the Certificate in Nginx

Your certificate is ready, so now you need to tell Nginx to use it. First, use the command below to create the site file:

Bash
sudo tee /etc/nginx/sites-available/example.com > /dev/null <<'EOF'server {    listen 80;    listen [::]:80;    server_name example.com *.example.com;    return 301 https://$host$request_uri;} server {    listen 443 ssl;    listen [::]:443 ssl;    http2 on;    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;     root /var/www/html;    index index.html;}EOF

Then, use the following commands to enable it, test the config, and reload:

Bash
sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/example.comsudo rm -f /etc/nginx/sites-enabled/defaultsudo nginx -tsudo systemctl reload nginx

Finally, use the command below to check that your site shows the new Let's Encrypt certificate:

Bash
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -issuer -dates

Renew Certificates Automatically with ACME

Let's Encrypt certificates are short-lived, so renewal must run on its own. The Certbot snap sets up a systemd timer that runs renewals twice a day. Check it with:

Bash
systemctl list-timers | grep certbot

Certbot renews a certificate when only one third of its life is left. Nginx must reload after each renewal, so you must add a deploy hook. After a successful renewal, Certbot runs every executable file in this folder:

Bash
sudo mkdir -p /etc/letsencrypt/renewal-hooks/deploysudo tee /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh > /dev/null <<'EOF'#!/bin/bashsystemctl reload nginxEOFsudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh

Next, run a dry run to test the full process, including the CAA check:

Bash
sudo certbot renew --dry-run

Why Automatic Renewal Matters

Let's Encrypt is making certificates shorter. The default lifetime drops to 64 days on February 10, 2027, and to 45 days on February 16, 2028. The time that a past domain check can be reused is also getting shorter.

In practice, your DNS credentials and CAA records will be checked on almost every renewal. Run sudo certbot renew --dry-run after every DNS change, and keep a monitor on dig for your CAA records. A quick monitor script:

Bash
sudo tee /usr/local/bin/check-caa.sh > /dev/null <<'EOF'#!/bin/bashDOMAIN="example.com"if dig +short CAA "$DOMAIN" @1.1.1.1 | grep -q 'letsencrypt.org'; then  echo "OK: CAA allows letsencrypt.org"else  echo "WARNING: CAA does not allow letsencrypt.org for $DOMAIN" >&2  exit 1fiEOFsudo chmod +x /usr/local/bin/check-caa.shsudo /usr/local/bin/check-caa.sh

You can run it from cron or your monitoring tool.

Conclusion

CAA Records for Let's Encrypt give you a simple safety lock for certificate issuance. You have learned to add issue, issuewild, and iodef records, check them with dig, issue test and wildcard certificates. You saw how a record that is too strict stops ACME. Always test with staging and certbot renew --dry-run after any DNS change.

If you are ready to lock down your own domain, you can register a domain with PerLod and limit certificate issuance to the CAs you trust.

We hope you enjoy this guide. For more detailed information, you can check the Let's Encrypt CAA Docs.

No. Without any CAA record, every CA may issue. A CAA record just adds a limit.

No. issue also covers wildcards. Add issuewild only if you want a different rule for wildcards.

Yes. The CA uses the closest record and stops there.

The CAA record does not allow Let's Encrypt for that name. Check the CA name and check for records on subdomains.

No. It limits which CAs may issue. Use it together with DNSSEC and good account security.