Skip to content
Control Plane Labs

CAA Record Checker: Find Gaps Before Renewal

How the CAA checker finds the record a CA uses, compares it with Certificate Transparency history, and flags renewal risks and partial account binding.

Control Plane Labs Staff

Published October 12, 2026

CAA has been mandatory for public CAs since September 2017, when the CA/Browser Forum passed Ballot 187. Every CA must look up the record before it issues. The trouble starts later, when the record and the way you actually get certificates drift apart. A DNS lookup tells you what the record says today. It cannot tell you whether that matches the CAs that issued for you last quarter. That comparison is what this tool adds.

Step 1: find the record that applies

A CA does not just query the exact name. RFC 8659 defines the “relevant resource record set” as the first non-empty CAA set found by starting at the name and removing one label at a time. For api.eu.example.com, a CA checks api.eu.example.com, then eu.example.com, then example.com, then com, and stops at the first name that has CAA.

Two consequences catch people out:

  • A record on the apex governs every subdomain that has none of its own. One issue line on example.com decides who can issue for hundreds of hostnames.
  • A single CAA record on a subdomain replaces the apex policy for that subtree completely. Nothing is merged from the parent.

The checker queries each name in turn through a public DNS-over-HTTPS resolver and shows which one supplied the record. When the answer comes from a parent, the result says so.

DNS aliases add a third rule. If a name is a CNAME, the resolver returns the CAA records of the target, and Cloudflare’s CAA documentation warns that a restrictive record on a CNAME target “take[s] precedence” over your own zone. The tool sees the same answer a CA would.

Step 2: parse what the record allows

Each record has a flags byte, a tag and a value. The checker handles the tags RFC 8659 defines:

  • issue lists a CA allowed to issue any certificate. Without an issuewild record, it also covers wildcards.
  • issuewild overrides issue for wildcard certificates only.
  • iodef gives CAs an address for reporting requests that violate your policy.
  • A value of ";" names no CA, so it forbids issuance for that tag.

Values are additive. As Let’s Encrypt’s CAA documentation puts it, “if any one of those records allows the CA to issue, then it is allowed.”

The checker also flags a critical flag (128) on a tag it does not recognize. RFC 8659 tells a CA to refuse issuance when it meets a critical tag it does not understand, so a stray flag can block every renewal.

Step 3: compare with Certificate Transparency

Every certificate from a public CA is published to the append-only logs defined in RFC 6962. The checker reads one page of certificates for the exact name from CertIndex, skips precertificates so each certificate counts once, and groups the last 13 months by issuing organization. It then maps each organization to the CAA identifiers that CA documents (for example letsencrypt.org, pki.goog, digicert.com or amazon.com) and reports:

  • “Issued here but not allowed now.” The most important finding. CAA is only evaluated at issuance, so a record edited after your last renewal does not affect the live certificate. It breaks the next one. Managed platforms make this easy to miss. Google Cloud’s managed-certificate documentation says that if CAA names only one of pki.goog and letsencrypt.org, “only that CA is used to create and renew your certificate.”
  • Allowed but unused. CAs that are on the list but issued nothing for this name in the window. One backup CA is a reasonable hedge against an outage. Five usually means a platform wrote the list. Cloudflare, for instance, adds its Universal SSL CAs automatically once a zone has any CAA record, and those additions do not appear in its dashboard.
  • Unmapped issuers. CAs the tool has no identifier for. Look the identifier up in section 4.2.1 of that CA’s CP/CPS.

Wildcard certificates are checked separately against issuewild when one exists. If CT shows no wildcards at all, the tool suggests adding issuewild ";".

CT history is a lower bound. The tool reads a single page, and an index can lag the logs. Treat “unused” as a prompt to check, not proof. The CT lookup shows the underlying records.

Step 4: check account binding

A plain allowlist does not stop someone who controls your DNS. They can pass domain validation with any allowed CA. RFC 8657 closes that gap with two parameters on issue and issuewild values. accounturi limits issuance to one ACME account, and validationmethods limits how the CA may verify control. Let’s Encrypt describes the purpose plainly: account binding ensures “someone who temporarily hijacks your domain, but doesn’t have access to your ACME Account key, can’t issue malicious certificates.”

The checker reports one of three states:

  • Every allowed value is bound. Good. A hijacker without your account key fails the CAA check.
  • Binding is partial. Some values are bound and others are not. Because values are additive, the unbound value is an open door, and the binding has no effect. In our survey of 60 developer sites, three of the four domains that used binding had this problem.
  • No binding. Common, and worth fixing on any domain an attacker would bother to target.

Step 5: use the suggested record

The tool ends with a starting-point record set built from the CAs seen in CT. It writes one issue line per CA with an accounturi=ACCOUNT placeholder. It adds issuewild ";" unless wildcards were issued, and an iodef contact at security@ your domain. Before publishing:

  1. Replace ACCOUNT with your ACME account URL. Certbot stores it in the account directory under /etc/letsencrypt/accounts. Remove the parameter for CAs you do not reach over ACME.
  2. Add every platform that requests certificates for you: CDNs, load balancers and SaaS custom domains.
  3. Lower the record’s TTL before the change and confirm the next renewal succeeds.

Limits

The checker queries one public resolver. A CA’s resolver could see a different answer during propagation or a split-horizon setup. It reads CT for the exact name you enter, so check the hostnames you actually serve, not only the apex. And it cannot see private CAs, which never write to public logs.

Frequently asked questions

Which CAA record does a CA use for a subdomain?+
The first non-empty CAA set found by starting at the exact name and removing one label at a time. If the subdomain has no CAA record, the apex record applies. If the subdomain has its own record, it replaces the apex policy entirely.
Why would a CA that issued my current certificate fail on renewal?+
CAA is checked at every issuance. If the record changed after your last certificate was issued and no longer lists that CA, the next request from it fails. The checker flags this by comparing the record with Certificate Transparency history.
Does accounturi work with every CA?+
accounturi identifies an ACME account, so it applies to CAs you use over ACME, such as Let's Encrypt and Google Trust Services. Some CAs document their own account parameter instead. Check your CA's CP/CPS before relying on it.
What does issuewild ";" do?+
It forbids every CA from issuing wildcard certificates for the name while leaving the issue records in charge of non-wildcard certificates. Use it when you never deploy wildcards.
Is a domain with no CAA record misconfigured?+
No. It means any publicly trusted CA may issue for the name. CAA narrows who can issue, and Certificate Transparency monitoring shows what was issued. Most domains benefit from both.
How current is the Certificate Transparency data in the checker?+
It reads one page of the newest certificates indexed for the exact name and limits the analysis to the last 13 months. Treat the counts as a lower bound and confirm in the CT lookup before removing a CA.

Tags: #caa, #dns, #tls, #certificate-transparency, #acme