Skip to main content

certbot-dns-nexdns

PyPI Python versions Docker image License

Official Certbot DNS authenticator for NexDNS, a managed authoritative DNS platform with a REST API.

The plugin answers the ACME dns-01 challenge for you. Before validation it creates the _acme-challenge TXT record in your NexDNS zone, and after validation it removes it again, so issuing and renewing certificates needs no manual DNS edits.

Why DNS-01

  • Wildcards. Let's Encrypt issues *.example.com only through dns-01. No wildcard certificate can be obtained with http-01.
  • No inbound HTTP. Validation happens entirely over DNS, so nothing has to listen on port 80 or 443 and the host does not have to be reachable from the internet. You can issue for a machine behind NAT, for an internal service, or before the site is live.
  • One certificate for the apex and everything under it, in a single run.

Requirements

  • Python 3.9 or newer. The test matrix covers 3.9, 3.10, 3.11, 3.12 and 3.13
  • Certbot 2.0 or newer
  • A NexDNS account on a plan that includes API access, with the zone for the certificate's domain already hosted on it
  • An API token carrying the three scopes listed below

requests 2.25 or newer is installed as a dependency.

Installation

pip install certbot-dns-nexdns

If Certbot itself was installed with pipx, add the plugin to that same environment instead, or Certbot will not find it:

pipx inject certbot certbot-dns-nexdns

Confirm that Certbot picked it up:

certbot plugins

dns-nexdns should appear in the list, described as "Obtain certificates using a DNS TXT record (if you are using NexDNS for DNS hosting)".

There is also a prebuilt image, nexdns/certbot, which is Certbot with this plugin already installed, for linux/amd64 and linux/arm64. See Docker.

API token

Create a token under Settings → API keys in the panel (nexdns.tech/settings/api-keys) and grant it exactly three scopes:

Scope What the plugin does with it
zones.read Finds the zone that owns the challenge name, so _acme-challenge.shop.example.com is written into the example.com zone
records.write Creates the challenge TXT record, and deletes it after validation
records.read Reads the zone's TXT records back during cleanup, to find the exact record it created

Nothing beyond these three is used. In particular the plugin never asks for zones.write, so a token scoped this way cannot create or delete zones.

Scopes are fixed when the key is created. To change them, create a new key and revoke the old one.

Credentials file

Certbot reads the token from an INI file:

mkdir -p ~/.nexdns && chmod 700 ~/.nexdns
cat > ~/.nexdns/certbot-credentials.ini <<'EOF'
dns_nexdns_token = nxd_0000000000000000000000000000000000000000
EOF
chmod 600 ~/.nexdns/certbot-credentials.ini

dns_nexdns_token is the only required key. When the file is world-readable Certbot logs Unsafe permissions on credentials configuration file and carries on regardless, so the warning is easy to miss in an unattended run. Set the mode yourself.

Issuing a certificate

Wildcard plus apex in one run

certbot certonly \
    --authenticator dns-nexdns \
    --dns-nexdns-credentials ~/.nexdns/certbot-credentials.ini \
    --email admin@example.com \
    --agree-tos \
    --non-interactive \
    -d example.com \
    -d '*.example.com'

Quote *.example.com so the shell does not expand it. A wildcard does not cover the apex, which is why both names are listed, and it covers only one label: *.example.com matches www.example.com but not a.b.example.com.

Both names are validated at _acme-challenge.example.com, so this run creates two TXT records there and removes both afterwards.

Several domains on one certificate

certbot certonly \
    --authenticator dns-nexdns \
    --dns-nexdns-credentials ~/.nexdns/certbot-credentials.ini \
    --email admin@example.com \
    --agree-tos --non-interactive \
    -d example.com -d www.example.com \
    -d example.net -d '*.example.net'

Every zone named in one run must be hosted on the account the token belongs to, since the plugin uses that single token for all of them.

Propagation wait

--dns-nexdns-propagation-seconds is how long Certbot waits after the record is written before asking the ACME server to look for it. The default is 60. Do not lower it: at 30 seconds validation fails with NXDOMAIN. Raise it if your resolvers are slower to pick the record up.

certbot certonly \
    --authenticator dns-nexdns \
    --dns-nexdns-credentials ~/.nexdns/certbot-credentials.ini \
    --dns-nexdns-propagation-seconds 120 \
    -d '*.example.com'

Renewal

On the first successful issuance Certbot stores the authenticator and its options in the certificate's renewal configuration (/etc/letsencrypt/renewal/example.com.conf), so renewal takes no plugin flags:

certbot renew --dry-run   # runs against the staging CA and issues nothing
certbot renew

Two things must still hold at renewal time: the credentials file has to exist at the path recorded in that configuration, and the token has to still be valid. A revoked or expired token is invisible until then, which is what makes a scheduled --dry-run worth having.

Certbot installed from PyPI does not schedule itself. Distribution packages usually ship a certbot.timer systemd unit or an /etc/cron.d/certbot entry; a pip or pipx install ships neither, so add one:

# /etc/cron.d/certbot
17 3,15 * * * root certbot renew --quiet

Twice a day is what Certbot's own documentation recommends. renew exits without doing anything unless a certificate is close to expiry, and an off-the-hour minute avoids the pile-up at 00.

Docker

The image inherits Certbot's entrypoint, so it is a drop-in replacement for certbot/certbot with the authenticator already present:

docker run --rm \
    -v /etc/letsencrypt:/etc/letsencrypt \
    -v /var/lib/letsencrypt:/var/lib/letsencrypt \
    -v ~/.nexdns/certbot-credentials.ini:/credentials.ini:ro \
    nexdns/certbot certonly \
        --authenticator dns-nexdns \
        --dns-nexdns-credentials /credentials.ini \
        --email admin@example.com \
        --agree-tos --non-interactive \
        -d example.com -d '*.example.com'

Tags are nexdns/certbot:latest and one per release, for example nexdns/certbot:1.0.0.

A renewal loop with Compose:

services:
  certbot:
    image: nexdns/certbot:latest
    restart: unless-stopped
    volumes:
      - /etc/letsencrypt:/etc/letsencrypt
      - /var/lib/letsencrypt:/var/lib/letsencrypt
      - ./certbot-credentials.ini:/credentials.ini:ro
    entrypoint: >
      /bin/sh -c 'trap exit TERM; while :; do
        certbot renew --quiet;
        sleep 12h & wait $${!};
      done'

$$ is how Compose escapes a literal $, so the container shell sees wait ${!} and waits on the backgrounded sleep. That is what makes docker compose down stop the container at once instead of up to twelve hours later.

Choosing the API base URL

The plugin talks to https://api.nexdns.tech/v1. Set dns_nexdns_api_url in the credentials file to point it at a different base URL:

# ~/.nexdns/certbot-credentials.ini
dns_nexdns_token    = nxd_0000000000000000000000000000000000000000
dns_nexdns_api_url = https://api.nexdns.tech/v1

The value is the full base URL including the API version segment. A trailing slash is ignored. Omit the key entirely to get the default, which is the value shown above.

Options

Command-line options, as reported by certbot --help dns-nexdns:

Option Default Description
--dns-nexdns-credentials none Path to the credentials INI file. Certbot prompts for it interactively when it is not supplied, and stores the resolved absolute path
--dns-nexdns-propagation-seconds 60 Seconds to wait for the TXT record to become visible before the ACME server is asked to verify it

Credentials file keys:

Key Default Description
dns_nexdns_token none, required API token, in the form nxd_…
dns_nexdns_api_url https://api.nexdns.tech/v1 API base URL

What happens during a challenge

  1. Certbot asks the plugin to publish a value at _acme-challenge.<domain>.
  2. The plugin looks up the zone by trying the longest suffix of that name first and shortening it label by label, so _acme-challenge.shop.example.com lands in a delegated shop.example.com zone when the account has one, and in example.com otherwise.
  3. It creates the TXT record with a TTL of 120 seconds. Certbot then waits out --dns-nexdns-propagation-seconds.
  4. After validation the plugin lists the zone's TXT records, matches its own value exactly and deletes that one record. Other TXT records at the same name are left untouched.
  5. If cleanup fails it logs a warning instead of failing the run, because the certificate has already been issued by that point. The leftover record stays in the zone until you remove it.

Every API call carries a 30-second timeout, so an unreachable API fails the run rather than hanging an unattended certbot renew indefinitely.

Troubleshooting

API failures reach you through Certbot as Failed to add TXT record: NexDNS API error: <message>, where the message comes from the API and names the cause.

NexDNS API error: Invalid API key. The value of dns_nexdns_token matches no key on the account. A truncated paste is the usual reason; tokens begin with nxd_. A key that has been revoked in the panel reports the same thing.

NexDNS API error: API key has expired. The key was created with an expiry date that has passed. Create a replacement and update the credentials file. The renewal configuration keeps pointing at the same file, so nothing else changes.

NexDNS API error: API access is not included in your plan. The key is valid, but the account's plan does not include API access. See pricing. This is checked on every request, so it can appear on a key that worked before a plan change.

NexDNS API error: API key lacks the "records.write" permission. The key is missing a scope. It needs all three of zones.read, records.read and records.write. Since scopes cannot be edited, create a new key with all three and revoke the old one. A key created with no scopes at all reports API key has no permissions assigned instead.

NexDNS API error: Too many requests. Please retry after … The account's API rate limit was reached. The response carries a Retry-After header. This is worth checking if you drive many certificates through one token in a tight loop.

Failed to add TXT record: Zone not found for _acme-challenge.example.com. No zone on the token's account matches any suffix of that name. nexdns zone list from the CLI shows what the account actually holds. A certificate for shop.example.com needs either the example.com zone or a shop.example.com zone of its own to be present there.

Missing property in credentials configuration file …: Property "dns_nexdns_token" not found The INI file exists but has no dns_nexdns_token line, or its value is empty. Key names are exact, including the dns_nexdns_ prefix.

File not found: … --dns-nexdns-credentials points at a path that does not exist. Certbot resolves the path to an absolute one at issuance and records that in the renewal configuration, so a credentials file that is later moved or deleted fails at renewal rather than at issuance.

No TXT record found at _acme-challenge.example.com, reported by the ACME server The record was written, but the ACME server did not see it. Two causes actually produce this:

  • The wait was too short. Keep --dns-nexdns-propagation-seconds at 60 or raise it.
  • The domain is not delegated to the NexDNS nameservers, so the record is published in a zone nobody queries for that name. Check the delegation with dig NS example.com.

To watch the record appear, run this from a second terminal while Certbot is in its propagation wait, since the record is cleaned up as soon as the attempt ends:

dig +short TXT _acme-challenge.example.com @1.1.1.1

Certbot writes the full detail of any failure to /var/log/letsencrypt/letsencrypt.log. Reproduce with --dry-run -v, which runs against the staging CA and so does not spend production issuance quota on a failing attempt.

Development

git clone https://github.com/nexdns/certbot-dns-nexdns
cd certbot-dns-nexdns
pip install -e .[dev] certbot
python -m pytest

The unit tests mock the HTTP session, so they need neither an account nor network access.

Installing is not the same as being loadable: Certbot discovers plugins through entry points, and a load failure is reported to the user as this plugin's bug. Check discovery the way CI does:

certbot plugins 2>&1 | grep dns-nexdns

For end-to-end work, issue against Let's Encrypt's staging CA. Its certificates are untrusted, which is the point, and a failing run there costs no production issuance quota:

certbot certonly \
    --server https://acme-staging-v02.api.letsencrypt.org/directory \
    --authenticator dns-nexdns \
    --dns-nexdns-credentials ~/.nexdns/certbot-credentials.ini \
    -d '*.example.com'

License

Apache-2.0

Metadata

Release files for certbot-dns-nexdns 1.0.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for certbot-dns-nexdns 1.0.1
File Size Uploaded Provenance Actions
certbot_dns_nexdns-1.0.1.tar.gz 19.9 kB With provenance Details for certbot_dns_nexdns-1.0.1.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for certbot-dns-nexdns 1.0.1
File Interpreter ABI Platform Actions
certbot_dns_nexdns-1.0.1-py3-none-any.whl Python 3 none any Details for certbot_dns_nexdns-1.0.1-py3-none-any.whl

Total release size: 35.0 kB

Release files / certbot_dns_nexdns-1.0.1.tar.gz

Download URL certbot_dns_nexdns-1.0.1.tar.gz
Size 19.9 kB
Tags Source
SHA-256 checksum
How to use checksums
7aef5180db6b90cacf19ac994975412203791e2132f0d08894a87fe17a7f65e0
BLAKE2b-256 checksum
How to use checksums
4177eb697abff338f2e8aee81251fbc59a4ec9b24cb596d2a821c769636d5af7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 1, 2026.

Transparency log

Release files / certbot_dns_nexdns-1.0.1-py3-none-any.whl

Download URL certbot_dns_nexdns-1.0.1-py3-none-any.whl
Size 15.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
5716c00b9101002d6a667211673ebbb7fce714fd698ef445d794f6abb159c378
BLAKE2b-256 checksum
How to use checksums
2afd286a8a5f0cd105f92926fddc0e8d7488f3326245b0d1d2d8de1e12e1f6fb
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 1, 2026.

Transparency log

Security for certbot-dns-nexdns 1.0.1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for certbot_dns_nexdns-1.0.1.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 1, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

1.0.1 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page