CertIntel DNS-01 plugin for Certbot¶
certbot-dns-certintel publishes and removes DNS-01 TXT challenge values through
CertIntel. Certbot handles certificate orders, keys, files and renewal. This
plugin does not install certificates or enroll a CertIntel agent.
Requires Linux/Unix, Python 3.10+, and Certbot 2.9 through 5.x. Tested with Certbot 5.8.0, and with the certbot packages of Debian 13 (4.0.0) and Ubuntu 24.04 (2.9.0). Use Linux or WSL for Certbot; native Windows is not supported, and the plugin says so at startup (this plugin runs on Linux, BSD, macOS or WSL only) instead of failing during a challenge.
Setup is four steps: create a delegation in CertIntel, install the plugin, write
one small JSON file, and run certbot certonly. If this server already uses
Certbot from Debian or Ubuntu, the plugin goes into that Certbot and its existing
renewal timer renews the new certificate together with the others. The commands
below are for Debian/Ubuntu with sudo; adjust the package manager for other
distributions.
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… → certbot, the credentials file and the certonly
command for your CertIntel deployment, ready to copy: choose the web server
that serves the certificate (NGINX, Apache or Nothing to reload) and
the command carries the matching --deploy-hook, with a First run, without a
terminal variant (see Issue the certificate). The
file holds four values from that page:
| In CertIntel | In the credentials file |
|---|---|
| Delegation ID | delegationId |
| Update secret (shown once) | apiKey |
CNAME target, e.g. abc123.dns01.certin.tel |
cnameTarget |
API base, e.g. https://api.certin.tel/api/v1 |
apiBase |
The delegation's own page shows the same configuration later, with a placeholder for the secret.
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). An apex/wildcard pair (example.com and *.example.com)
shares one delegation. Any other name such as www.example.com needs its own
delegation and CNAME.
2. Install the plugin¶
Install Python and curl, then download the latest wheel. The release record at
https://api.certin.tel/api/v1/releases/plugin-certbot/latest.json always points at
the current version and carries its SHA-256, so nothing below hardcodes a version:
sudo apt update && sudo apt install -y python3 python3-venv curl
RELEASE_JSON=$(curl -fsSL https://api.certin.tel/api/v1/releases/plugin-certbot/latest.json)
WHEEL_URL=$(printf '%s' "$RELEASE_JSON" | python3 -c 'import json,sys; print(json.load(sys.stdin)["url"])')
WHEEL_SHA256=$(printf '%s' "$RELEASE_JSON" | python3 -c 'import json,sys; print(json.load(sys.stdin)["sha256"])')
WHEEL_FILE=$(basename "$WHEEL_URL")
curl -fsSLO "$WHEEL_URL"
echo "$WHEEL_SHA256 $WHEEL_FILE" | sha256sum -c -
sha256sum must print OK. If it does not, delete the file and download again.
See Downloads for the record format and how to check a
hash by hand.
Then install it in one of two places:
- 2a. Into the Certbot from your distribution (
apt install certbot), when it is version 2.9 to 5.x: Debian 13, Ubuntu 24.04 and later. Nothing else changes on the server: the distribution's renewal timer keeps renewing every certificate, including ones obtained with--nginxor--apache. - 2b. Into its own environment for everything else: Debian 12 and Ubuntu 22.04 (their Certbot is older than 2.9), the Certbot snap, or a server without Certbot. That environment's Certbot then has to be the one that renews; see Renewal.
2a. Into the distribution's Certbot¶
Run this in the directory holding the downloaded wheel:
if python3 -c 'import sys; from importlib.metadata import version; v = tuple(int(p) for p in version("certbot").split(".")[:2]); sys.exit(not (2, 9) <= v < (6, 0))'; then
sudo python3 -m zipfile -e "./$WHEEL_FILE" /usr/local/lib/certintel-certbot.new &&
sudo rm -rf /usr/local/lib/certintel-certbot &&
sudo mv /usr/local/lib/certintel-certbot.new /usr/local/lib/certintel-certbot &&
echo /usr/local/lib/certintel-certbot | sudo tee /usr/lib/python3/dist-packages/certintel-certbot.pth >/dev/null &&
sudo certbot plugins 2>/dev/null | grep '^\* dns-certintel'
else
echo "certbot $(certbot --version 2>&1 | cut -d' ' -f2) is not 2.9 to 5.x: install the plugin in its own environment instead."
fi
It must print * dns-certintel. The plugin's files go to
/usr/local/lib/certintel-certbot, and one line in
/usr/lib/python3/dist-packages/certintel-certbot.pth puts that directory on the
system Python's path, where the distribution's Certbot finds the plugin. No
package installed by apt is changed, and the path does not depend on the
Python version, so a distribution upgrade keeps it. The version check matters:
an older Certbot such as Debian 12's 2.1 stops working for every certificate
when it finds the plugin. To upgrade later, download the new wheel and run the
same block. To remove the plugin, delete both paths (renewals of certificates
that use it then fail).
Use sudo certbot in the steps below.
2b. In its own environment¶
sudo python3 -m venv /opt/certintel-certbot
sudo /opt/certintel-certbot/bin/python -m pip install "./$WHEEL_FILE"
sudo /opt/certintel-certbot/bin/certbot plugins
Confirm that dns-certintel appears in the plugin list. Pip also installs a
compatible Certbot into the same environment. If this server has certificates
obtained with --nginx or --apache, install those plugins into the same
environment too, so that it can renew them:
For certbot-apache, first install what it needs to build
(sudo apt install -y gcc python3-dev libaugeas-dev), then add certbot-apache
to the same command. To upgrade later, repeat the download block and run the
pip install command with --upgrade. Existing renewal configuration is
unchanged.
Use sudo /opt/certintel-certbot/bin/certbot in place of sudo certbot in the
steps below, and set up Renewal for it.
3. Configure the first delegation¶
Copy the credentials-file block from the delegation's page in CertIntel. It looks like this, with your values:
[ -d /etc/certintel ] || sudo install -d -m 755 /etc/certintel
sudo install -m 600 /dev/null /etc/certintel/delegations.json
sudo tee /etc/certintel/delegations.json >/dev/null <<'EOF'
{
"apiBase": "https://api.certin.tel/api/v1",
"delegations": {
"example.com": {
"delegationId": "11111111-1111-1111-1111-111111111111",
"cnameTarget": "YOUR-SUBDOMAIN.dns01.certin.tel",
"apiKey": "YOUR-DELEGATION-SECRET"
}
}
}
EOF
The first line creates /etc/certintel only when it is missing, and leaves an
existing folder as it is. The CertIntel agent uses the same folder: it runs as
its own account and must be able to read it, so the folder stays 0755 and
owned by root (do not run chmod 700 on it; an agent that cannot read it
reports its configuration lock as on and its post-renewal scripts as
unusable). What protects the secret is the file itself: the second line
creates it empty, readable by root only (0600), before the secret is
written into it. Running the block again replaces the file. Earlier versions of
this page closed the folder with install -d -m 700 /etc/certintel; where the
agent also runs, open it again with sudo chmod 755 /etc/certintel.
The key under delegations is the certificate name, without *. or
_acme-challenge.. The plugin refuses to start unless the file is a regular file
owned by the user running Certbot (normally root) with no group or other
permission bits. The error names the rule, for example CertIntel: credentials
file permissions must deny all group and other access.
apiBase is where the plugin sends the TXT updates: take it from the
delegation's page, which shows the right address for your workspace. It must be
HTTPS and include /api/v1. Without it the plugin uses
https://api.certin.tel/api/v1, the hosted service, and says so: Certbot prints
the API it uses, once per run, for example CertIntel: using the API at
https://api.certin.tel/api/v1 (the default; set apiBase in the credentials file
for a private deployment). On a private CertIntel deployment, always set it:
otherwise the Delegation ID and secret go to the hosted service.
apiKey is the delegation's update secret, or a CertIntel DNS API key that has
been granted access to that delegation. To cover more certificate names, add
sibling entries under delegations, each with its own Delegation ID, CNAME target
and key; when the file already exists, add the new entry instead of replacing the
file. An entry may set its own apiBase; an entry without one, or with
"apiBase": null, uses the file's. See
the example configuration.
The renewal account needs write access to /etc/certintel for the
.certintel-locks directory. Keep the file path stable; Certbot saves it for renewal.
4. Issue the certificate¶
sudo certbot certonly --authenticator dns-certintel \
--dns-certintel-credentials /etc/certintel/delegations.json \
--cert-name example.com -d example.com \
--deploy-hook 'systemctl reload nginx'
Replace the domain, and the deploy hook with what your server needs (see
Deploying the certificate); with 2b, use
/opt/certintel-certbot/bin/certbot. On a server where Certbot has never been
used, it first asks for an email address and to accept the CA's terms. Without
a terminal for those questions (a script, for example), use the delegation
page's First run, without a terminal command: it adds
--non-interactive --agree-tos -m <your email>, with the signed-in user's
address as the contact for expiry and account notices. For an
apex/wildcard pair add -d '*.example.com' (quoted, to prevent shell expansion).
The propagation wait defaults to 60 seconds (--dns-certintel-propagation-seconds);
raise it if validation fails on DNS caching. To rehearse without using production
rate limits, add --test-cert (Let's Encrypt staging, also spelled --staging; the
certificate is saved but not publicly trusted) or --dry-run (a test run against
staging that saves nothing) first.
--dns-certintel-lock-timeout <seconds> (1 to 3600, default 60) sets how long a
renewal waits for another Certbot process on this host that is using the same
delegation. Certbot saves it with the other plugin options for renewal.
The certificate chain is written to /etc/letsencrypt/live/example.com/fullchain.pem
and the private key to /etc/letsencrypt/live/example.com/privkey.pem.
Deploying the certificate¶
certonly does not configure a web server: point its TLS configuration at
fullchain.pem and privkey.pem above, once. Certbot then replaces those files
at each renewal, and the web server must reload to use them. The
--deploy-hook given at issuance is saved with the certificate and runs after
every successful issuance and renewal; use systemctl reload apache2 for
Apache. To reload after the renewal of any certificate instead, put a script in
Certbot's deploy-hook directory:
printf '#!/bin/sh\nsystemctl reload nginx\n' | sudo tee /etc/letsencrypt/renewal-hooks/deploy/reload-nginx >/dev/null
sudo chmod 755 /etc/letsencrypt/renewal-hooks/deploy/reload-nginx
Renewal¶
Exactly one schedule should renew this server's certificates, with a Certbot that can load every plugin they were obtained with. Certbot saves the authenticator, credentials path and propagation delay with each certificate.
- 2a: nothing to set up. The distribution's
certbot.timer(or/etc/cron.d/certboton a system without systemd) runs/usr/bin/certbot, which now loads the plugin. Check that the timer runs:systemctl list-timers certbot.timermust list it. If it lists nothing, the timer is disabled or enabled but not started (some containers and minimal images do not start it, although Certbot says it "has set up a scheduled task"); start it withsudo systemctl enable --now certbot.timer. - 2b, with the distribution's Certbot also installed (Debian 12, Ubuntu
22.04): its timer would run
/usr/bin/certbot, which cannot load the plugin, so every renewal of this certificate would fail. Make that timer run the environment's Certbot instead, which renews all certificates once it has thenginx/apacheplugins they use (2b above):
sudo install -d /etc/systemd/system/certbot.service.d
printf '[Service]\nExecStart=\nExecStart=/opt/certintel-certbot/bin/certbot -q renew --no-random-sleep-on-renew\n' | sudo tee /etc/systemd/system/certbot.service.d/certintel.conf >/dev/null
sudo systemctl daemon-reload
- 2b, with the Certbot snap: the snap cannot load this plugin. Stop its
renewal (
sudo snap stop --disable certbot.renew) and schedule the environment's Certbot as below; it then renews all certificates once it has the plugins they use. - 2b, with no other Certbot: a root-owned
/etc/cron.d/certintel-certbotfile can contain:
The plugin checks this at issuance and renewal: when the server's renewal timer
runs a Certbot that cannot load it, Certbot shows a warning such as CertIntel:
certbot.timer renews this host's certificates with /usr/bin/certbot, which cannot
load the dns-certintel plugin, so renewing this certificate there will fail. It
also warns when nothing renews: a certbot.timer that is enabled but not active
(CertIntel: certbot.timer is enabled but not active (inactive), so it does not
renew this host's certificates … Start it: sudo systemctl enable --now
certbot.timer), disabled or masked, or no timer and no cron entry for Certbot.
Then test every certificate the schedule renews, against the CA's staging service:
(with 2b, sudo /opt/certintel-certbot/bin/certbot renew --dry-run). From a
terminal it starts at once. Without a terminal (a script, an automation tool, or
ssh host 'sudo certbot renew --dry-run'), Certbot first waits a random delay of
up to 8 minutes, so that scheduled renewals do not all reach the CA at the same
moment; its log says Non-interactive renewal: random delay of … seconds. Add
--no-random-sleep-on-renew to skip that wait when you test by hand. After
rotating a delegation secret, update the credentials file and run
renew --dry-run again. A CA may reuse valid authorizations, so a renewal does
not always publish a new TXT challenge.
Moving from 2b to 2a (earlier versions of this guide used 2b only): install
the plugin as in 2a, then remove the schedule you added for the environment
(sudo rm /etc/cron.d/certintel-certbot, or
sudo rm /etc/systemd/system/certbot.service.d/certintel.conf followed by
sudo systemctl daemon-reload), and run sudo certbot renew --dry-run. The
certificates' saved settings stay as they are. A distribution Certbot older than
the environment's notes that the renewal file was written by a newer version and
renews it as usual.
Troubleshooting¶
| Symptom | Check |
|---|---|
dns-certintel is missing |
Run plugins with the Certbot that issues and renews: sudo certbot plugins (2a) or sudo /opt/certintel-certbot/bin/certbot plugins (2b). For 2b, install the wheel with that environment's Python. |
| A renewal fails with The requested dns-certintel plugin does not appear to be installed | The schedule runs a Certbot without the plugin. See Renewal. |
| A renewal fails with The requested nginx plugin does not appear to be installed | The schedule runs the environment's Certbot (2b) without certbot-nginx; install it there. |
Every Certbot command fails with VersionConflict naming requests |
The plugin was put into a Certbot older than 2.9. Delete /usr/lib/python3/dist-packages/certintel-certbot.pth and use 2b. |
sha256sum reports FAILED |
The download is corrupt or incomplete; delete the wheel and download again. Do not install it. |
| No exact delegation mapping | Add the exact certificate name without a wildcard prefix; parent mappings do not cover subdomains. |
CertIntel: credentials file … |
Fix ownership (chown root:root), permissions (chmod 600), or replace a symlink with a regular file. |
| API 401/403 | Check the update secret or granted DNS API key, delegation status and apiBase. |
| API 429/502/503/504 | The plugin already retried (three attempts, five for a cleanup, honouring Retry-After). Wait and run again; check the service status if it persists. |
apiBase must be an HTTPS URL / delegationId must be a UUID |
Fix the named field from the delegation's page in CertIntel; apiBase may simply be omitted for the hosted service. |
this plugin runs on Linux ... only |
Run Certbot under Linux, BSD, macOS or WSL. |
| API 404 or CNAME mismatch | Check the Delegation ID, apiBase and full CNAME target against the delegation's page. |
| DNS validation fails | Check the permanent CNAME and public TXT propagation; increase the propagation wait. |
| Invalid certificate identifier | Remove underscores from the certificate hostname; keep the required underscore in _acme-challenge. |
| Delegation already has two TXT values | Check for another issuer or stale values; do not clear another active challenge. CertIntel stops counting a value two hours after it was set. |
CertIntel cleanup failed for … |
The certificate was issued, but the named delegation may keep the challenge value. Clear it as the warning says once no issuance is running. |
Use one issuing client/host per delegation. The plugin preserves other TXT values and deletes only its own, but CertIntel's API replaces the entire TXT set and permits at most two values. Local file locks cannot serialize another host, the dedicated CertIntel agent, or dashboard edits. Do not run them concurrently against the same delegation.
After a crash or cleanup failure, first confirm that no issuance is active,
then remove stale values in CertIntel before retrying. API errors omit response
bodies and secrets; the underlying exception class is written to Certbot's debug
log in /var/log/letsencrypt. Transient API failures (HTTP 429, 502, 503, 504,
timeouts and connection 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 (the service's rate-limit window
is one minute); a 429 without one waits 20 seconds; other transient failures back
off from one second with jitter. Other errors fail at once.
Only challenges whose value the plugin wrote, or tried to write, are removed afterwards. A challenge that failed before anything was written (the delegation could not be read, already held two values, its lock timed out, or the name has no delegation) is not cleaned up and gets no warning.
A removal that still fails is reported as a warning naming the delegation's ID
and CNAME target and how to clear the value it may have left (Clear TXT now
on the delegation in CertIntel, or DELETE on its TXT endpoint). Certbot still
finishes successfully (exit status 0) when the certificate was issued, so check
for that warning after issuance.
See also the official Certbot user guide.
Optional CertIntel reporting¶
The DNS plugin does not report check-ins or issued certificates. The separate
PowerShell 7 reporting wrappers
can add those reports. Pass the Certbot that has the plugin with -CertbotPath
for both issuance and renewal (/usr/bin/certbot with 2a, the environment's
binary with 2b), and use -Authenticator dns-certintel -CredentialsPath when creating
the certificate. DNS credentials and the write-scoped reporting key are separate.
If you adopt the renewal wrapper, schedule it in place of the bare Certbot command
above so the same lineages are not run by two schedulers.