cert-manager DNS-01 with DNScale
Quick answer
cert-manager can solve ACME DNS-01 challenges through DNScale using the public webhook source and Helm chart. Build a webhook image, install the chart with that image, store a DNS-name-scoped DNScale API token as a Kubernetes Secret, and configure an Issuer or ClusterIssuer with groupName acme.dnscale.eu and solverName dnscale. A public GHCR image and OCI chart are not yet published. Test against Let's Encrypt staging before production.
On this page
What you'll learn
- Install and configure the DNScale cert-manager DNS-01 webhook
- Store DNScale API credentials safely in Kubernetes
- Create a staging and production ClusterIssuer
- Request wildcard and normal certificates through DNS-01
- Troubleshoot common cert-manager DNS-01 failures
cert-manager can automate Let's Encrypt certificates inside Kubernetes. With DNScale's DNS-01 webhook, cert-manager proves domain control by creating temporary _acme-challenge TXT records through the DNScale API.
Use this guide when:
- you need wildcard certificates
- port 80 HTTP-01 validation is blocked or unsuitable
- certificates should be issued from inside Kubernetes
- Ingress or Gateway resources need automated TLS
For broader ACME background, read Let's Encrypt DNS-01 Challenges with DNScale. For cert-manager's generic webhook model, see the official cert-manager webhook solver documentation and DNS01 documentation.
How It Works
The flow is:
- A
Certificateresource asks cert-manager for a certificate. - cert-manager creates an ACME order and challenge.
- cert-manager sends the DNS01 challenge to the DNScale webhook.
- The webhook creates a TXT record at
_acme-challenge.<name>through the DNScale API. - Let's Encrypt validates the TXT record.
- cert-manager stores the issued certificate in a Kubernetes Secret.
- The webhook removes the temporary TXT record.
The webhook uses cert-manager's external DNS01 solver fields: groupName, solverName, and provider-specific config.
Prerequisites
You need:
- a Kubernetes cluster
- cert-manager installed
- Helm 3 or newer and tools to build and push a container image
- a DNScale-hosted DNS zone publicly delegated to its assigned nameservers
- a DNScale API token scoped to the exact challenge owners in the validation zone
- CAA records that allow Let's Encrypt if you publish CAA
Use staging before production. It proves the webhook, token, DNS propagation, and cleanup path without spending production rate limits.
Live staging issuance for an apex and wildcard, renewal, and TXT cleanup have been verified with Kubernetes 1.35.0 and cert-manager 1.19.3. These are the tested versions, not a compatibility guarantee for every release.
Install cert-manager
Install cert-manager with your normal cluster process. The official installation path changes over time, so follow the current cert-manager installation documentation.
After installation, confirm the controller is running:
kubectl get pods -n cert-managerInstall the DNScale Webhook
The public webhook repository contains the source, Dockerfile, and Helm chart. A public GHCR container image and OCI chart are not yet published, so build an image and install from the checkout:
git clone https://github.com/dnscaleou/cert-manager-webhook-dnscale.git
cd cert-manager-webhook-dnscale
# Choose a registry and image tag your cluster can pull.
IMAGE_REPOSITORY=registry.example.com/your-team/cert-manager-webhook-dnscale
IMAGE_TAG=1.0.0
docker build -t "$IMAGE_REPOSITORY:$IMAGE_TAG" .
docker push "$IMAGE_REPOSITORY:$IMAGE_TAG"
helm install cert-manager-webhook-dnscale ./deploy/dnscale-webhook \
--namespace cert-manager \
--set fullnameOverride=cert-manager-webhook-dnscale \
--set image.repository="$IMAGE_REPOSITORY" \
--set image.tag="$IMAGE_TAG" \
--wait --timeout 180s
kubectl wait apiservice/v1alpha1.acme.dnscale.eu \
--for=condition=Available --timeout=60sBuild for your cluster's CPU architecture and configure image pull credentials if your registry requires them. For a disposable kind cluster, the repository README also documents loading the image directly.
The webhook group name used by the DNScale chart is:
acme.dnscale.euConfirm the webhook service is present:
kubectl get deployment,service,apiservice -n cert-manager | grep dnscaleStore the DNScale API Token
For a ClusterIssuer, put the token Secret in cert-manager's cluster resource
namespace, normally cert-manager. For a namespaced Issuer, put it in the
Issuer's namespace. The webhook uses the credential namespace supplied by
cert-manager.
Save only the token in a local .test-api-token file, then create the Secret
without placing the token in command history or a manifest:
chmod 600 .test-api-token
kubectl -n cert-manager create secret generic dnscale-api-token \
--from-file=api-token=.test-api-tokenThe webhook repository excludes this file from Git and Docker build contexts.
Use a DNS-name-scoped DNScale key. The webhook needs to find the matching zone and create/delete TXT records, so grant zones:read, records:read, and records:write. Add _acme-challenge for an apex or wildcard certificate and names such as _acme-challenge.api for certificates below the apex. Matching is exact rather than a subtree or record-type rule. It does not need billing, user-management, or unrelated zone access.
The chart defaults to allowing reads of dnscale-api-token in the configured
cert-manager namespace. For an Issuer in apps, create its Secret there and
include the extra permission in a Helm values file:
secretAccess:
- namespace: cert-manager
secretNames: [dnscale-api-token]
- namespace: apps
secretNames: [dnscale-api-token]Include that file with -f when installing or upgrading the chart, retaining
your image settings. Adjust secretAccess and certManager values if your
cluster uses a different resource namespace or service account.
Create a Staging ClusterIssuer
Start with Let's Encrypt staging:
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-staging-dnscale
spec:
acme:
email: admin@example.com
server: https://acme-staging-v02.api.letsencrypt.org/directory
privateKeySecretRef:
name: letsencrypt-staging-dnscale-account-key
solvers:
- dns01:
webhook:
groupName: acme.dnscale.eu
solverName: dnscale
config:
apiTokenSecretRef:
name: dnscale-api-token
key: api-tokenApply it:
kubectl apply -f clusterissuer-staging.yaml
kubectl describe clusterissuer letsencrypt-staging-dnscaleRequest a Test Certificate
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: example-com-staging
namespace: default
spec:
secretName: example-com-staging-tls
issuerRef:
name: letsencrypt-staging-dnscale
kind: ClusterIssuer
dnsNames:
- example.com
- "*.example.com"Apply and inspect:
kubectl apply -f certificate-staging.yaml
kubectl describe certificate example-com-staging
kubectl get orders,challengesReplace example.com with your delegated test zone. During validation, query
the zone's assigned authoritative nameservers for the TXT record, replacing
ns1.example.net below with one of those servers:
dig NS example.com +short
dig TXT _acme-challenge.example.com @ns1.example.net +shortWhen issuance succeeds, cert-manager stores the certificate in the configured Secret. Check readiness and the Secret's presence:
kubectl wait certificate/example-com-staging --for=condition=Ready --timeout=300s
kubectl get secret example-com-staging-tlsThe staging certificate is not browser-trusted. Use it only to verify the flow.
Create a Production ClusterIssuer
After staging works, create the production issuer:
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-production-dnscale
spec:
acme:
email: admin@example.com
server: https://acme-v02.api.letsencrypt.org/directory
privateKeySecretRef:
name: letsencrypt-production-dnscale-account-key
solvers:
- dns01:
webhook:
groupName: acme.dnscale.eu
solverName: dnscale
config:
apiTokenSecretRef:
name: dnscale-api-token
key: api-tokenSwitch your real Certificate resources to letsencrypt-production-dnscale only after staging succeeds.
Ingress Example
If your ingress controller reads cert-manager certificates from Secrets, reference the production issuer and TLS secret:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app
namespace: default
annotations:
cert-manager.io/cluster-issuer: letsencrypt-production-dnscale
spec:
tls:
- hosts:
- app.example.com
secretName: app-example-com-tls
rules:
- host: app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: app
port:
number: 80Troubleshooting
Challenge stays pending
Inspect the challenge:
kubectl describe challenge -ACommon causes:
- webhook deployment is not ready
- APIService is unavailable
- Secret name or key is wrong
- token cannot access the zone
- CAA does not allow Let's Encrypt
- resolver cannot see the TXT record yet
Secret not found
Check where cert-manager expects the token Secret:
kubectl get secret -A | grep dnscale-api-tokenThe apiTokenSecretRef name and key must match the Secret exactly. For a
forbidden error, check that the chart's secretAccess grants access to that
name in the correct namespace.
TXT record does not appear
Check webhook logs:
kubectl logs -n cert-manager deploy/cert-manager-webhook-dnscaleThen verify the zone is hosted in DNScale and the token can edit it.
CAA blocks issuance
If the zone publishes CAA, allow Let's Encrypt:
example.com. CAA 0 issue "letsencrypt.org"Verify:
dig CAA example.com +shortRead DNS CAA Record Explained before changing CAA policy for production domains.
Cleanup leaves stale records
Stale _acme-challenge TXT records usually mean cleanup failed after validation. They are not normally dangerous, but they are clutter. Confirm the token has delete permission and inspect webhook logs for API errors.
Operational Notes
- Use one token per zone or environment.
- Keep staging and production ACME account keys separate.
- Use staging before production issuer changes.
- Monitor certificate renewal events.
- Treat webhook upgrades like production certificate infrastructure changes.
- Keep CAA records aligned with your ACME CA.
Frequently asked questions
- Why use DNS-01 with cert-manager?
- DNS-01 is required for wildcard certificates and works even when the Kubernetes workload is not publicly reachable on port 80. cert-manager publishes a temporary TXT record, waits for validation, then removes it after issuance.
- What does the DNScale webhook do?
- The webhook implements cert-manager's external DNS01 solver interface. cert-manager sends ACME challenge requests to the webhook, and the webhook creates or deletes the required
_acme-challengeTXT records through the DNScale API. - What DNScale permissions does the webhook need?
- Use an API token with
zones:read,records:read, andrecords:write, scoped to every exact_acme-challengeowner the certificate set requires. Do not reuse an account-wide administrative key. - Should I use an Issuer or ClusterIssuer?
- Use an Issuer when one namespace owns the certificate workflow. Use a ClusterIssuer when many namespaces need the same ACME account and DNS-01 solver.
- Can this issue wildcard certificates?
- Yes. DNS-01 is the ACME challenge type that supports wildcard certificates such as
*.example.com. - What should I test first?
- Use Let's Encrypt staging first, request a disposable certificate, inspect the Challenge and Order resources, and confirm the temporary TXT record appears and is cleaned up.
Put the guide into practice
Manage authoritative DNS records, review changes, and monitor your zones with DNScale.
Start free