What it does
Before a publicly trusted certificate authority issues a certificate for a name, it is required (since 2017, by the CA/Browser Forum baseline requirements) to look up the name's CAA records. If records exist and none of them names that CA, it must refuse. If there are no CAA records anywhere up the tree, any CA may issue. So CAA does not stop an attacker who has taken over your DNS, because they can change the record; it stops a CA being tricked or compromised into issuing for you, and it stops someone in your own organisation ordering a certificate from a provider you did not approve.
Browsers do not check CAA. It is enforced at issuance time, by the CA, and only then.
The record
example.com. CAA 0 issue "letsencrypt.org" example.com. CAA 0 issue "digicert.com" example.com. CAA 0 issuewild ";" example.com. CAA 0 iodef "mailto:[email protected]"
| Tag | Meaning |
|---|---|
| issue | This CA may issue certificates for the name (and, unless issuewild says otherwise, wildcards too). One record per permitted CA. The value is the CA's identifying domain, which each CA publishes: letsencrypt.org, digicert.com, sectigo.com, pki.goog, amazon.com, globalsign.com. |
| issuewild | Same, but for wildcard certificates only. If present, it overrides issue for wildcards. A value of ; means nobody may issue a wildcard. |
| iodef | Where a CA should report a request it refused because of CAA: a mailto: or https: URL. Not every CA sends these, but the ones that do give you an early warning that someone tried. |
The leading 0 is the flags byte. 128 marks a record as critical, meaning a CA that does not understand the tag must refuse; you do not need it for the three standard tags. The value ; on its own for issue means no CA at all may issue for the name, which is useful for a domain that should never have a certificate.
Some CAs support parameters after the domain, for instance Let's Encrypt's accounturi and validationmethods, which tie issuance to one ACME account or to DNS-01 only. They are worth using if you have one automated issuer and want to rule out everything else.
How a CA walks the tree
The lookup is not just on the name being certified. The CA starts at the exact name, and if it finds no CAA records there, moves up one label at a time until it either finds a CAA record set or runs out of parents. The first set it finds is the one that applies; it does not merge them. So:
- A CAA record on
example.comgovernswww.example.com,api.example.comand everything else beneath it that has no CAA of its own. - A CAA record on
api.example.comoverrides the apex record forapi.example.comand its children, entirely. If it lists only DigiCert, Let's Encrypt cannot issue forapi.example.comeven though the apex allows it. - If
www.example.comis a CNAME, the CA checks the CNAME target's CAA first, then falls back to walkingwww.example.com's own parents. This catches people who CNAME to a hosting provider that publishes a restrictive CAA on their side.
You can reproduce the walk yourself: look up CAA on the full name, then on each parent, with the DNS lookup tool. The first non-empty answer is what the CA saw.
The renewal that fails months later
This is the failure I have been called about most. The sequence is always the same. Someone reads that CAA is good practice and adds issue "digicert.com" at the apex, because that is where the main certificate came from. Nothing happens; the existing certificates are fine and CAA is only checked at issuance. Three months later the Let's Encrypt certificate on a staging host, or a CDN's automatically managed certificate, or the mail server, comes up for renewal. The CA checks CAA, finds only DigiCert permitted, refuses, and the renewal job logs an error nobody is watching. The certificate expires on the day it was always going to expire and the site goes down with a warning that has nothing obviously to do with DNS.
The error message from the CA is clear if you find it. Let's Encrypt says CAA record for example.com prevents issuance. Other CAs say something similar. The fix is a one-line DNS change and a retry. Avoiding it means one thing: before adding or changing CAA, list every CA that currently issues for anything under the domain. The SSL/TLS certificate checker shows the issuer of the certificate a host is actually serving; run it against every public hostname you can think of, including mail hosts on port 25 and 465, and CDN and SaaS hostnames you have CNAME'd to, because their issuer is theirs, not yours.
A sensible policy
- Inventory issuers first. Certificates from your own automation, your CDN, your hosting platform, your mail provider, any SaaS on a custom domain. Their issuers all need an
issuerecord, or a CAA on the specific subdomain that permits theirs. - Publish at the apex: one
issueper CA, anissuewildif you want to restrict wildcards further, and aniodefso you hear about refusals. - Where a subdomain is delegated to a third party (a CNAME to a hosting provider that manages its own certificate), either include their CA at the apex or put a CAA on that subdomain listing theirs.
- Put a CAA check in your certificate monitoring. If a renewal fails, the log line should reach a person.
The domain health checker reports whether the apex publishes CAA as part of its DNS pillar, with a link into the full TLS report where the issuer of the certificate being served is listed, so the two things you need to compare are a click apart.