Structured per-target error codes emitted by every distribution module, the error class each code maps to, and which classes the fan-out per-target retry re-attempts. Codes appear in the error_code field of the per-target results (last_target_results) on the distribution and in job logs.
Retry policy is based on a normalized error class, not on raw error strings. Each code maps to one of five classes; only the first two are re-attempted by fan-out per-target retry.
Class
Meaning
Retried by fan-out?
network
Timeout, connect failure, DNS resolution failure.
Yes
io_transient
Broken pipe, connection reset, transient SFTP/API failure — likely to succeed on retry.
Yes
io_permanent
Remote permission denied, no space left, quota/ownership conflicts — operator action needed.
The module credential referenced by the target could not be resolved.
No code for external secret stores
A credential read from an external secret store is resolved before any target is attempted, so a store failure has no target to attach a code to. It is reported at the job level instead — the job status and last error name the credential, the store and the underlying failure. There is deliberately no SECRETSTORE_* code, because no target result could ever carry one.
The host rejected the credentials during the SSH handshake (key or password). A handshake that fails for a transport reason — the peer closes the connection, connection reset — is SSH_CONNECT, not this.
SFTP_FAILED
io_transient
SFTP session could not be opened or an SFTP operation failed.
FILE_UPLOAD
io_transient
Writing a PathSet file on the target failed for a transient reason (I/O error, lost connection, full disk).
SSH_PERMISSION
io_permanent
The host refused the file operation: permission denied on the file or directory, a missing parent directory the SSH user could not create, a path that is not a directory, or a read-only filesystem (from the SFTP status code or its text). Not retried — give the SSH user write access, create the directory, or fix the PathSet path.
SCRIPT_EXIT_NONZERO
io_transient
The ActionSet script ran but exited with a non-zero status.
SCRIPT_FAILED
io_transient
The ActionSet script could not be executed.
SSH_VALIDATION
validation
Pre-flight validation of the SSH deployment inputs failed: the target has no SSH spec or its bound PathSet/ActionSet no longer resolves (deleted, another project, kind changed), a path could not be rendered, an unresolved or secret variable in a path, path traversal, an invalid owner or run_as, a pfx passphrase variable that is missing, not secret or empty. Also the fallback for any unclassified error.
SSH_HOST_KEY_ALGO
validation
Host-key negotiation failed before any key was presented: the server offers only key types CertAutoPilot does not advertise once verification is enabled — in practice a DSA-only server. Not retried, and there is nothing to approve. Add a modern host key to the server, or turn verification off for that target. A server that merely dropped the pinned key type does not land here: the other types stay on offer, so it is reported as SSH_HOST_KEY_CHANGED and can be reviewed.
SSH_HOST_KEY_CHANGED
validation
The server presented a host key that does not match the one pinned for this target. Nothing was deployed, and the run is not retried — review and approve the new key on the target. Only raised when host-key verification is enabled on that target.
The webhook endpoint could not be reached on the last attempt (DNS, refused, timeout, TLS, policy refusal, redirect limit, unparseable URL). Connection failures are retried within retry_count; an unparseable URL is not.
WEBHOOK_NON_2XX
io_transient
The endpoint answered with a non-2xx HTTP status on the last attempt. 5xx, 408, 425 and 429 are retried within retry_count; any other 4xx ends the attempts immediately.
WEBHOOK_TEMPLATE
validation
The payload template failed to render (or the default payload could not be built). No request was sent and nothing is retried — fix the module-config template and run a dry run.
The API server could not be reached (dial, DNS, TLS, timeout, 503/504), or a live-picker listing (namespaces / Secrets / workloads) failed for a reason other than RBAC.
K8S_FORBIDDEN
auth
The API server answered 401 (expired or revoked token) or 403 (RBAC) for the secret create/update or workload patch — or, in a live picker, the list verb the listing needs.
K8S_NOT_FOUND
validation
Namespace or referenced object does not exist.
K8S_APPLY_FAILED
io_transient
Applying the Secret or the restart patch failed for another reason (conflict, immutable/wrong-type Secret, admission or quota rejection).
K8S_VALIDATION
validation
The API client could not be built — kubeconfig credential missing, empty or unparseable, unknown context, in_cluster outside a pod, invalid proxy_url. Nothing was dialed; not retried.
NITRO API unreachable at any step (refused, DNS, TLS, timeout, dropped connection), or the pre-flight nsversion probe did not answer like a NITRO endpoint.
NETSCALER_AUTH
auth
HTTP 401/403 or NITRO 354 at any step — wrong password, or a command the user's command policy does not allow.
NETSCALER_UPLOAD
io_transient
NetScaler rejected a cert/key systemfile write (rights, cert_path, disk space).
NETSCALER_UPDATE
io_transient
NetScaler rejected the certkey create/update, a create conflict could not be resolved safely, or a certkey read failed for a non-transport, non-auth reason.
The link step failed (link read, unlink, link, or the read-back showing a different link). Strict mode only, except a broken link that could not be restored, which fails in every mode.
NETSCALER_OWNERSHIP
io_permanent
The object exists but is not managed by CertAutoPilot — refusing to overwrite.
Huawei Cloud API unreachable, HTTP 5xx, client initialisation failed, or a 2xx create response without an id.
HUAWEICLOUD_AUTH
auth
AK/SK authentication rejected (401/403).
HUAWEICLOUD_NOT_FOUND
validation
The referenced certificate object or CDN domain does not exist.
HUAWEICLOUD_CONFLICT
io_permanent
Name/state conflict — e.g. ambiguous name lookup matched multiple certificates, or the name listing was cut off (WAF page incomplete, ELB more than 500 matches) with no exact match, so nothing is created.
HUAWEICLOUD_QUOTA
io_transient
API quota / rate limit hit (429).
HUAWEICLOUD_VALIDATION
validation
HTTP 400 — the API rejected the request content (invalid PEM, name or field). Not retried.
The device did not answer (unreachable, TLS/verify error, timeout, transport error on any call), or the first call failed with a non-auth HTTP error. A rejected login is F5BIGIP_AUTH.
F5BIGIP_AUTH
auth
HTTP 401/403 anywhere in the run: a rejected token login (wrong password or login provider), a role denial on the upload endpoint, or a forbidden object/profile/transaction call.
F5BIGIP_UPLOAD
io_transient
Uploading a cert/key file failed (5xx or transport error). A 401/403 role denial is F5BIGIP_AUTH.
F5BIGIP_UPDATE
io_transient
TMOS below 13.0 or an unparseable version; creating or updating the cert/key objects failed; a transaction could not start, commit or timed out; the versioned name is too long.
F5BIGIP_NOT_FOUND
validation
An import-only in-place update targeted an object that does not exist (F5 answered 404) — typically an explicit key_name whose ssl-key object was never created while the cert object exists.
F5BIGIP_CHAIN
io_transient
Installing the intermediates failed (strict mode): chain unparseable, more than 5 intermediates, a lookup/upload/create failed, or an existing object conflicts with no identical object to reuse.
F5BIGIP_OWNERSHIP
io_permanent
A cap_f5ic_<fp24> intermediate object exists but holds a different certificate (strict mode).
F5BIGIP_PROFILE
io_permanent
Profile-aware only — the module never creates profiles: no listed client-ssl profile exists, a profile could not be read, its certKeyChain PATCH or commit failed or timed out, or it already serves a different certificate of the same key type.
AppRole login rejected, or Vault answered 403 on the secret read or write (invalid/expired token, or the policy lacks read / create / update on the path). Not retried.
VAULT_READ_FAILED
io_transient
Reading the existing secret failed for a reason other than 403 (network, 5xx, unparsable body). Nothing is written.
VAULT_WRITE_FAILED
io_transient
Writing the secret failed for a reason other than 403 (network, 5xx).
VAULT_VALIDATE_FAILED
validation
Post-write validation of the stored material failed.
NTLM / Basic / Kerberos authentication rejected. For Kerberos, check the realm, that the target is addressed by AD FQDN, and that this host's DNS resolves AD records (or set the KDC Address field).
WINRM_TIMEOUT
io_transient
Command execution timed out.
WINRM_EXEC
io_transient
ActionSet command/script failed on the host.
WINRM_PS_SYNTAX
validation
The PowerShell payload has a syntax error, or calls a cmdlet that is not recognized (detected in the failed ActionSet output).
WINRM_FILE
io_transient
The file transfer to the host failed, or what arrived did not match what was sent (the content check runs on the host and the destination is left untouched on a mismatch).
WINRM_VALIDATION
validation
Operator configuration error: unresolved PathSet/ActionSet, invalid file path or missing variable, missing/non-secret pfx passphrase variable, invalid or absent shell, a command rejected by allowed_commands, or an unusable allowed_commands pattern. Nothing is retried.
WinRM connection or in-flight transport to the Windows host failed.
IIS_AUTH
auth / validation
NTLM / Basic / Kerberos authentication rejected, an authorization denial before the bind ("Access is denied", 0x80070005, 401/403, "permission denied" — e.g. the account lacks store permissions), or a permanent Kerberos configuration error (qualified username, IP-addressed target, missing realm). A filesystem "Access to the path … is denied" is IIS_IMPORT_FAILED, not this code.
IIS_PFX_FAILED
validation / io_transient
Building the PKCS#12 bundle locally failed (validation — cert/key problem), or uploading it over WinRM failed (io_transient — retried).
IIS_IMPORT_FAILED
io_permanent
A script failure before the bind step (module import, password file, opening or importing the PFX, binding lookup — including a filesystem access-to-path denial on the staged file), a missing thumbprint in the result, or a script result that could not be parsed.
IIS_BINDING_NOT_FOUND
validation
No HTTPS binding matched the configured filter (or, in all_sites, no binding on any non-excluded site).
IIS_BINDING_AMBIGUOUS
validation
More than one binding matched in single mode — refusing to guess.
IIS_BINDING_FAILED
io_permanent
single mode: assigning the certificate to the binding threw (the exception message follows the code). Multi-binding modes: no bindings could be updated (including the all-matched-bindings-are-CCS case).
IIS_APPPOOL_DETECT_FAILED
validation
Detecting the application pools to recycle failed.
IIS_APPPOOL_RECYCLE_FAILED
io_permanent
Recycling one or more application pools failed (run is partial).
IIS_VALIDATE_STORE
validation
Post-deploy check of the certificate store failed.
IIS_VALIDATE_BINDING
validation / io_transient
Post-deploy binding check found a mismatch (validation), or the check itself failed at the transport level (io_transient — retried; the bind already succeeded).
IIS_MODULE_MISSING
validation
A required server-side component (e.g. the WebAdministration PowerShell module) is missing.
IIS_SITE_NOT_FOUND
validation
The configured IIS site does not exist — or the resolved placement names no site at all (a site-less per-certificate override inherited onto an all_sites target; reported by Execute and Dry Run).
Dual-class rows: the same code covers two failure natures; the module sets the
class explicitly at the failure site, so fan-out retries only the transient one.
The SMTP module classifies by SMTP reply code and sets the class directly on the result.
Code
Class
Meaning
SMTP_CONNECT
network
TCP dial / DNS failure before any SMTP conversation.
SMTP_TLS
auth
TLS handshake or certificate-trust failure (STARTTLS/SMTPS).
SMTP_AUTH
auth
Authentication rejected (reply 530/534/535/538).
SMTP_TRANSIENT
io_transient
Temporary server condition (421/450/451/452/454, other 4xx, or errors without a reply code that are not a recognised negotiation failure).
SMTP_PERMANENT
io_permanent
Permanent rejection (550/552/554, other 5xx) — operator action needed.
SMTP_VALIDATION
validation
The request was malformed (500/501/503/553), or the target and the relay cannot agree deterministically: STARTTLS required but not advertised, AUTH or the chosen PLAIN/LOGIN mechanism not offered, password refused over plaintext. Not retried — change tls_mode/auth_method.
SMTP_PAYLOAD
validation
Building the PEM / PKCS#12 attachment failed before sending.
Resolving the module credential for the target failed.
AWS_IMPORT
validation (bad input: missing region, ARN region ≠ target region, or an ARN naming a certificate ACM issued itself — refused before the import) or io_permanent (import rejected)
ImportCertificate failed or returned an unusable result, or the resolved ARN cannot be imported over.
The certificate is uploaded to a Cloudflare zone as a Custom Certificate (Business/Enterprise plan) over the Cloudflare API; renewals update it in place.
Code
Class
Meaning
CLOUDFLARE_CONNECT
network
Cloudflare API (api.cloudflare.com:443) unreachable, HTTP 5xx, or an HTTP 3xx redirect (never followed — a wrong or intercepting endpoint) — check egress/DNS/proxy and retry.
CLOUDFLARE_AUTH
auth
API token rejected — verify the token is active and carries Zone → SSL and Certificates → Edit on the target zone.
CLOUDFLARE_PLAN
validation
The zone's plan does not permit Custom Certificates — upgrade the zone to Business or Enterprise. Also raised when the zone's custom-certificate allocation is full (Cloudflare 1445 "Hit maximum cert allocation") — delete unused custom certificates or buy more slots.
CLOUDFLARE_NOT_FOUND
validation
The referenced zone or custom certificate does not exist — check the zone ID / certificate ID.
CLOUDFLARE_CONFLICT
validation
The zone holds a custom certificate covering the hostname that CertAutoPilot did not upload (one is enough — there is no adoption by hostname) — pin an explicit certificate_id to take it over, or remove it in Cloudflare.
CLOUDFLARE_VALIDATION
validation
The request itself is invalid — bad bundle_method/type/geo_restrictions, or a cert/key the API rejected (any 4xx on the upload, including error 1002 there).
CLOUDFLARE_QUOTA
io_transient
API rate limit or custom-certificate quota hit — retried; if persistent, free a custom-cert slot or slow the cadence.
The cPanel/WHM host is unreachable — check host, port, use_ssl and network egress; retried.
CPANEL_AUTH
auth
The API token was rejected — verify username + token and the mode (cpanel vs whm).
CPANEL_NOT_FOUND
validation
The server answered HTTP 404 — usually a wrong port/mode pairing (UAPI path on the WHM port or the reverse) or a proxy in front of the API.
CPANEL_CONFLICT
validation
The server answered HTTP 409 — read the error text in the target detail and correct the server state.
CPANEL_VALIDATION
validation
The target spec is invalid — check host, mode and the domain list.
CPANEL_INSTALL
io_permanent
The API reported failure (status ≠ 1 in cpanel mode; metadata.result ≠ 1 or data.status ≠ 1 in whm mode) — check the certificate/key/chain and that the domain is on the account.
CPANEL_QUOTA
io_transient
Rate limit exceeded — retried; if persistent, slow the cadence.
The certificate is imported into an Azure Key Vault as a new version of a KV certificate (AES-256-encrypted PKCS#12 under a random per-import password) over the Key Vault REST API, authenticated via an AAD service principal.
Code
Class
Meaning
AZUREKV_CONNECT
network
Vault or AAD endpoint unreachable (also raised on an unexpected redirect) — check egress and the vault_url.
AZUREKV_AUTH
auth
Token rejected, missing permission, blocked by the vault firewall, or the owning Azure subscription is disabled. The message names the case — fix tenant/client/secret, grant Key Vault Certificates Officer (or access-policy Get+Import+List), allowlist CertAutoPilot's egress IP in the Key Vault firewall, or re-enable the subscription (no role change helps a disabled one).
AZUREKV_NOT_FOUND
validation
Vault or certificate not found — verify the vault URL and certificate name. Also raised when the vault host does not resolve in DNS (a mistyped or deleted vault), which is permanent and therefore not retried.
AZUREKV_CONFLICT
validation
A soft-deleted certificate holds the name (az keyvault certificate recover, or purge — blocked by purge protection — or pick a different cert_name); also covers a pending certificate operation.
AZUREKV_VALIDATION
validation
Bad vault_url/cert_name, or Azure rejected the PFX.
AZUREKV_QUOTA
io_transient
Key Vault throttling (429). Transient class: retried per target in a fan-out batch and by the sweep re-arm; the module itself performs no in-run backoff.
Certificates on MerlinCDN are immutable — each deploy uploads a new cert, then binds it by re-pointing the distribution's canonical name. The codes below distinguish an upload failure (nothing bound yet) from a bind failure (a new cert was uploaded; the module best-effort deletes that orphan).
Code
Class
Meaning
MERLINCDN_CONNECT
network
MerlinCDN API unreachable.
MERLINCDN_AUTH
auth
Personal access token or org/workspace headers rejected.
MERLINCDN_NOT_FOUND
validation
The referenced distribution or canonical name does not exist.
MERLINCDN_CONFLICT
io_permanent
Name/state conflict on the CDN.
MERLINCDN_QUOTA
io_transient
API quota / rate limit hit (HTTP 429) — during the upload, or during binding when every failed row was rate-limited. Retried.
MERLINCDN_UPLOAD
io_permanent
Uploading the new certificate failed (nothing was bound), or the upload landed but its id could not be recovered (a warning names the possible stray upload).
MERLINCDN_BIND
io_permanent
Re-binding a canonical name was rejected — a 4xx other than 429, a missing CNAME/distribution, or an incomplete row. Not retried. If every row failed and the upload was fresh in this run, that upload is best-effort removed; if only some rows failed, the successful ones stay bound and a re-run heals the rest. A bind phase whose failures were all transient (5xx/network → MERLINCDN_CONNECT, 429 → MERLINCDN_QUOTA) is retried instead.
On-premises Microsoft Exchange, deployed over WinRM + the Exchange Management Shell. Every renewal imports a new thumbprint (import → enable → optional remove-old).
Code
Class
Meaning
EXCHANGE_CONNECT
network
WinRM unreachable — host/port/network.
EXCHANGE_AUTH
auth
WinRM authentication rejected, or a TLS-verify / Kerberos misconfiguration (terminal).
EXCHANGE_EMS_UNAVAILABLE
io_transient
The Exchange /PowerShell RBAC session did not open (reason included) — not a real Exchange server, remote PowerShell disabled for the account, the account has no Exchange RBAC role, the per-user concurrent-session limit is exhausted, or the endpoint is down while IIS restarts. Retried: the last two clear on their own; a permanent cause fails again. A role that opens the session but lacks the certificate cmdlets surfaces as EXCHANGE_IMPORT / EXCHANGE_ENABLE instead.
EXCHANGE_CREDENTIAL_MISSING
validation
No or malformed exchange_winrm credential (missing username/password).
EXCHANGE_UNSUPPORTED_KEY_TYPE
validation
The certificate does not use an RSA key, or its RSA key is smaller than 2048 bits. Exchange accepts RSA 2048 and above only; re-issue the certificate with such a key.
EXCHANGE_IMPORT
io_permanent
Import-ExchangeCertificate failed.
EXCHANGE_ENABLE
io_permanent
Enable-ExchangeCertificate failed.
EXCHANGE_REMOVE
io_permanent
Removing the previous certificate failed (it may still be in use).
EXCHANGE_NOT_FOUND
validation
A referenced Exchange server or certificate was not found.
The certificate, key and chain are imported into the firewall as one named certificate object over the PAN-OS XML API, followed by a partial commit scoped to the API user.
Code
Class
Meaning
PANOS_CONNECT
network
TCP/TLS connection to the firewall management interface failed. Retried.
PANOS_AUTH
auth
Authentication rejected (bad username/password or api_key, or an expired session key). Not retried. Note: username is required even with api_key.
PANOS_IMPORT
io_transient
The certificate import was rejected or failed on the device. Retried — a same-name re-import is idempotent. Note: PAN-OS refuses a self-signed non-CA certificate, and the PKCS#12 passphrase is device-limited to 31 characters (the module already complies).
PANOS_COMMIT
io_transient
The partial commit failed or its job timed out. Retried; a timed-out commit may still complete in the background, and the next run finishes with a commit-only pass instead of re-importing.
PANOS_NOT_FOUND
validation
A referenced object does not exist on the firewall. Not retried.
PANOS_VALIDATION
validation
The device rejected the request as invalid (bad name, vsys set while multi-vsys is off, malformed input). Not retried.