Skip to content

CertIntel for acme.sh

dns_certintel.sh publishes and removes DNS-01 TXT challenge values through CertIntel. acme.sh handles certificate orders, keys, certificate files and renewal. This plugin does not install certificates or enroll a CertIntel agent.

Requires acme.sh 3.x (tested with 3.1.6) on Linux, BSD or macOS with a POSIX sh, curl and openssl. acme.sh itself can also use wget, but the plugin needs curl: it hands curl the API key on standard input, and wget accepts a header only on its command line, where other local users could read it. The plugin is a single file in acme.sh's native DNS API script format and uses acme.sh DNS alias mode: CertIntel delegated DNS-01 works through a permanent CNAME from _acme-challenge.<domain> to the delegation's target, and --domain-alias tells acme.sh to publish and check the TXT record at that target instead of in your own zone.

Setup is four steps: create a delegation in CertIntel, download the script into the acme.sh home, export five variables, and run acme.sh --issue.

1. Create the delegation and CNAME

In CertIntel, open Delegated DNS-01 and create a delegation for the certificate name. The page that follows shows the CNAME to add and, under Use this delegation with… → acme.sh, the variables and the issue command for your CertIntel deployment, ready to copy (the delegation's own page shows them later, with a placeholder for the secret). They hold four values from that page:

In CertIntel Used as
Delegation ID CERTINTEL_DELEGATION_ID
Update secret (shown once) CERTINTEL_API_KEY
CNAME target, e.g. abc123.dns01.certin.tel CERTINTEL_CNAME_TARGET and --domain-alias
API base, e.g. https://api.certin.tel/api/v1 CERTINTEL_API_BASE

At your DNS provider, add the permanent CNAME that CertIntel shows you:

_acme-challenge.example.com.  CNAME  YOUR-LABEL.dns01.certin.tel.

Wait until the CNAME resolves publicly before issuing. With Cloudflare, use DNS only (no proxy). example.com and *.example.com share one delegation. Other names such as www.example.com need their own delegation and CNAME.

2. Install the plugin

Download the latest script into the acme.sh home directory, where acme.sh looks for custom DNS API scripts. The home is the directory that holds the acme.sh script itself: ~/.acme.sh for a default install, or the directory given with --home. It is not the --config-home. The block below assumes the default home; for the official Docker image see Docker. The release record at https://api.certin.tel/api/v1/releases/plugin-acme-sh/latest.json points at the current version and carries its SHA-256, so nothing below hardcodes a version:

RELEASE_JSON=$(curl -fsSL https://api.certin.tel/api/v1/releases/plugin-acme-sh/latest.json)
SCRIPT_URL=$(printf '%s' "$RELEASE_JSON" | grep -o '"url":"[^"]*"' | head -1 | cut -d'"' -f4)
SCRIPT_SHA256=$(printf '%s' "$RELEASE_JSON" | grep -o '"sha256":"[^"]*"' | head -1 | cut -d'"' -f4)

curl -fsSL "$SCRIPT_URL" -o "$HOME/.acme.sh/dns_certintel.sh"
echo "$SCRIPT_SHA256  $HOME/.acme.sh/dns_certintel.sh" | sha256sum -c -
chmod 600 "$HOME/.acme.sh/dns_certintel.sh"

sha256sum must print OK. If it does not, delete the file and download again. On macOS use shasum -a 256 -c - in place of sha256sum -c -.

acme.sh 3.x finds dns_certintel.sh in the home itself or in its dnsapi/ directory, but not in the config home. Keeping the script in the home itself, outside dnsapi/, keeps it apart from the scripts acme.sh --upgrade installs. The script is sourced by acme.sh, so it does not need to be executable. To upgrade later, run the same block again. See Downloads for the record format.

Docker

The official neilpang/acme.sh image keeps the acme.sh home at /acmebin (inside the image) and the config home at /acme.sh (the volume). A script placed in the volume is not found. Mount the verified script into the home instead, for example -v /srv/acme/dns_certintel.sh:/acmebin/dns_certintel.sh:ro next to the usual -v /srv/acme/data:/acme.sh.

3. Configure the first delegation

Settings are environment variables on the first issuance. Once a challenge has been published with them, the plugin saves them for that certificate and reuses them for its renewals, so no environment is needed later: the domains, delegation IDs, CNAME targets, API base and lock directory in the certificate's own acme.sh configuration (<domain>.conf), and each delegation's key in the acme.sh account configuration under that delegation's ID. acme.sh creates both files with mode 600; keep its configuration directory private. Each certificate keeps its own settings: issuing another certificate with other variables does not change them. Values must not contain whitespace or line breaks.

export CERTINTEL_DOMAIN='example.com'
export CERTINTEL_DELEGATION_ID='11111111-1111-1111-1111-111111111111'
export CERTINTEL_CNAME_TARGET='YOUR-LABEL.dns01.certin.tel'
export CERTINTEL_API_KEY='YOUR-DELEGATION-UPDATE-SECRET'
export CERTINTEL_API_BASE='https://api.certin.tel/api/v1'
Variable Meaning
CERTINTEL_DOMAIN Certificate domain without *. or _acme-challenge.
CERTINTEL_DELEGATION_ID Delegation ID
CERTINTEL_CNAME_TARGET Full CNAME target shown by CertIntel
CERTINTEL_API_KEY Delegation update secret, or a DNS API key granted access to the delegation
CERTINTEL_API_BASE The API base shown on the delegation's page in CertIntel, which is the right address for your workspace (HTTPS, including /api/v1). Optional for the hosted service: the default is https://api.certin.tel/api/v1. On a private deployment always set it. Each call prints the API it uses, and says when that is the default
CERTINTEL_LOCK_DIR Optional absolute lock directory; default .certintel-locks in the acme.sh config home (the home unless --config-home is used)
CERTINTEL_LOCK_TIMEOUT Optional. Seconds to wait for another acme.sh run using the same delegation (1 to 3600); default 60

4. Issue the certificate

acme.sh --issue --server letsencrypt --dns dns_certintel \
  --domain-alias YOUR-LABEL.dns01.certin.tel \
  -d example.com

acme.sh 3.x uses ZeroSSL unless told otherwise; --server letsencrypt selects Let's Encrypt (any other ACME CA works the same way with its own --server).

--domain-alias must be the CNAME target exactly, without _acme-challenge. (--challenge-alias would add that prefix and does not match a CertIntel delegation). One alias given once applies to every -d on the command line. For an apex/wildcard pair add -d '*.example.com' (quoted, to prevent shell expansion). acme.sh verifies the TXT values publicly before asking the CA; --dnssleep 60 replaces that check with a fixed wait. To rehearse first, use --server letsencrypt_test (Let's Encrypt staging); a staging certificate is not publicly trusted.

acme.sh stores the certificate under its home directory and remembers the DNS API and alias for renewal. Use --install-cert or a --reloadcmd to deploy it; certificate deployment is a separate task. Unset the variables afterwards; the certificate's saved settings carry them. A shell that still exports them overrides the saved values for that run, and they are saved for the certificate being issued or renewed once its challenge is published.

Multiple delegations

Give every list the same number of entries in the same order. The API key and endpoint may be a single shared value or one per domain:

export CERTINTEL_DOMAIN='example.com www.example.com'
export CERTINTEL_DELEGATION_ID='11111111-1111-1111-1111-111111111111 22222222-2222-2222-2222-222222222222'
export CERTINTEL_CNAME_TARGET='FIRST-LABEL.dns01.certin.tel SECOND-LABEL.dns01.certin.tel'
export CERTINTEL_API_KEY='FIRST-SECRET SECOND-SECRET'
acme.sh --issue --server letsencrypt --dns dns_certintel \
  -d example.com --domain-alias FIRST-LABEL.dns01.certin.tel \
  -d '*.example.com' --domain-alias FIRST-LABEL.dns01.certin.tel \
  -d www.example.com --domain-alias SECOND-LABEL.dns01.certin.tel

acme.sh pairs each --domain-alias with the -d at the same position. Mappings are exact: the plugin matches the record name acme.sh passes against each configured CNAME target (or _acme-challenge.<domain>), and a parent domain never covers a child.

Renewal

acme.sh renews from its cron job using the saved settings. To exercise the saved configuration without touching the production certificate:

acme.sh --renew -d example.com --force --staging

A CA may reuse valid authorizations, so a renewal does not always publish a new TXT challenge. To rotate a key, export the new CERTINTEL_API_KEY (and every other list, if you use per-domain keys) and run one issuance or forced renewal; the plugin saves the new values on that run. Keys are saved per delegation, so the new key also serves the other certificates that use the same delegation.

Earlier plugin versions saved one set of settings for the whole acme.sh account, which the last issuance replaced. A certificate without saved settings of its own still uses that account-wide set, and saves it for itself on its next successful renewal. If such a renewal fails with no exact delegation mapping, export that certificate's variables and run one forced renewal.

Locks and limits

The plugin holds one lock per delegation while it reads and rewrites the TXT set, in .certintel-locks under the acme.sh config home or CERTINTEL_LOCK_DIR. Another run waits up to CERTINTEL_LOCK_TIMEOUT seconds (default 60) for it. A lock left behind by a crashed run is reclaimed once that process has exited. Use one issuing client/host per delegation: the lock cannot serialize another host, the dedicated CertIntel agent, or dashboard edits, and CertIntel replaces the whole TXT set with at most two values.

Each attempt gives up after 10 seconds without a connection or 30 seconds in total. Transient API failures (HTTP 429, 502, 503, 504, timeouts and other curl transport errors) are retried: up to three attempts when publishing a value and five when removing one, because a failed removal leaves the value on the delegation. A Retry-After header is honoured up to 60 seconds; a 429 without one waits 20 seconds; other transient failures back off from one second with jitter. Other errors fail at once. acme.sh reports a failed removal as Error removing txt and carries on; the plugin's message before it names the delegation and how to clear the value. The certificate is still issued, and acme.sh exits 0.

The plugin accepts only DNS-01 digests. It refuses API redirects, sends the key only as the X-Api-Key header to the configured HTTPS endpoint, and reports HTTP status without echoing response bodies or keys. TXT values that are not plain printable ASCII (never the case for ACME digests) are rejected rather than rewritten. IDNs must be written in punycode.

The plugin calls the API with curl itself rather than through acme.sh's HTTP functions. The key reaches curl on its standard input, so it never appears in the process list, in acme.sh's --debug output at any level, or in the curl trace files acme.sh writes to /tmp at --debug 2 and above. TLS is always verified for these calls: acme.sh's --insecure does not apply to them (the plugin says so when it is set), and ~/.curlrc is ignored. For an API behind a private CA, give acme.sh --ca-bundle /path/to/ca.pem; the plugin passes it to curl.

Troubleshooting

Run with --debug (or --debug 2) for the plugin's diagnostics; they never contain the key. The key does remain in the environment of the shell that exported CERTINTEL_API_KEY for the first issuance (unset it afterwards, and do not pass it with docker run -e where docker inspect shows it), and acme.sh saves it base64-encoded, not encrypted, in its account configuration. If the key was exposed, rotate the delegation secret and run one issuance with the new CERTINTEL_API_KEY so the plugin saves it.

Symptom Check
sha256sum reports FAILED The download is corrupt or incomplete; delete the script and download again. Do not use it.
No exact delegation mapping --domain-alias must equal CERTINTEL_CNAME_TARGET; add the exact certificate name to CERTINTEL_DOMAIN.
supply matching CERTINTEL_DOMAIN... Every list needs one entry per domain in the same order.
API 401/403 Check the update secret, delegation status and endpoint.
API 404 or CNAME mismatch Check the Delegation ID, endpoint and full CNAME target against the delegation's page.
answered with a redirect The endpoint is wrong; the plugin never follows redirects with the key.
Delegation already has two TXT values Another issuer or stale values; do not clear another active challenge. CertIntel stops counting a value two hours after it was set.
API ... failed (HTTP 429/502/503/504) The plugin already retried (three attempts, five for a removal). Wait and run again; check the service status if it persists.
DNS check never succeeds Check the permanent CNAME and public propagation; try --dnssleep.
timed out acquiring delegation lock Another acme.sh run holds the delegation; wait, or remove the named lock directory if no run is active.

After a crash or cleanup failure, confirm that no issuance is active, then remove stale values in CertIntel before retrying.

See also the acme.sh DNS alias mode and DNS API guides.