Plate № 22 · Cloudflare · DNS / TLS
A pattern from the gf.cx specimen book
Scoped per-subdomain CAA
"…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:
- Negative-cache timing. The first (pre-CAA) issuance attempt cached "this host has no CAA" for the zone's SOA-minimum TTL (
gf.cx= 1800s / 30 min). Re-trigger the CA inside that window and it re-reads the stale negative, walks up to the apex again, and fails identically. Add the scoped CAA, then wait out the SOA-minimum before re-triggering. - Distribution rotation (Amplify-specific). Each Amplify
delete + create domain-associationmints a new CloudFront distribution with a new target. The routingCNAMEfrom a prior attempt then points to another distribution → CloudFront's alias check errors "points to another CloudFront distribution." Re-read the association's target after every recreate, keep the routing CNAME at a short TTL (60s), and park it on a non-CloudFront target while re-provisioning so no stale distribution is cached.
Reusable elements
The named, copyable pieces:
- Scoped-CAA cutoff — a per-subdomain CAA record short-circuits the walk-up; the surgical way to permit one CA for one host without touching a shared apex.
- Apex-CAA awareness — a Cloudflare apex carries a virtual, un-editable managed CAA set;
digshows it, the API doesn't. Diagnose with the served set, not the zone editor. - SOA-minimum wait — after changing CAA, wait the zone's negative-cache TTL before re-triggering issuance, or you'll get a false repeat-failure.
- Short-TTL routing during provisioning — keep an externally-fronted CNAME at TTL 60 and park it off-target while the CA/distribution churns, so caches never pin a stale target.
Reference
- Source
- Dan, 2026-07-07 · first applied wiring
costco.gf.cxto an AWS Amplify app (ACM cert). - Tool
gfcx_scoped_caa.pyinxlab-co/toolkit— verbsadd/check/cas. Stdlib only; Cloudflare token env-first. Idempotent add, authoritative-NS readback.- Applies when
- Any
*.gf.cxsurface 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.