Let's Encrypt DNS-01 Challenges with DNScale
Automate Let's Encrypt certificate issuance and renewal with DNS-01 challenges using DNScale. Works for wildcard certificates, edge-proxied origins, and servers with no public port 80.
Answer snapshot
ACME DNS-01 proves you control a domain by publishing a `_acme-challenge.<name>` TXT record that the CA verifies. Required for wildcard certificates (HTTP-01 can't validate wildcards), works for any server regardless of port-80 reachability, and integrates cleanly with private/internal hosts. Use a zone-scoped DNScale API token so the ACME client has minimum access. DNScale is built into lego v5+ as provider code `dnscale`; embedded products such as Traefik only support it once they ship a lego v5+ build, while Caddy needs a native DNS module rather than a plain lego CLI configuration.
What you'll learn
- Understand when DNS-01 is the right ACME challenge type and when HTTP-01 is simpler
- Set up Let's Encrypt with DNScale using certbot, lego v5+, acme.sh, or cert-manager
- Scope API tokens correctly so ACME clients have the minimum access they need
- Avoid common pitfalls — propagation timeouts, CNAME delegation, CAA conflicts
Let's Encrypt supports three ACME challenge types: HTTP-01, TLS-ALPN-01, and DNS-01. For most public websites, HTTP-01 is the default because it's simple — a file on port 80 proves you control the domain. But when HTTP-01 doesn't fit, DNS-01 is the escape hatch: instead of proving control via HTTP, you prove it by placing a TXT record in DNS.
DNScale's API lets any ACME client automate DNS-01 challenges against your zones. This guide covers when to use DNS-01, and how to set it up with the four most common Let's Encrypt clients.
When to use DNS-01
HTTP-01 breaks in four common situations:
- Wildcard certificates (
*.example.com). Let's Encrypt does not support HTTP-01 for wildcards — DNS-01 is the only option. - Servers behind edge proxies or CDNs. If your origin sits behind a proxy (DNScale's Postscale edge network, Cloudflare, an ADC, etc.) the origin can't receive HTTP-01 validation traffic. DNS-01 bypasses the data plane entirely.
- No port 80 exposed. Firewalled origins, internal services, mail servers, and hosts behind strict NATs often have no public HTTP listener. DNS-01 needs no HTTP endpoint.
- Private or internal hostnames that still resolve publicly (split-horizon DNS, lab environments). HTTP-01 can't reach them; DNS-01 only needs DNS reachability for the ACME validator.
If none of those apply, HTTP-01 is usually simpler — fewer moving parts, no API token to manage. DNS-01 is worth the extra setup only when you need it.
Tip: DNS-01 plays nicely with CAA records. Set
0 issue "letsencrypt.org"on your zone, and Let's Encrypt will be the only CA that can issue for it regardless of challenge type.
How DNS-01 works
For each domain on a certificate request, Let's Encrypt asks the client to publish a TXT record at _acme-challenge.<domain> containing a specific value. The ACME validator then queries public DNS for that record. If the value matches, the challenge passes.
Your ACME client drives three steps on each issuance or renewal:
- Add — create the TXT record via the DNScale API
- Wait — give the record time to propagate across DNScale's authoritative nameservers
- Clean up — delete the TXT record after validation completes
All four clients covered below automate this flow end-to-end.
API token setup
Before any client can use DNS-01, create a DNScale API token:
- Sign in at dnscale.eu.
- Go to Dashboard → API Keys and create a new key.
- Grant the minimum scopes:
zones:read,records:read,records:write. - Scope the key to specific zones if you can — e.g. only
example.comif that's the only domain the client will renew.
Store the token as you would any other secret: a file with mode 600, a secret manager, or an environment variable in your CI/CD pipeline. Never check it into a public repository.
Tip: Separate tokens per client. If certbot and cert-manager both issue certs on different hosts, use two tokens so you can rotate one without disrupting the other.
Client: certbot
The certbot-dns-dnscale plugin is published on PyPI.
pip install certbot-dns-dnscaleCreate the credentials file:
# /etc/letsencrypt/dnscale.ini
dns_dnscale_api_token = <your-token>chmod 600 /etc/letsencrypt/dnscale.iniIssue a certificate:
certbot certonly \
--authenticator dns-dnscale \
--dns-dnscale-credentials /etc/letsencrypt/dnscale.ini \
--dns-dnscale-propagation-seconds 60 \
-d example.com \
-d "*.example.com"Subsequent renewals (certbot renew) automatically use the same authenticator — certbot stores the choice in /etc/letsencrypt/renewal/<name>.conf.
To convert an existing certificate from HTTP-01 to DNS-01, edit its renewal config:
[renewalparams]
authenticator = dns-dnscale
dns_dnscale_credentials = /etc/letsencrypt/dnscale.ini
dns_dnscale_propagation_seconds = 60Remove any webroot_path, standalone, or [[webroot_map]] leftover from the old authenticator.
Client: lego v5+
lego is the Go ACME client and library behind many DNS-01 integrations. DNScale is available in lego v5+ as provider code dnscale.
For a dedicated walkthrough with systemd, Docker, and external Caddy/Traefik certificate-file examples, see lego DNS-01 with the DNScale DNS Provider.
| Tool | DNScale DNS-01 status |
|---|---|
lego CLI v5+ | Works with --dns dnscale and DNSCALE_API_TOKEN. |
| Traefik | Traefik uses lego internally, but DNScale works only in Traefik versions built with lego v5 or newer. If your Traefik build still embeds lego v4, dnscale will not be recognized. |
| Caddy | Caddy uses CertMagic and Caddy DNS modules rather than the lego CLI provider list. Use lego as an external certificate runner for now; do not configure dns dnscale in Caddy until a native DNScale Caddy DNS module is available. |
lego CLI:
export DNSCALE_API_TOKEN="<your-token>"
lego \
--accept-tos \
--email you@example.com \
--dns dnscale \
--domains example.com \
--domains "*.example.com" \
runUse the Let's Encrypt staging directory before the first production run:
DNSCALE_API_TOKEN="<your-token>" lego \
--server https://acme-staging-v02.api.letsencrypt.org/directory \
--accept-tos \
--email you@example.com \
--dns dnscale \
--domains example.com \
--domains "*.example.com" \
runSecret-file form:
sudo install -d -m 700 /etc/lego
printf '%s' '<your-token>' | sudo tee /etc/lego/dnscale-token >/dev/null
sudo chmod 600 /etc/lego/dnscale-token
DNSCALE_API_TOKEN_FILE=/etc/lego/dnscale-token lego \
--accept-tos \
--email you@example.com \
--dns dnscale \
--domains example.com \
--domains "*.example.com" \
runDocker:
docker run --rm \
-e DNSCALE_API_TOKEN="<your-token>" \
-v "$PWD/.lego:/root/.lego" \
goacme/lego:v5.2.0 \
--accept-tos \
--email you@example.com \
--dns dnscale \
--domains example.com \
--domains "*.example.com" \
runUseful DNScale-specific lego environment variables:
| Variable | Purpose | Default |
|---|---|---|
DNSCALE_API_TOKEN | DNScale API token. | Required |
DNSCALE_HTTP_TIMEOUT | API request timeout, in seconds. | 30 |
DNSCALE_POLLING_INTERVAL | Delay between propagation checks, in seconds. | 2 |
DNSCALE_PROPAGATION_TIMEOUT | Maximum wait for propagation, in seconds. | 60 |
DNSCALE_TTL | TTL for ACME TXT records, in seconds. | 120 |
lego stores issued certificates under .lego/certificates/ by default. For unattended renewals, run the same command with renew instead of run from a systemd timer, cron job, or CI job that can read the token secret.
Client: acme.sh
The DNScale API plugin ships with acme.sh.
export DNSCALE_Token=<your-token>
acme.sh --issue --dns dns_dnscale -d example.com -d "*.example.com"acme.sh saves the token to ~/.acme.sh/account.conf on first use, so subsequent renewals don't need the env var. To rotate, edit that file or pass --renew-with-dnsapi dns_dnscale on the next renewal.
Client: cert-manager (Kubernetes)
For Kubernetes workloads, DNScale's cert-manager webhook plugs into cert-manager's external webhook interface.
-
Install the webhook via Helm:
helm install cert-manager-webhook-dnscale \ oci://ghcr.io/dnscaleou/charts/cert-manager-webhook-dnscale \ --namespace cert-manager -
Store the token as a Kubernetes secret:
apiVersion: v1 kind: Secret metadata: name: dnscale-api-token namespace: cert-manager stringData: api-token: <your-token> -
Define a ClusterIssuer referencing the webhook:
apiVersion: cert-manager.io/v1 kind: ClusterIssuer metadata: name: letsencrypt-dnscale spec: acme: email: you@example.com server: https://acme-v02.api.letsencrypt.org/directory privateKeySecretRef: name: letsencrypt-dnscale-account-key solvers: - dns01: webhook: groupName: acme.dnscale.eu solverName: dnscale config: apiTokenSecretRef: name: dnscale-api-token key: api-token -
Request a certificate as usual — cert-manager will drive DNS-01 through the webhook.
Common pitfalls
Propagation too short
ACME validators cache DNS. If your client's propagation wait is shorter than the TTL of negative caches and nearby resolvers, validation will fail. Start at 60 seconds. If you see intermittent failures, raise to 120.
With lego, tune the DNScale provider rather than adding an unrelated global delay:
DNSCALE_PROPAGATION_TIMEOUT=120 \
DNSCALE_POLLING_INTERVAL=5 \
DNSCALE_TTL=120 \
DNSCALE_API_TOKEN="<your-token>" \
lego --accept-tos --email you@example.com --dns dnscale \
-d example.com -d "*.example.com" runlego says the provider is unknown
If lego returns an error such as unrecognized DNS provider: dnscale, the binary is too old. DNScale support starts with lego v5. Check the version:
lego --versionUpgrade the standalone CLI, or wait for the product embedding lego to ship a build based on lego v5 or newer. This matters for Traefik and other Go products that vendor lego internally: their supported provider list is the provider list in the embedded lego version, not the list on your workstation.
Missing or unreadable token
The lego provider expects DNSCALE_API_TOKEN or DNSCALE_API_TOKEN_FILE. The _FILE value must point to a file containing only the token value:
sudo install -d -m 700 /etc/lego
printf '%s' '<your-token>' | sudo tee /etc/lego/dnscale-token >/dev/null
sudo chmod 600 /etc/lego/dnscale-token
DNSCALE_API_TOKEN_FILE=/etc/lego/dnscale-token lego \
--accept-tos --email you@example.com --dns dnscale \
-d example.com -d "*.example.com" runCNAME delegation
If _acme-challenge.example.com is a CNAME to another name (a common pattern for delegating DNS-01 to a separate zone), the ACME validator follows the CNAME — but your ACME client must publish the TXT at the target of the CNAME, not at _acme-challenge.example.com itself. All four clients above handle this automatically if configured with the correct zone.
CAA blocking Let's Encrypt
If your zone has a 0 issue "other-ca.com" CAA record and no entry for letsencrypt.org, Let's Encrypt will refuse to issue. Before moving a domain to DNS-01, confirm CAA allows the CA you're using:
dig CAA example.com +shortOrphan _acme-challenge.* TXT records
If cleanup fails silently (API error, plugin bug, client crash) the challenge TXT record may linger on your zone. These don't affect future issuance (every renewal uses a fresh random value), but they can clutter the zone over time. Periodically delete any _acme-challenge.* TXT records older than a few hours.
For lego renewals, orphan records usually mean the challenge process was interrupted after validation but before cleanup. Confirm the token has record delete permission and rerun the command once; if cleanup still fails, inspect the DNScale API response in lego's debug output:
DNSCALE_API_TOKEN="<your-token>" lego \
--debug \
--accept-tos --email you@example.com --dns dnscale \
-d example.com -d "*.example.com" renewRate limits
Let's Encrypt limits 50 certificates per registered domain per week and 5 duplicate certs per week. DNS-01 doesn't change these limits — but if your client is stuck in a retry loop (e.g. a misconfigured propagation wait causing repeated failures) you can burn through the duplicate cert limit quickly. Always run --dry-run first when changing client or zone setup.
Which client should you pick?
- Standalone host with a single cert — certbot. Familiar, well-documented, ships in every distro.
- Go-stack or standalone certificate runner — use lego v5+. It is a single binary and now has DNScale built in.
- Traefik already in your stack — use Traefik's DNS challenge once your Traefik version embeds lego v5 or newer; older builds do not know provider
dnscale. - Caddy already in your stack — keep Caddy serving certificates from disk or use another ACME client until a native DNScale Caddy DNS module exists.
- Minimal, shell-only host — acme.sh. No Python, no compiled binary, just bash.
- Kubernetes — cert-manager with the DNScale webhook.
For production today, choose a client that already has a DNScale integration in the version you run. Pick by where the rest of your infrastructure already sits.
Frequently asked questions
- When should I use DNS-01 instead of HTTP-01?
- Three cases: (1) wildcard certificates — HTTP-01 can't validate wildcards, DNS-01 can; (2) internal hosts not reachable from the public internet — DNS-01 only needs DNS, not port 80; (3) a fleet of servers behind a load balancer where centralising cert issuance is easier than coordinating per-host HTTP-01 challenges. For a single public web server with port 80 available, HTTP-01 is simpler.
- Why do I need a CAA record alongside Let's Encrypt?
- Strictly, you don't — Let's Encrypt issues to any domain that passes ACME validation, with or without CAA. But CAA is a defence-in-depth: it constrains *which* CAs may issue for your domain, so a compromised CA elsewhere can't issue a rogue certificate. Add `0 issue "letsencrypt.org"` to authorize Let's Encrypt; combine with iodef for incident reporting.
- How do I scope a DNScale API token for ACME?
- Create a zone-scoped API key for the specific zone(s) the ACME client manages. The client only needs permission to create and delete TXT records under `_acme-challenge.*` — not to modify other records or manage other zones. Zone-scoping limits the blast radius if the token is leaked. See the DNScale dashboard's API-key creation flow.
- What happens if propagation is slow and the challenge times out?
- Some ACME clients add a configurable propagation wait before signalling 'ready' to the CA. lego's DNScale provider uses `DNSCALE_PROPAGATION_TIMEOUT` and `DNSCALE_POLLING_INTERVAL`; certbot's DNScale plugin has equivalent settings. If your TXT TTL is reasonable (60–300s) and your DNS provider propagates promptly, defaults work. For very large or geographically distributed setups, increase the wait.
- Can I delegate `_acme-challenge` to a separate provider?
- Yes — the CNAME-delegation pattern. Add a CNAME at `_acme-challenge.example.com` pointing to a name on a different zone (typically a dedicated `acme.example.com` subzone) where the ACME client has write access. Useful when your main zone is locked down or managed by a different team. Let's Encrypt and most ACME clients follow CNAMEs during validation.
Related guides
Ready to manage your DNS with confidence?
DNScale provides anycast DNS hosting with a global network, real-time analytics, and an easy-to-use API.
Start free