Skip to main content

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":"..."}), slot default.
  • Per target: resolve the zone → resolve the custom-certificate identity → POST (create) or PATCH (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 more than 14 days of remaining validity (min_validity_floor — a conservative floor: Cloudflare may refuse a shorter-lived certificate when it builds the chain itself).
  • 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 → Edit on the target zone (Read suffices for dry-run/health-check). Reading the zone itself (GET /zones/{zone_id}, for the plan) is answered with that permission too. Add Zone → Zone → Read when a target uses zone_name instead of zone_id, or to use the Zone picker — those are the only calls that list zones (GET /zones). User-owned and account-owned tokens both work.
  • Cloudflare may refuse a certificate with under 14 days of validity left when it builds the chain itself (bundle_method ubiquitous / optimal); with force a shorter-lived — even expired — certificate is accepted (verified live). CertAutoPilot does not pre-check this on a deploy. Rollback keeps a conservative 14-day floor for every Cloudflare target: a previous version with under 14 days left is reported ineligible with reason min_validity_floor.
  • The private key must be unencrypted; for ECDSA keys any EC PARAMETERS block is stripped automatically before upload.

Target configuration​

FieldRequiredDescription
zone_idone ofThe Cloudflare zone id — exactly 32 lowercase hex characters, as shown on the dashboard Overview (it is not lowercased for you). Wins over zone_name.
zone_nameone ofThe zone domain (e.g. example.com); resolved to an id via GET /zones?name= when zone_id is empty.
certificate_idnoPin an existing custom certificate to take it over — update it in place. The id is the UUID Cloudflare shows (4e1a9c2b-7d3f-…); the older 32-hex spelling is accepted too. Empty → the remembered id, else this certificate's own upload recognised by content, else a new upload (refused, or uploaded alongside, when another certificate serves one of the names — see the identity ladder). Cannot be combined with allow_overlap (400 on the target, 422 on an override). See take over or upload alongside.
allow_overlapnotrue lets the first upload go ahead when a certificate CertAutoPilot did not upload already serves one of the names: the new certificate is uploaded alongside it, and every later run updates that same upload. Default false refuses with CLOUDFLARE_CONFLICT. Cannot be combined with certificate_id (400 on the target, 422 on an override).
bundle_methodnoubiquitous (default — an empty value is sent explicitly as ubiquitous, broadest trust) · optimal (shortest chain) · force (upload the chain as-is). ubiquitous/optimal rebuild the chain from Cloudflare's trust store, so a certificate from a CA Cloudflare does not trust (a private CA, Let's Encrypt staging) fails with CLOUDFLARE_VALIDATION ("cannot be bundled using Cloudflare's trust store"); use force for those.
typenosni_custom (default — an empty value is sent explicitly as sni_custom when the certificate is created; Cloudflare's own default for an omitted type would be legacy_custom) · legacy_custom (non-SNI legacy clients; incompatible with BYOIP). Applies on create only: Cloudflare's edit endpoint does not take a type, so an update never changes an existing certificate's type.
geo_restrictionsnous · 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"
}

Choosing from Cloudflare (live pickers)​

Both opaque fields on a Cloudflare target can be read from the account instead of copied out of the dashboard:

  • Zone ID — every zone the API token can reach, listed by domain name. Each row shows the zone's plan, and says outright when that plan cannot hold a custom certificate at all: a custom certificate needs Business or Enterprise, and without this you would only find out when a deployment failed. This picker needs no saved target — the token is the whole scope, so it works on a form you have not saved yet.
  • Certificate ID — the custom certificates already on the chosen zone, named by the hosts they cover (a custom certificate has no label of its own), with their issuer, status and expiry. That is what decides whether adopting one is the right move or whether a new upload is.

Listing is read-only and no certificate material is sent. Every field still accepts a typed value, so an unreachable API never blocks the form.

Identity resolution​

Cloudflare custom certificates carry no tag or metadata field, so ownership cannot be proven from the API. A lost id is recovered only when the zone holds this certificate's own upload — matched by its names, issuer, expiry and upload time (step 3; the upload must be later than both the version's start of validity and the moment CertAutoPilot obtained it — so an identical copy you uploaded by hand before importing the certificate stays yours) — and never by a hostname match alone (see the refusal below). The module resolves which certificate to update in order:

  1. Explicit certificate_id (verified to exist) — a deliberate takeover. A pin that no longer exists is followed to the remembered id it was re-keyed into, or — with no remembered state — to this certificate's own upload if one is in the zone (the takeover's first run happened but its record was lost); otherwise it fails with CLOUDFLARE_NOT_FOUND.
  2. The id remembered from a previous run (remote state), verified to still exist — an out-of-band delete in the dashboard falls through.
  3. This certificate already in the zone — Cloudflare returns no fingerprint or serial, so ownership is inferred from facts that must all agree: exactly the names of the certificate being deployed, the same issuing CA, the expiry (to the second) of the deployed version or of one of its 100 most recent retained versions, and an upload no older than that version itself (a copy uploaded before the certificate existed cannot be CertAutoPilot's). For the deployed version the signature algorithm must match too. Such a certificate is updated in place. This covers a run that uploaded successfully but whose record of the id was lost: the next run — usually a renewal, with a new certificate — finds its own upload instead of refusing it or uploading a second copy. Names and expiry alone are not enough: CAs that expire every certificate at 23:59:59 give another certificate for the same names, issued the same day, the same expiry. The current version wins over an earlier one; further copies are named in the job log (and in the dry run) as safe to delete.
  4. allow_overlap → create a new custom certificate alongside whatever serves the names (recorded as created_alongside; as created_new when nothing does — see take over or upload alongside). The remote state also keeps the pin the record came from (pin, pin_known), which is how a later missing pin is told apart from a typo.
  5. Refuse — if the zone already holds any custom certificate covering the primary domain that CertAutoPilot did not upload, the run fails with CLOUDFLARE_CONFLICT naming it. One is enough; there is no adoption by hostname match.
  6. Nothing found → create a new custom certificate — unless any other name on the certificate (a SAN) is already served by a custom certificate CertAutoPilot did not upload: that also fails with CLOUDFLARE_CONFLICT, naming each shared name and the certificate serving it (see overlapping names).

If listing the zone's certificates fails, a first upload is not attempted — it could not be checked for names another certificate serves — while an update of a known certificate goes ahead with a warning in the job log.

"Already in the zone" includes certificates that are still activating: Cloudflare's default listing returns active certificates only, so the module also reads the pending, initializing and — explicitly — active ones and merges them (a certificate that has just activated is, for a couple of seconds, in the active filter but not yet in the default listing) — and every read carries a unique query parameter, since Cloudflare answers an identical listing URL from a short cache for a few seconds after a change. A certificate uploaded seconds earlier is not invisible to these checks.

Step 5 is deliberate: losing remote state (a restore, a recreated target) does not self-heal into taking over a zone's production certificate. Pin certificate_id to hand one back.

CertAutoPilot never modifies a certificate it did not upload

A Cloudflare custom certificate carries no tag, so ownership cannot be proven from the API — and "its hosts cover my domain" is not proof. A zone's production wildcard (*.example.com) covers every subdomain, so adopting on that basis would let a deployment for a single test hostname overwrite the certificate serving the whole domain — and on sni_custom an update deletes the original id.

So a deployment that finds an untracked certificate covering its hostname fails, naming that certificate, with CLOUDFLARE_CONFLICT. Three ways forward: set certificate_id to take it over deliberately, set allow_overlap to upload alongside it, or remove it in Cloudflare first. Only three things count as CertAutoPilot's own: a certificate it uploaded (tracked in remote state), one an operator explicitly pinned, and one that carries this certificate (same names, and the expiry of the deployed version or of a retained earlier one).

A consequence worth planning for: if CertAutoPilot's record of its own certificate is lost and the zone holds a version of it that is no longer retained (history pruned, or the certificate re-created), the next run refuses rather than silently re-adopting. Pin the id once to hand it back.

A target moved to another zone leaves the certificate's copy in the previous zone behind: the run and the dry run warn with its id so you can delete it there.

Update re-keys the id. A PATCH with a new certificate — on sni_custom and legacy_custom alike — 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 Cloudflare answers the original pin with HTTP 400 / error 1002 "Invalid certificate" (not a 404), which the module treats as not found and falls back to the remembered successor id rather than erroring. That fallback happens only when the remembered certificate came from that pin: a pin that never resolved — a typo, another zone's id, a certificate deleted since — fails with CLOUDFLARE_NOT_FOUND, even when the target remembers some other upload of its own (it used to be silently followed to that one, skipping the takeover). A pin that is the remembered certificate and was deleted in Cloudflare also fails with CLOUDFLARE_NOT_FOUND rather than uploading a fresh certificate; pick the certificate to take over again, or clear the pin. A target whose record was written before 1.5.53 does not know which pin its certificate came from, so a vanished pin is still followed to the remembered id there. legacy_custom certificates re-key the same way (verified live).

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. Free and Pro zones cannot hold custom certificates at all. On Business and Enterprise the number of custom-certificate slots (modern and legacy are counted separately) is plan- and contract-dependent — Cloudflare does not publish reliable per-plan counts, so check the zone's allocation in the dashboard under SSL/TLS → Edge Certificates. 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, ask your Cloudflare account team about additional slots. Exceeding the allocation surfaces as CLOUDFLARE_PLAN (Cloudflare answers 403 "Hit maximum cert allocation"); CLOUDFLARE_QUOTA is reserved for rate limits, including the per-minute limit on certificate updates, which is retried automatically.

Take over or upload alongside​

When a certificate CertAutoPilot did not upload already serves the names — typically a *.example.com uploaded by hand before CertAutoPilot — the target (or a distribution override) chooses one of three behaviours:

ChoiceSettingWhat happens
Stop and report it (default)neitherA first upload fails with CLOUDFLARE_CONFLICT, naming the certificate; nothing in the zone changes. A certificate CertAutoPilot already uploaded is still updated, with a CLOUDFLARE_HOST_OVERLAP warning.
Take over (recommended)certificate_idThe chosen certificate is updated in place. One certificate per name, no extra allocation. Every later run updates it too. A pin always wins: if CertAutoPilot already had an upload of its own on the target (from Upload alongside), that upload is no longer updated — the run and the dry run name it so you can delete it.
Upload alongsideallow_overlap: trueA new certificate is uploaded next to the existing one, which stays untouched. Only the first run uploads: every renewal and re-run updates that same upload — tracked in remote state, and if that record is ever lost, recognised by its names and the expiry of any retained version of the certificate — so copies never pile up.

Do not delete the existing certificate first and let CertAutoPilot upload a new one — between the delete and the upload no custom certificate serves those names, and unless Cloudflare's own edge certificate covers them, clients get a TLS error. Both options above avoid that gap. Measured on an Enterprise zone: during a takeover update about 900 TLS handshakes against the edge saw no error and no third certificate; edge servers served old and new side by side for roughly a minute, then only the new one.

Take over — things to know:

  • The original certificate is replaced and cannot be restored from CertAutoPilot; a rollback re-deploys CertAutoPilot's own previous versions.
  • Names the original served that the new certificate does not carry are no longer served by it after the update (Cloudflare falls back to another certificate covering them, or its own edge certificate) — the run and the dry run warn CLOUDFLARE_HOSTS_DROPPED, naming them. The usual case is the apex: take over *.example.com + example.com with a certificate issued for both (*.example.com, example.com), not the wildcard alone.

Upload alongside — things to know:

  • Which one the edge serves follows Cloudflare's order: the more specific name first, then a legacy_custom certificate over a modern one, then the most recently uploaded. So a name the other certificate carries exactly (www.example.com) stays on it when CertAutoPilot's certificate covers that name only with *.example.com — the overlap check reports that direction too. Uploaded after the existing one, CertAutoPilot's modern certificate is served unless the other is legacy_custom — then CertAutoPilot's is never served for those names. The dry run states the outcome per name.
  • The priority setting does not reorder modern (sni_custom) certificates — Cloudflare applies it to legacy_custom ties only (verified: reprioritising two modern certificates left the newer one served).
  • The other certificate keeps its allocation slot, so the upload needs a free one; on a zone with a single modern slot (common on Business) it fails with CLOUDFLARE_PLAN.
  • If the other certificate is uploaded again later, it becomes the newest and takes its names back until CertAutoPilot's next renewal — the two keep trading. Every run warns CLOUDFLARE_HOST_OVERLAP.
  • If CertAutoPilot's certificate is deleted, Cloudflare serves the remaining one with the latest expiry — which may be expired by then. The edge can keep serving a deleted certificate for over a minute.

Per distribution. On a target that serves several certificates, choose per distribution in Distributions → Overrides: Inherit, Take over (with a certificate picker), Upload alongside or Stop and report it. The most specific choice wins, whole — a target row over the Default row over the target itself — so a distribution can upload alongside on a target that pins a certificate, and Stop and report it on one target row cancels a takeover the others inherit. Upload alongside and Stop and report it decide how the first upload is placed: once the certificate has an upload of its own on the target, every run updates that one. Take over always wins over that upload (see the table above). Take over is offered on target rows only — a custom certificate belongs to one zone, so an id on the Default row is refused (422). An override cannot set both a certificate and allow_overlap (422).

Overlapping names​

Cloudflare decides which certificate serves a hostname by its own certificate priority: a legacy_custom certificate outranks a modern one, a specific name outranks a wildcard, and among certificates of the same type covering the same name the most recently uploaded wins. Uploading a certificate that carries a name another certificate already serves therefore takes that name over from it — immediately, and without any error from Cloudflare (verified: it accepts the upload). Against a legacy_custom certificate the opposite happens: the upload succeeds and is never served for that name.

So CertAutoPilot checks every name on the certificate, not only the primary, against the custom certificates it did not upload:

  • The check also looks the other way: a name the other certificate carries exactly that this certificate covers only with a wildcard is reported (CLOUDFLARE_HOST_OVERLAP, the job log says that name stays on the other certificate) but never refused — that name is not one of this certificate's, and a wildcard next to specific overrides is ordinary coexistence.
  • First upload — refused with CLOUDFLARE_CONFLICT, naming each shared name and the certificate (and its type) serving it. Remove those names from the certificate, remove the other certificate in Cloudflare, pin certificate_id to take it over, or set allow_overlap to upload alongside it.
  • First upload with allow_overlap — goes ahead, carries the warning CLOUDFLARE_HOST_OVERLAP, and the job log says which certificate serves each shared name afterwards.
  • Update of a certificate CertAutoPilot already uploaded — goes ahead and carries the warning CLOUDFLARE_HOST_OVERLAP. The takeover happened when the certificate was first uploaded (or someone uploaded the overlapping certificate afterwards); refusing the renewal would only let the certificate expire on the edge.
  • Dry run names the overlap, says which outcome the run would have, and — for an upload or update that goes ahead — which certificate the edge serves for each shared name.

Copies of the certificate being deployed are never counted as someone else's certificate.

Credential​

Create a Cloudflare API Token credential (Settings → Distribution → Credentials):

{ "api_token": "your-cloudflare-api-token" }

Create the token in the Cloudflare dashboard — either a user-owned token (My Profile → API Tokens) or an account-owned token (Manage Account → Account API Tokens); both work, because the module only calls zone endpoints. Use Create Custom Token with permission Zone → SSL and Certificates → Edit (plus Zone → Zone → Read for zone_name targets and the Zone picker) and the target zone in Zone Resources. Surrounding whitespace in a pasted token is trimmed; a blank token is refused at save time.

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​

CodeMeaningResolution
CLOUDFLARE_AUTHToken rejected or lacks permissionGrant Zone → SSL and Certificates → Edit and include the zone.
CLOUDFLARE_PLANZone plan disallows custom certificates, or the zone's custom-certificate allocation is full (Cloudflare 1445 "Hit maximum cert allocation")Upgrade the zone to Business or Enterprise; for a full allocation delete unused custom certificates in Cloudflare (or buy more slots on Enterprise).
CLOUDFLARE_NOT_FOUNDZone or pinned certificate id not found (for an id, Cloudflare answers HTTP 400 / error 1002 on the by-id read or update; for a zone id it answers HTTP 403 / error 9109 "Invalid zone identifier", also when the token cannot see the zone; the module treats both as not found)Verify zone_id/zone_name and any pinned certificate_id.
CLOUDFLARE_CONFLICTThe zone holds a custom certificate CertAutoPilot did not upload that covers the primary hostname — or, on a first upload, any other name on the certificate (overlapping names)Take it over (certificate_id), upload alongside it (allow_overlap), or remove it in Cloudflare first — see take over or upload alongside.
CLOUDFLARE_VALIDATIONCloudflare rejected the cert/key (any other 4xx on the upload, including a 1002 there) — or the zone holds more than 500 custom certificates in one state, so a first upload or an id recovery cannot check it whole and stops (updating a pinned or remembered certificate goes ahead with a warning)Check the PEM chain, key match, and ≥14-day validity; use bundle_method: force for a chain Cloudflare cannot rebuild. For the 500-certificate case, remove unused custom certificates from the zone.
CLOUDFLARE_QUOTARate limit exceededRetry after a brief wait.
CLOUDFLARE_CONNECTCould not reach the Cloudflare API (transport error, timeout, HTTP 5xx, or an HTTP 3xx redirect — never followed; it means a wrong or intercepting endpoint)Check outbound egress and any proxy between CertAutoPilot and api.cloudflare.com.