Skip to content

CAA record

Any certificate authority in a browser's trust store can issue a certificate for any domain. That's not a bug in the design — it's the design, and it's the weakest thing about the public PKI. A single CA that's compromised, tricked, or simply careless can issue a valid certificate for a domain it has no relationship with, and every browser will accept it.

A CAA record is a domain's way of narrowing that down: these are the only CAs allowed to issue for me.

dig +short CAA example.com
0 issue "letsencrypt.org"
0 issuewild "letsencrypt.org"
0 iodef "mailto:[email protected]"

Three fields

0 issue "letsencrypt.org"
│ │     └── value: which CA, by its documented identifier
│ └──────── tag: what this record controls
└────────── flags: 0, or 128 for "critical"

Three tags cover everything:

  • issue — this CA may issue certificates for this domain.
  • issuewild — this CA may issue wildcard certificates (*.example.com). When absent, the issue tag governs wildcards too; when present, it overrides for wildcards specifically.
  • iodef — where to report a policy violation. A CA that receives a request it must refuse can notify this address, which turns the record into a small intrusion-detection signal: a report means someone tried to get a certificate for your domain.

The flags byte is 0 almost always. 128 marks the record critical, meaning a CA that doesn't understand the tag must refuse to issue rather than proceed.

To forbid issuance entirely — for a domain that serves nothing and should never have a certificate:

0 issue ";"

The semicolon is the documented value for "no CA is authorised." A parked domain or an internal-only name with this record set cannot have a public certificate issued for it, which closes off a real avenue for phishing with legitimate-looking certificates.

Who checks it, and when

This is the part that determines what CAA actually buys you.

Certificate authorities check it, at issuance time. It has been mandatory for CAs in the CA/Browser Forum's baseline requirements since September 2017: before issuing, the CA queries CAA and refuses if it isn't authorised.

Browsers do not check it, ever. A certificate that was issued in violation of a CAA record is still a valid certificate, and every client accepts it.

So CAA is a preventive control, not a detective one. It stops a mistaken or manipulated issuance from happening in the first place; it does nothing about a certificate that already exists. The detective half of the job belongs to Certificate Transparency logs, which record every certificate a public CA issues and can be monitored for your domain — the two are complementary, and neither replaces the other.

The tree walk that catches people out

CAA is checked by walking up the domain tree from the name being issued for, stopping at the first level that has any CAA record at all.

Requesting a certificate for api.staging.example.com, a CA queries in this order:

api.staging.example.com   → no CAA records
staging.example.com       → no CAA records
example.com               → 0 issue "letsencrypt.org"   ← stops here

The policy at example.com applies. Two consequences follow, and both cause real failures:

A record on a subdomain replaces the parent's policy, it doesn't add to it. Put 0 issue "digicert.com" on staging.example.com and Let's Encrypt can no longer issue there, even though the parent allows it. The walk stops at the first level with a record, and that record is the whole policy.

Adding a CAA record to a domain that never had one can break renewals immediately. Every certificate previously issued by a CA you didn't list will fail to renew, silently, at whatever point in the next ninety days its renewal is due — long after the change that caused it. This is a genuinely nasty delayed failure.

List every CA you actually use before publishing the record

Take an inventory first, and include the ones that aren't obvious: a CDN that provisions certificates on your behalf (Cloudflare, Fastly, CloudFront), a load balancer with managed certificates, a mail service with its own hostname under your domain, and any monitoring or status-page vendor with a custom domain. Each of those uses a CA, and each will stop being able to renew if it isn't listed.

Then verify before trusting it:

dig +short CAA example.com
sudo certbot renew --dry-run

A dry-run renewal exercises the real issuance path, CAA check included, without consuming a certificate. Run it after any CAA change, and treat a failure there as the alarm it is.

Checking the tree yourself

CAA records are frequently set at the apex and nowhere else, so querying the subdomain alone tells you nothing:

for name in api.staging.example.com staging.example.com example.com; do
  echo -n "$name: "
  dig +short CAA "$name" | tr '\n' ' '
  echo
done
api.staging.example.com: 
staging.example.com: 
example.com: 0 issue "letsencrypt.org" 0 iodef "mailto:[email protected]" 

Empty, empty, then the policy — which is exactly the walk a CA performs, and the fastest way to answer "why did issuance fail for this one subdomain?"

Practice

  1. Query CAA for several large domains. Note which have records, which include iodef, and which set issuewild separately from issue.
  2. Write out the query sequence a CA performs for www.dev.example.co.uk, and say where the walk stops if only example.co.uk has a record.
  3. Inventory every service that obtains a certificate for a domain you control — including CDN and load-balancer managed certificates — and list the CA each one uses. Then write the CAA records that would allow exactly those.
  4. Explain in two sentences why a CAA record does nothing to protect a user from a certificate that has already been mis-issued, and what mechanism does cover that case.

Exercise 3 is the real work. The record itself takes thirty seconds to publish; knowing which CAs your infrastructure quietly depends on is the part that takes an afternoon, and skipping it is how CAA turns into an outage ninety days later.

Every record in this module has assumed a host already has an IP address worth publishing. Something has to hand that address out in the first place, automatically, to every device that joins a network — and that's a different protocol entirely, running underneath all of DNS rather than alongside it: DHCP.

Sources