Let's Encrypt DNS-01 with certbot and lego

Let's Encrypt DNS-01 with certbot and lego

Nov 12, 2026 ssl certificates let's encrypt acme protocol dns certbot lego api developers

Short answer: To get a Let's Encrypt certificate with the DNS-01 challenge, your ACME client puts a TXT record at _acme-challenge.<your domain>, Let's Encrypt checks it, and the record is removed. It is the only challenge that can issue wildcard certificates, and it works for servers that are not reachable from the internet. Automating it needs a DNS provider with an API. This guide shows a small hook script that lets certbot and lego do it through the NameOcean API, and the shortcut of letting NameOcean issue the certificate for you.

How the DNS-01 challenge works

  1. Your ACME client (certbot, lego, acme.sh…) orders a certificate and receives a token for each name.
  2. It creates a TXT record at _acme-challenge.example.com with a value derived from the token and your account key.
  3. Let's Encrypt queries your domain's nameservers. If the value is there, the name is validated and the certificate is issued.
  4. The client deletes the TXT record.

Three details catch people out:

  • A certificate for example.com and *.example.com needs two values at the same name, _acme-challenge.example.com. Your script must add a record, not replace the existing one.
  • Old records pile up if cleanup fails, and Let's Encrypt rejects the lookup once the answer gets too big. Always delete what you created.
  • All of the domain's nameservers must have the record before Let's Encrypt looks. Wait for propagation instead of sleeping a fixed time when you can.

Got an idea? Find the perfect domain for it

Our AI domain name generator suggests available names across hundreds of extensions. Free SSL, DNS and Whois privacy included.

The shortcut: let NameOcean issue it

If your domain uses NameOcean nameservers, you don't need an ACME client at all. We issue a free wildcard Let's Encrypt certificate (example.com and *.example.com) with DNS-01 on our side and renew it automatically. Turn it on in the dashboard, or from code:

nameocean ssl issue example.com --wait

# or with the API
curl -X POST https://nameocean.net/api/v1/ssl/example.com \
  -H "Authorization: Bearer $NAMEOCEAN_API_KEY"

GET /api/v1/ssl/example.com then returns the status, the expiry date and a download link. Use your own ACME client instead when the private key must never leave your server, or when you need a different CA or certificate layout.

certbot and lego with the NameOcean API

Neither certbot nor lego ships a NameOcean plugin, but both can run an external script: certbot with --manual-auth-hook and --manual-cleanup-hook, lego with its exec DNS provider. The script below works for both. It finds the right zone in your account, adds the TXT record with a 5-minute TTL, waits until every nameserver answers with it (certbot only; lego checks this itself), and on cleanup deletes only the value it added.

1. Create an API key

At Dashboard → API, create a key with only the dns:read and dns:write permissions. Add the server's IP address to the key's allowed list, so the key is useless anywhere else. Keys need two-step login on your account.

2. Save the script

Save it as /usr/local/bin/nameocean-dns01.sh and make it executable (chmod +x). It needs curl and jq, and uses dig when it is installed.

#!/bin/sh
# DNS-01 hook for NameOcean DNS: certbot (manual hooks) and lego (--dns exec).
# Needs curl and jq, and an API key with dns:read and dns:write in NAMEOCEAN_API_KEY.
#   certbot: nameocean-dns01.sh present | nameocean-dns01.sh cleanup
#   lego:    called as  <present|cleanup> <fqdn> <value>
set -eu
API=${NAMEOCEAN_API:-https://nameocean.net/api/v1}
: "${NAMEOCEAN_API_KEY:?set NAMEOCEAN_API_KEY}"

api() {
    method=$1; path=$2; shift 2
    curl -fsS -X "$method" -H "Authorization: Bearer $NAMEOCEAN_API_KEY" \
        -H "Content-Type: application/json" "$@" "$API$path"
}

action=$1
if [ $# -ge 3 ]; then                 # lego
    fqdn=${2%.}; value=$3; wait=no
else                                  # certbot (wildcards arrive without "*.")
    fqdn=_acme-challenge.${CERTBOT_IDENTIFIER:-$CERTBOT_DOMAIN}; value=$CERTBOT_VALIDATION; wait=yes
fi

# the longest zone in the account that the name is under
zones=$(api GET /dns)
zone=$(printf '%s' "$zones" | jq -r --arg f "$fqdn" \
    '[.zones[].name | select(. as $z | $f | endswith("." + $z))] | max_by(length) // empty')
[ -n "$zone" ] || { echo "no NameOcean DNS zone for $fqdn" >&2; exit 1; }
host=${fqdn%."$zone"}

case $action in
present)
    api POST "/dns/$zone/records" -d "$(jq -n --arg h "$host" --arg t "$value" \
        '{type: "TXT", hostname: $h, text: $t, ttl: "5m"}')" >/dev/null
    # lego checks propagation itself; for certbot, wait until every nameserver answers
    if [ "$wait" = yes ] && command -v dig >/dev/null; then
        for i in $(seq 1 24); do
            missing=0
            for ns in $(dig +short NS "$zone"); do
                dig +short TXT "$fqdn" @"$ns" | grep -q -- "$value" || missing=1
            done
            [ "$missing" = 0 ] && break
            sleep 5
        done
    elif [ "$wait" = yes ]; then
        sleep 30
    fi
    ;;
cleanup)
    records=$(api GET "/dns/$zone/records")
    printf '%s' "$records" | jq -r --arg h "$host" --arg t "$value" \
        '.records[] | select(.type == "TXT" and .hostname == $h and .text == $t) | .id' |
    while read -r id; do
        api DELETE "/dns/$zone/records/TXT/$id" >/dev/null
    done
    ;;
*)
    echo "usage: $0 present|cleanup" >&2; exit 2
    ;;
esac

3a. certbot

export NAMEOCEAN_API_KEY=no_live_…
certbot certonly --manual --preferred-challenges dns \
  --manual-auth-hook "/usr/local/bin/nameocean-dns01.sh present" \
  --manual-cleanup-hook "/usr/local/bin/nameocean-dns01.sh cleanup" \
  -d example.com -d '*.example.com'

Certbot passes the domain and the value in CERTBOT_IDENTIFIER and CERTBOT_VALIDATION; for a wildcard it passes the domain without *., so both names use _acme-challenge.example.com. Run certbot renew --dry-run afterwards to check that renewals run the hooks and can see NAMEOCEAN_API_KEY (set it in the environment of the cron job or systemd timer that runs renewals).

3b. lego

export NAMEOCEAN_API_KEY=no_live_…
EXEC_PATH=/usr/local/bin/nameocean-dns01.sh \
  lego run --dns exec -d example.com -d '*.example.com'

This is lego v5 syntax; in v4 the flags come before the command (lego --dns exec -d … run). lego calls the script with present or cleanup, the full record name and the value, then polls the nameservers itself (60 seconds by default, EXEC_PROPAGATION_TIMEOUT to change it). In v5, running the same lego run command again renews the certificate when it is due.

Test against staging first

Let's Encrypt's production limits are strict. Add --test-cert to certbot, or point lego at the Let's Encrypt staging directory (https://acme-staging-v02.api.letsencrypt.org/directory), until the whole flow works, then run it once more against production.

Other clients

  • acme.sh: supports custom DNS scripts in its dnsapi folder (dns_<name>_add and dns_<name>_rm). The two API calls are the same as in the script above.
  • cert-manager: needs a webhook solver for providers it doesn't support. We don't publish one yet; use the NameOcean-issued certificate or lego in a CronJob meanwhile.

Domain DNS somewhere else?

The script writes the TXT record into the zone that holds the name, so the domain must use NameOcean DNS. If you only want DNS-01 records on NameOcean, move just the _acme-challenge name with a CNAME: Let's Encrypt follows CNAME records when it validates. Your ACME client then has to write to the target name, so this needs a client or script that supports a challenge alias.

FAQ

How long should I wait after creating the TXT record?

Until every authoritative nameserver of the domain returns it. The script asks each of them instead of guessing a delay, and gives up after two minutes.

Do I need DNS-01 for a wildcard certificate?

Yes. Let's Encrypt issues wildcard certificates only with the DNS-01 challenge; HTTP-01 and TLS-ALPN-01 can't validate them.

Is it safe to keep a DNS API key on the web server?

Limit what the key can do: only DNS permissions, an expiry date and the server's IP in the allowed list. A leaked key can then change DNS records from that IP only, and you can delete it at once in the dashboard.

What about DNS-PERSIST-01?

It replaces per-certificate TXT records with one permanent record and is not available at Let's Encrypt yet. See DNS-PERSIST-01 explained.

Sources

Don't have your domain on NameOcean yet? Search for one below, or transfer it and get DNS, the API and a free wildcard certificate with it.

Got an idea? Find the perfect domain for it

Our AI domain name generator suggests available names across hundreds of extensions. Free SSL, DNS and Whois privacy included.

Read in other languages: