How-to guide · Harden and secure

How to set CAA records for your domain

In one sentence

You will have DNS CAA records that let only the CAs you really use issue certificates for your names, with separate wildcard rules and an address for violation reports.

Task
Harden and secure
Platforms
  • Linux
  • macOS
  • Windows
Who it is for
Level
Practitioner · 5 steps
On this page
  1. Before you start
  2. Steps
  3. 1. Find every CA that issues for you
  4. 2. Check what is published today
  5. 3. Write your records
  6. 4. Optional: lock issuance to your ACME account
  7. 5. Publish and test before the next renewal
  8. Check it worked
  9. If something goes wrong
  10. Why this works
  11. Go deeper

Before you start

  • Access to your domain’s DNS, through the provider’s web console, API or zone file.
  • A list of the CAs your organisation uses. Step 1 shows how to build it.
  • dig (Linux, macOS or WSL) or curl for testing.

Why this matters: a CAA record tells every public CA which CAs may issue certificates for your names. CAA is defined in RFC 8659, and since 8 September 2017, CAs must check CAA records before issuing. One wrong record also blocks your own renewals, so build the list carefully.

Steps

1. Find every CA that issues for you

Do not guess. Look at what was really issued:

  • Certificate Transparency. Search the logs for your domain and note every issuer of the last year, as in How to monitor Certificate Transparency.
  • Your CDN, cloud and SaaS providers. Many obtain certificates for your names on your behalf, sometimes from more than one CA. Their documentation lists the CAA values they need.
  • Your teams. Ask who buys certificates, and from whom.

Then look up each CA’s issuer domain name, the value CAA uses. The root programs’ shared database, CCADB, publishes the list for every CA. Some common ones, as listed there in October 2026:

CA CAA value
Let’s Encrypt letsencrypt.org
Google Trust Services pki.goog
DigiCert digicert.com (also accepts www.digicert.com)
Sectigo sectigo.com (also comodoca.com)
Amazon Trust Services amazon.com (also amazontrust.com, awstrust.com, amazonaws.com)
GlobalSign globalsign.com
SSL.com ssl.com

2. Check what is published today

Look at the records at your domain:

dig CAA example.com +short

On Windows, Resolve-DnsName does not know the CAA type. Use dig in WSL, or ask a DNS-over-HTTPS resolver:

curl -s -H "accept: application/dns-json" "https://cloudflare-dns.com/dns-query?name=debian.org&type=CAA"
{"Status":0,"TC":false,"RD":true,"RA":true,"AD":true,"CD":false,"Question":[{"name":"debian.org","type":257}],"Answer":[{"name":"debian.org","type":257,"TTL":28800,"data":"0 iodef \"mailto:dsa@debian.org\""},{"name":"debian.org","type":257,"TTL":28800,"data":"128 issue \"letsencrypt.org;validationmethods=dns-01;accounturi=https://acme-v02.api.letsencrypt.org/acme/acct/346607\""},{"name":"debian.org","type":257,"TTL":28800,"data":"128 issuewild \";\""}]}

A CA does not stop at the exact name. It climbs the DNS tree and uses the first name that has CAA records (RFC 8659 section 3), and each lookup follows CNAMEs. This small script does the same, so you can see which records apply to any name:

#!/bin/sh
# caa-climb.sh: show the CAA records a CA would apply to a name (RFC 8659 section 3)
name="$1"
while [ -n "$name" ]; do
  records=$(dig CAA "$name" +short | grep -E '^[0-9]+ ')
  if [ -n "$records" ]; then
    echo "CAA records that apply, found at $name:"
    echo "$records"
    exit 0
  fi
  case "$name" in
    *.*) name="${name#*.}" ;;
    *) name="" ;;
  esac
done
echo "No CAA records: CAA does not restrict issuance."

Real results from 20 October 2026:

$ sh caa-climb.sh www.debian.org
CAA records that apply, found at debian.org:
128 issue "letsencrypt.org;validationmethods=dns-01;accounturi=https://acme-v02.api.letsencrypt.org/acme/acct/346607"
0 iodef "mailto:dsa@debian.org"
128 issuewild ";"

$ sh caa-climb.sh example.com
No CAA records: CAA does not restrict issuance.

www.debian.org has no records of its own, so its parent’s apply. www.github.com is a CNAME to github.com, so a lookup at www.github.com already returns github.com’s records.

3. Write your records

A CAA record has three parts: flags (almost always 0), a tag and a value. The main tags:

  • issue: a CA that may issue certificates for the name. Several issue records add up: any one of them allows that CA.
  • issuewild: the same, for wildcard certificates only. If any issuewild record exists, it replaces all issue records for wildcards (section 4.3).
  • iodef: where a CA may report a request it refused.

One CA, no wildcards:

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

Two CAs, wildcards from only one:

example.com.  3600  IN  CAA  0 issue "letsencrypt.org"
example.com.  3600  IN  CAA  0 issue "digicert.com"
example.com.  3600  IN  CAA  0 issuewild "digicert.com"

A domain that never needs certificates, such as a parked or mail-only domain:

parked-example.com.  3600  IN  CAA  0 issue ";"

A subdomain with different rules. Records at a subdomain replace the parent’s for that name and everything below it; they are not merged. If shop.example.com uses a provider whose CA you do not use elsewhere, give it its own complete set:

shop.example.com.  3600  IN  CAA  0 issue "pki.goog"

If the subdomain is a CNAME to the provider, you cannot add records next to it. The CA then uses the CNAME target’s CAA records if it has any, and otherwise yours from the parent domain, so your apex list must include the provider’s CA.

In a web console, enter the same three parts in separate fields. With the Azure CLI, for example:

az network dns record-set caa add-record -g my-dns-rg -z example.com -n @ \
  --flags 0 --tag issue --value letsencrypt.org

4. Optional: lock issuance to your ACME account

RFC 8657 adds two parameters to issue and issuewild. accounturi allows only one ACME account, and validationmethods allows only certain challenge types:

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

An attacker who briefly controls your web server can then no longer pass an HTTP challenge with an account of their own. Let’s Encrypt honours both parameters (Let’s Encrypt: CAA). For all public CAs, the rule is: public CAs must process the accounturi and validationmethods CAA parameters of RFC 8657 (from 15 March 2027). Until then, check that your CA supports them, because RFC 8657 section 5.2 warns that they are not effective unless the CA’s support is established beforehand.

Find your account URL in your ACME client’s account files. Keep a second record for a backup account, or every renewal stops when you replace the account.

5. Publish and test before the next renewal

  1. Lower the TTL to a few minutes while you change records, so mistakes are quick to fix.
  2. Publish the records, then run caa-climb.sh for every important name, including names behind CNAMEs.
  3. Request a test certificate from each CA you allowed, for example with certbot certonly --dry-run against Let’s Encrypt staging, which checks CAA like production.
  4. Raise the TTL again.

Check it worked

Run the climb for the names you care about and compare the result with your list from step 1:

for n in example.com www.example.com shop.example.com; do sh caa-climb.sh "$n"; echo; done

Then try what should fail. If you set issuewild ";", a dry run for *.example.com must be refused with a CAA error. A refusal you expected is proof that the CA reads your records.

If something goes wrong

  • A renewal fails with CAA record for example.com prevents issuance (Let’s Encrypt’s wording; other CAs word it differently). The CA is missing from issue, or from issuewild for a wildcard. Add it, or remove the name from the certificate.
  • A provider’s renewal for one subdomain fails. That subdomain is a CNAME to the provider, or inherits your apex records, and your list lacks the provider’s CA. Check it with caa-climb.sh.
  • The CA reports a lookup error or SERVFAIL. Some old DNS servers answer badly for unknown record types, and broken DNSSEC makes the answer bogus. Since 15 March 2026, public CAs must validate DNSSEC on every DNS query of their CAA lookups, so a bogus answer stops issuance. Test with dig CAA example.com +dnssec through a validating resolver: you want status: NOERROR, and SERVFAIL points at broken DNSSEC.
  • A typo in the value, such as letsencrypt.com or extra quotes typed into a console field, silently allows nobody. Compare each value with CCADB.
  • A critical flag (128) on a tag the CA does not know stops that CA from issuing. Use flag 0 unless you mean it.

Why this works

Every public CA must look up CAA for every name in a certificate before it issues, and refuse if the records do not allow it (domain validation). CAA is checked only at issuance, by CAs; browsers never look at it, so it cannot block a certificate that already exists. That is why it pairs with Certificate Transparency, which shows you every certificate that does get issued.

Go deeper

Early edition. Checked against the standards; an independent expert review is in progress. Found a mistake? Tell us.

Last reviewed Edition 0.9