Skip to content

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:

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

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 --nginx or --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:

sudo /opt/certintel-certbot/bin/python -m pip install "./$WHEEL_FILE" certbot-nginx

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/certbot on a system without systemd) runs /usr/bin/certbot, which now loads the plugin. Check that the timer runs: systemctl list-timers certbot.timer must 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 with sudo 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 the nginx/apache plugins 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-certbot file can contain:
17 3,15 * * * root /opt/certintel-certbot/bin/certbot renew --quiet

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:

sudo certbot renew --dry-run

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