Plate № 22 · Cloudflare · DNS / TLS

A pattern from the gf.cx specimen book

Scoped per-subdomain CAA

Source · Dan, 2026-07-07

"…and the fix is a scoped per-subdomain CAA permitting the CA. Can we do that?"

Surfaced wiring costco.gf.cx to an AWS Amplify app: ACM refused to issue the TLS cert with a CAA error, even though the apex looked fine. The one-off fix became a reusable primitive — gfcx_scoped_caa.py.

The problem

A gf.cx apex served by Cloudflare carries an auto-managed CAA record set — the five CAs Cloudflare's Universal SSL uses (comodoca, digicert, letsencrypt, pki.goog, ssl.com). It is injected virtually: you see it via dig gf.cx CAA, but it is not an editable zone record (the Cloudflare API lists zero apex CAA).

CAA resolution walks up the name. A CA validating foo.gf.cx checks foo.gf.cx first; if it has no CAA of its own, the CA walks up to gf.cx — and hits that Cloudflare set. Any subdomain fronted by a non-Cloudflare CA — AWS ACM (Amplify / CloudFront / ELB / API Gateway), or any external CA — is therefore blocked: that CA isn't on the apex list, so issuance fails with a CAA error that looks like it's about the apex you can't even edit.

The mechanism that makes the fix work

CAA lookup stops at the first name that has any CAA records (RFC 8659). It does not merge a subdomain's CAA with the apex's — the closest name with records wins outright. So if the subdomain has its own CAA, the CA reads that set and never walks up.

The fix

Give the subdomain its own CAA record permitting the CA. One line:

gfcx_scoped_caa.py add costco.gf.cx            # permit AWS ACM (default)
gfcx_scoped_caa.py add foo.gf.cx --ca google  # permit pki.goog
gfcx_scoped_caa.py check costco.gf.cx         # what CAA a host actually serves

That writes costco.gf.cx CAA 0 issue "amazon.com". Because resolution stops there, the apex's Cloudflare-managed set — and every other surface's edge cert — is untouched. Zero blast radius. (Cloudflare Pages surfaces never hit this: they're proxied and use Cloudflare's own cert, so they want the apex set. check shows that walk-up plainly, which makes it double as the "why is my CA blocked?" diagnostic.)

Two gotchas that compound it

Both bit the costco setup after the CAA was correct — worth knowing because they mimic the original failure:

Reusable elements

The named, copyable pieces:

Reference

Source
Dan, 2026-07-07 · first applied wiring costco.gf.cx to an AWS Amplify app (ACM cert).
Tool
gfcx_scoped_caa.py in xlab-co/toolkit — verbs add / check / cas. Stdlib only; Cloudflare token env-first. Idempotent add, authoritative-NS readback.
Applies when
Any *.gf.cx surface fronted by a non-Cloudflare CA (AWS ACM → Amplify / CloudFront / ELB / API Gateway, or an external CA). Cloudflare Pages surfaces are unaffected.
Mechanism
RFC 8659 — CAA resolution uses the closest ancestor name that publishes CAA records; it does not merge with the apex.