Skip to content

CAA pinning

A CAA record on a hostname restricts which ACME account — and which validation method — may issue a TLS cert for it, even from a different account on a different server. Pin whoever runs ACME for the hostname.

Tunneling is TLS passthrough: your origin runs ACME and holds the cert. So you pin your own ACME client’s account. accounturi= is your client’s account URI, and validationmethods= is whatever your client actually uses (http-01, dns-01, or tls-alpn-01).

Where to find your account URI:

  • cert-manager: the account URL is stored on the Issuer / ClusterIssuer status (status.acme.uri) once the issuer is registered. You can also re-derive it by re-registering with the existing account private key — Let’s Encrypt’s newAccount endpoint returns the existing account’s URL via the Location header when the key already has an account.
  • certbot: /etc/letsencrypt/accounts/<server>/directory/<id>/regr.json, field uri.
  • acme.sh: ~/.acme.sh/ca/<server>/account.json, field location.
  • Caddy: in the data directory, look for acme/<server>/users/<email>/<acc>.json, field location.

Publish the record at your hostname:

<your-hostname> CAA 0 issue "letsencrypt.org;validationmethods=<method>;accounturi=<your-account-uri>"
  • validationmethods=... — only that challenge type may be used. The others are refused even from the same Let’s Encrypt account.
  • accounturi=... — only that specific Let’s Encrypt account may issue. Pin the account that actually runs ACME for the hostname; pinning any other account refuses every renewal.

Certificate Transparency is the backstop: every cert issued for your hostname appears in public CT logs, even if CAA enforcement failed. Watch your hostname with:

Some DNS providers (Cloudflare in particular) silently rewrite or drop CAA records when their own cert features are on. Check with an external resolver:

Terminal window
dig @1.1.1.1 +short <your-hostname> CAA

If your letsencrypt.org;... line isn’t there, look for a “Universal SSL” / “auto CAA” / “managed certs” toggle in the provider dashboard and turn it off.

The hub runs ACME for its own control-plane hostname (e.g. hub.example.eu). Pin that hostname so a rogue ACME client can’t get a cert for it.

The hub’s account is public at /v1/acme/account:

Terminal window
curl https://<hub>/v1/acme/account
{
"account_uri": "https://acme-v02.api.letsencrypt.org/acme/acct/123456789",
"caa_record": "letsencrypt.org;validationmethods=tls-alpn-01;accounturi=https://acme-v02.api.letsencrypt.org/acme/acct/123456789"
}

Paste caa_record verbatim as the value of a CAA record at the hub hostname. The CLI prints the same line ready to paste:

Terminal window
towonel acme account