Cloudflare module
Uploads the certificate to a Cloudflare zone as a Custom Certificate so Cloudflare serves it at the edge, and updates it in place on every renewal. It is the cloud sibling of the on-prem F5/NetScaler modules.
Overview
- Type:
cloudflare, credential type:cloudflare_api_token(JSON{"api_token":"..."}), slotdefault. - Per target: resolve the zone → resolve the custom-certificate identity →
POST(create) orPATCH(update in place)/zones/{zone_id}/custom_certificates→ persist the returned id for the next renewal. - Rollback: supported — a rollback re-uploads a previous retained certificate version to the zone (Rollback). Extra eligibility rule: the version must have at least 14 days of remaining validity (
min_validity_floor— the same floor Cloudflare imposes on any upload). - Scope: only per-zone Custom Certificates. SSL for SaaS / Custom Hostnames (
/zones/{id}/custom_hostnames, for serving TLS on your customers' domains) is a separate API and out of scope.
Requirements
- Business or Enterprise plan. Free/Pro zones cannot upload custom certificates — the module surfaces this as
CLOUDFLARE_PLAN. - API token with
Zone → SSL and Certificates → Editon the target zone (Read suffices for dry-run/health-check). - The certificate must not expire in under 14 days at upload time. This floor also gates rollback eligibility: a previous version with under 14 days of validity left is reported ineligible with reason
min_validity_floor. - The private key must be unencrypted; for ECDSA keys any
EC PARAMETERSblock is stripped automatically before upload.
Target configuration
| Field | Required | Description |
|---|---|---|
zone_id | one of | The Cloudflare zone id (32-hex). Wins over zone_name. |
zone_name | one of | The zone domain (e.g. example.com); resolved to an id via GET /zones?name= when zone_id is empty. |
certificate_id | no | Pin an existing custom certificate to update in place. Empty → remembered id, else hosts match, else create new. |
bundle_method | no | ubiquitous (default, broadest trust) · optimal (shortest chain) · force (upload the chain as-is). |
type | no | sni_custom (default, recommended) · legacy_custom (non-SNI legacy clients; incompatible with BYOIP). |
geo_restrictions | no | us · eu · highest_security — restricts where the private key is stored (Geo Key Manager v1). The finer-grained v2 policy expression is not currently exposed. |
{
"zone_name": "example.com",
"type": "sni_custom",
"bundle_method": "ubiquitous"
}
Identity resolution
Cloudflare custom certificates carry no tag or metadata field, so a lost id is recovered by matching the certificate's covered hostnames. The module resolves which certificate to update in order:
- Explicit
certificate_id(verified to exist). - The id remembered from a previous run (remote state), verified to still exist — an out-of-band delete in the dashboard falls through.
- Hosts match — list the zone's custom certificates and adopt the one whose
hostscover the primary domain. - None found → create a new custom certificate.
If more than one certificate covers the primary domain, the run fails with CLOUDFLARE_CONFLICT and asks for an explicit certificate_id rather than guessing.
Update re-keys the id (sni_custom). A PATCH on a modern (sni_custom) certificate makes Cloudflare delete the old id and return a new one, so the id from the response — not the one sent — is what gets persisted. Consequence for a pinned certificate_id: it is a first-run adoption hint. After CertAutoPilot adopts and updates it, the pinned id is re-keyed and no longer exists; from then on CertAutoPilot tracks the (new) id in its own remote state. You can safely leave the pin set — on the next renewal the original pin 404s and the module falls back to the remembered successor id (and, failing that, hosts match) rather than erroring. A pin that never resolved and has no remembered state (a typo/wrong id) still fails with CLOUDFLARE_NOT_FOUND. legacy_custom ids are stable across updates.
Multiple certificates on one zone
A single Cloudflare target (one zone) can be referenced by many CertAutoPilot certificates — each is uploaded as its own custom certificate and tracked independently per certificate (remote_states.<certID>), so certificates never clobber each other's id or history. This is the same "one target, many certificates" model as IIS/F5.
But Cloudflare caps custom certificates per zone by plan:
| Plan | Custom certificates per zone |
|---|---|
| Free / Pro | none (custom certs not available) |
| Business | 1 modern (sni_custom) + 1 legacy (legacy_custom) |
| Enterprise | 1 modern + 1 legacy included; more can be purchased (no fixed published ceiling) |
So on Business a zone effectively holds one sni_custom certificate. In practice this fits the common case: a Cloudflare zone is one domain, and a single wildcard certificate (example.com + *.example.com) covers the whole zone — deploy one certificate per zone. If you genuinely need several distinct custom certificates on one zone, use Enterprise with purchased slots. Exceeding the quota surfaces as CLOUDFLARE_PLAN/CLOUDFLARE_QUOTA.
If two custom certificates in a zone overlap on a hostname, Cloudflare's own certificate priority decides which serves (legacy outranks modern; then priority/recency). CertAutoPilot does not manage priority — set it in the Cloudflare dashboard for overlapping certs. Non-overlapping certificates need no priority management.
Credential
Create a Cloudflare API Token credential (Settings → Distribution → Credentials):
{ "api_token": "your-cloudflare-api-token" }
Create the token in the Cloudflare dashboard under My Profile → API Tokens → Create Custom Token with permission Zone → SSL and Certificates → Edit and the target zone in Zone Resources.
Verification
Cloudflare edge propagation is asynchronous. For an explicit "new cert is live" signal, add a tls_fingerprint validation endpoint pointed at the domain with retries.
Error codes
| Code | Meaning | Resolution |
|---|---|---|
CLOUDFLARE_AUTH | Token rejected or lacks permission | Grant Zone → SSL and Certificates → Edit and include the zone. |
CLOUDFLARE_PLAN | Zone plan disallows custom certificates | Upgrade the zone to Business or Enterprise. |
CLOUDFLARE_NOT_FOUND | Zone or pinned certificate id not found | Verify zone_id/zone_name and any pinned certificate_id. |
CLOUDFLARE_CONFLICT | >1 custom cert covers the hostname | Pin an explicit certificate_id to disambiguate. |
CLOUDFLARE_VALIDATION | Cloudflare rejected the cert/key | Check the PEM chain, key match, and ≥14-day validity. |
CLOUDFLARE_QUOTA | Rate limit exceeded | Retry after a brief wait. |
CLOUDFLARE_CONNECT | Could not reach the Cloudflare API | Check outbound egress to api.cloudflare.com. |