CAA records tell certificate authorities which of them may issue certificates for a name, and a CA checks them only at the moment of issuance. Our own domain publishes no CAA at all, and its two main hostnames are served certificates from two different CAs. Both facts shape how a policy should be written: it has to describe what already issues before it can safely restrict anything.

What an empty CAA answer means

Live capture, 26 September 2026. Asking Cloudflare’s public resolver (1.1.1.1) for CAA at our apex and at its top-level domain:

$ dig +nocmd @1.1.1.1 and.guide CAA +noall +comments +authority
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 13245
;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; AUTHORITY SECTION:
and.guide.		1800	IN	SOA	frida.ns.cloudflare.com. dns.cloudflare.com. 2414193944 10000 2400 604800 1800

$ dig +nocmd @1.1.1.1 guide CAA +noall +comments +authority
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 38949
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; AUTHORITY SECTION:
guide.			3600	IN	SOA	v0n0.nic.guide. hostmaster.donuts.email. 1790433683 7200 900 1209600 3600

Read the header line first. NOERROR with ANSWER: 0 means the name exists and has no CAA records; the SOA in the authority section is there so resolvers can cache that negative answer. A CA evaluating and.guide finds nothing, climbs to guide., finds nothing again, and stops, because RFC 8659 never queries the root. With no relevant record set, CAA does not restrict issuance: any publicly trusted CA that completes domain validation may issue for and.guide or for any name beneath it. That is the default for most domains, not an error.

Two more details in this output matter on the day you add a record:

  • The negative answer for and.guide can be cached for up to 1,800 seconds (the SOA’s TTL and its last field), so a resolver may keep reporting “no CAA” for about 30 minutes after you publish one. The negative caching guide explains how that number is derived.
  • guide. came back with the ad flag and and.guide did not, because our zone is not DNSSEC-signed. That changes what a CA may do when a CAA lookup fails, covered in the diagnosis section below.

Two hostnames, two issuers

Live capture, 26 September 2026. The certificate each of our main hostnames presents, read with OpenSSL 3.6.3:

$ echo | openssl s_client -connect and.guide:443 -servername and.guide 2>/dev/null | openssl x509 -noout -issuer -dates -ext subjectAltName
issuer=C=US, O=Google Trust Services, CN=WE1
notBefore=Aug  8 05:34:39 2026 GMT
notAfter=Nov  6 06:34:36 2026 GMT
X509v3 Subject Alternative Name: 
    DNS:and.guide
$ echo | openssl s_client -connect www.and.guide:443 -servername www.and.guide 2>/dev/null | openssl x509 -noout -issuer -dates -ext subjectAltName
issuer=C=US, O=Let's Encrypt, CN=YE2
notBefore=Sep  6 14:25:26 2026 GMT
notAfter=Dec  5 14:25:25 2026 GMT
X509v3 Subject Alternative Name: 
    DNS:*.and.guide, DNS:and.guide

The apex is a Cloudflare Pages custom domain, and its certificate comes from Google Trust Services and names only and.guide. www.and.guide is a proxied hostname that exists to redirect, and Cloudflare’s edge presents a Let’s Encrypt certificate for and.guide and *.and.guide, the apex-plus-first-level coverage Cloudflare describes for Universal SSL. A third path exists too: our DNS-only wildcard points at our self-hosted deployment server for our own apps, and the apps it serves get Let’s Encrypt certificates that the server requests itself and Cloudflare never handles. The subdomain inventory runbook shows that history from Certificate Transparency logs.

A policy written from the www padlock alone would have described one issuer for three issuance paths. Build the issuer list from each platform’s documentation and from Certificate Transparency data for your domain, not from one browser tab.

What Cloudflare adds when you publish CAA

Cloudflare documents that when a zone uses Universal SSL and you add any CAA record, it adds records for its own certificate authorities automatically. Those records do not appear in the dashboard, but dig returns them. The documented set covers both issue and issuewild:

CA Value Cloudflare adds Cloudflare’s stated use
Let’s Encrypt letsencrypt.org Universal, advanced, and SSL for SaaS certificates
Google Trust Services pki.goog; cansignhttpexchanges=yes Universal, advanced, and SSL for SaaS certificates
SSL.com ssl.com Universal, advanced, and SSL for SaaS certificates
Sectigo sectigo.com Backup certificates only

Cloudflare says the list is not exhaustive and can change. Advanced certificates get no automatic records, and a subdomain setup depends on the parent zone either allowing Cloudflare’s CAs or having no CAA at all. Because CAA cannot tell who is asking, the hidden records authorize those CAs for anyone who passes validation, not only for Cloudflare.

For our zone, publishing 0 issue "letsencrypt.org" alone would therefore not produce a Let’s Encrypt-only policy. Resolvers would also return the hidden Google Trust Services, SSL.com, and Sectigo records, while the dashboard would show one line. If we published CAA for and.guide, we would list the issuers we depend on explicitly, so the dashboard shows the policy we actually rely on:

and.guide.  300  IN  CAA  0 issue "letsencrypt.org"
and.guide.  300  IN  CAA  0 issue "pki.goog; cansignhttpexchanges=yes"
and.guide.  300  IN  CAA  0 issue "ssl.com"

letsencrypt.org covers both Cloudflare’s edge certificates and the ACME client on our deployment server, pki.goog covers the Pages apex, and ssl.com is the third CA Cloudflare uses for these certificate types. No issuewild line is needed because the same CAs issue our wildcard certificates. Cloudflare’s own additions would still sit on top, including sectigo.com for backup certificates and the issuewild entries in its documented set, so dig, not the dashboard, remains the way to read the effective policy. The short TTL keeps a mistake cheap to revert. The zone had no CAA on the capture date; the CAA record generator builds the same lines for your own domain.

How a CA finds the record set that applies

RFC 8659 defines the search, and it is simpler than the word “inheritance” suggests:

  1. Query CAA at the exact name in the certificate request, following CNAMEs the way any resolver does.
  2. If the answer is empty, remove the leftmost label and query again.
  3. Stop at the first non-empty set, or give up before reaching the root.

That first non-empty set is the whole policy. A CAA set at api.example.com replaces the one at example.com for that branch; nothing is merged, so a subdomain’s records can tighten the parent’s policy or quietly loosen it.

Live capture, 26 September 2026. A subdomain with no CAA of its own, and a subdomain that is a CNAME:

$ dig +nocmd @1.1.1.1 www.google.com CAA +noall +comments +authority
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 24289
;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; AUTHORITY SECTION:
google.com.		60	IN	SOA	ns1.google.com. dns-admin.google.com. 988166603 900 900 1800 60

$ dig +nocmd @1.1.1.1 www.github.com CAA +noall +answer
www.github.com.		3600	IN	CNAME	github.com.
github.com.		3600	IN	CAA	0 issue "digicert.com"
github.com.		3600	IN	CAA	0 issue "globalsign.com"
github.com.		3600	IN	CAA	0 issue "letsencrypt.org"
github.com.		3600	IN	CAA	0 issue "sectigo.com"
github.com.		3600	IN	CAA	0 issuewild "digicert.com"
github.com.		3600	IN	CAA	0 issuewild "letsencrypt.org"
github.com.		3600	IN	CAA	0 issuewild "sectigo.com"

www.google.com has no CAA set (the SOA in the authority section belongs to google.com), so a CA climbs one label and applies google.com’s single issue "pki.goog" record, shown in the next capture. www.github.com is a CNAME: the resolver followed it and returned github.com’s records, and those govern www.github.com.

Aliases have one subtlety. RFC 8659 dropped the older RFC 6844 behavior of climbing above CNAME targets. The CA sees records that sit on the alias target itself, but if the target has none, the climb continues from the parent of the name in the request, not from the hosting provider’s parent domain. For a wildcard request such as *.preview.example.com, the search starts at preview.example.com.

Reading real policies: issue, issuewild, and parameters

Live capture, 26 September 2026. CAA sets for four well-known domains:

$ for d in google.com github.com cloudflare.com letsencrypt.org; do dig +nocmd @1.1.1.1 "$d" CAA +noall +answer; done
google.com.		86400	IN	CAA	0 issue "pki.goog"
github.com.		3600	IN	CAA	0 issue "digicert.com"
github.com.		3600	IN	CAA	0 issue "globalsign.com"
github.com.		3600	IN	CAA	0 issue "letsencrypt.org"
github.com.		3600	IN	CAA	0 issue "sectigo.com"
github.com.		3600	IN	CAA	0 issuewild "digicert.com"
github.com.		3600	IN	CAA	0 issuewild "letsencrypt.org"
github.com.		3600	IN	CAA	0 issuewild "sectigo.com"
cloudflare.com.		300	IN	CAA	0 iodef "mailto:tls-abuse@cloudflare.com"
cloudflare.com.		300	IN	CAA	0 issue "comodoca.com"
cloudflare.com.		300	IN	CAA	0 issue "digicert.com; cansignhttpexchanges=yes"
cloudflare.com.		300	IN	CAA	0 issue "letsencrypt.org"
cloudflare.com.		300	IN	CAA	0 issue "pki.goog; cansignhttpexchanges=yes"
cloudflare.com.		300	IN	CAA	0 issue "ssl.com"
cloudflare.com.		300	IN	CAA	0 issuewild "comodoca.com"
cloudflare.com.		300	IN	CAA	0 issuewild "digicert.com; cansignhttpexchanges=yes"
cloudflare.com.		300	IN	CAA	0 issuewild "letsencrypt.org"
cloudflare.com.		300	IN	CAA	0 issuewild "pki.goog; cansignhttpexchanges=yes"
cloudflare.com.		300	IN	CAA	0 issuewild "ssl.com"
letsencrypt.org.	300	IN	CAA	0 issue "amazon.com"
letsencrypt.org.	300	IN	CAA	0 issue "letsencrypt.org"
letsencrypt.org.	300	IN	CAA	0 issue "pki.goog"
letsencrypt.org.	300	IN	CAA	0 issue "sectigo.com"
letsencrypt.org.	300	IN	CAA	0 issue "ssl.com"
letsencrypt.org.	300	IN	CAA	0 issue "www.digicert.com"
letsencrypt.org.	300	IN	CAA	0 issuewild "amazon.com"
letsencrypt.org.	300	IN	CAA	0 issuewild "letsencrypt.org"
letsencrypt.org.	300	IN	CAA	0 issuewild "pki.goog"
letsencrypt.org.	300	IN	CAA	0 issuewild "sectigo.com"
letsencrypt.org.	300	IN	CAA	0 issuewild "ssl.com"
letsencrypt.org.	300	IN	CAA	0 issuewild "www.digicert.com"
Domain TTL issue values issuewild values Effect
google.com 86400 1 none One CA for ordinary and wildcard names alike
github.com 3600 4 3 globalsign.com is authorized for github.com but not for *.github.com
cloudflare.com 300 5 the same 5 Same issuers for both, plus an iodef reporting address
letsencrypt.org 300 6 the same 6 Let’s Encrypt’s own domain lists five identifiers besides its own

The GitHub row is the rule behind most wildcard surprises:

  • issue authorizes a CA for ordinary names, and for wildcards too when the set contains no issuewild record.
  • Once any issuewild record exists, every issue record is ignored for wildcard requests, and issuewild records are ignored for ordinary names.
  • Records are additive: two issue values authorize both CAs, and issue ";" next to a named CA leaves that CA authorized.
  • issue ";" on its own forbids ordinary issuance; a set with only issuewild restricts wildcards and leaves ordinary names unrestricted.

Google’s single record is the simplest correct policy: you do not need issuewild just because you use wildcard certificates. Add it only when wildcard authorization should differ, as GitHub’s does. Note the TTLs as well: they range from 300 seconds to a full day, and that is how long a resolver may keep serving an old policy after you change it.

Two more things are visible in the raw records. Text after a semicolon, such as cansignhttpexchanges=yes, is a parameter whose meaning RFC 8659 leaves entirely to the named CA. Let’s Encrypt documents two of its own: validationmethods (limit issuance to http-01, dns-01, or tls-alpn-01) and accounturi (limit it to one ACME account). Both reduce risk and both create something you must update when your automation changes.

Identifiers are also whatever each CA publishes, not its brand name. Google Trust Services is pki.goog, and cloudflare.com lists comodoca.com, a value that does not appear in Sectigo’s current list of recognized names (sectigo.com, trust-provider.com, usertrust.com). Copying a well-known domain’s CAA set copies its history, so check each CA’s current documentation instead. The CA/Browser Forum’s Baseline Requirements oblige CAs to state the identifiers they recognize in their CP/CPS documents, and to refuse issuance when they meet an unrecognized tag with the critical flag (128) set.

Diagnosing a CAA refusal

When a CA refuses because of CAA, or issuance stalls after a DNS change:

  1. List every DNS name in the request. The Baseline Requirements require CAA processing for each dNSName in the certificate, so one forgotten alternative name is enough to fail.
  2. For each name, find the first non-empty set by walking up from that name and following CNAMEs.
  3. For wildcard names, look for issuewild first, and fall back to issue only if the set has no issuewild.
  4. Compare the CA’s documented identifier exactly: letsencrypt.org, not https://letsencrypt.org and not an ACME directory URL.
  5. Account for caching. Resolvers may hold the previous answer for its TTL, and the Baseline Requirements let a CA issue up to the record’s TTL or 8 hours after its check, whichever is longer.
  6. Look for DNS failures rather than policy. RFC 8659 lists middleboxes that drop unknown query types, name servers that answer NOTIMP or REFUSED for CAA, and delegations to private name servers. Let’s Encrypt notes that SERVFAIL most often means a DNSSEC validation failure.

Step 6 depends on the zone. The Baseline Requirements allow a CA to treat a failed lookup as permission only when the failure is outside the CA’s infrastructure, the lookup was retried, and the domain is provably unsigned (“Insecure” in DNSSEC terms), as and.guide is. For a signed zone with broken CAA answers, expect a refusal.

Wildcards add a validation constraint unrelated to CAA: Let’s Encrypt’s HTTP-01 and TLS-ALPN-01 challenges cannot validate wildcard names, so wildcard certificates need DNS-01. The wildcard DNS and TLS guide covers that setup.

Finally, remember what CAA cannot do. It does not affect certificates that already exist; RFC 8659 describes a record set as a current grant of authority only. It does not tell you when a certificate was issued; Certificate Transparency monitoring does that. And it does not deploy anything, so after issuance, inspect the certificate the server actually presents before calling the change done.