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:
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:
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.