cert-manager: wildcard certificates with DNS-01 challenge
Published: 2026-04-01
cert-manager automates TLS certificate issuance and renewal. For wildcard certificates (*.dev.test.antonnovikov.com), HTTP-01 challenges don't work — you need DNS-01, which proves domain ownership by creating a DNS TXT record.
Installation
cert-manager runs on the infra cluster and issues certs for all environments. Spoke clusters consume the certs via SealedSecrets.
yamlapiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: cert-manager
namespace: cert-manager
spec:
chart:
spec:
chart: cert-manager
version: "v1.14.*"
sourceRef:
kind: HelmRepository
name: jetstack
namespace: flux-system
values:
installCRDs: true
podDnsPolicy: None
podDnsConfig:
nameservers:
- 8.8.8.8
- 1.1.1.1
Custom DNS config is important — cert-manager pods need to reach the authoritative DNS server for your domain, not just the cluster DNS.
ClusterIssuer with ACME DNS-01
For domains hosted on Yandex Cloud DNS:
yamlapiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: admin@antonnovikov.com
privateKeySecretRef:
name: letsencrypt-prod-account-key
solvers:
- dns01:
webhook:
groupName: acme.yandex.cloud
solverName: yandex-cloud-dns
config:
folder: "${YC_FOLDER_ID}"
serviceAccountSecretRef:
name: yc-dns-sa-key
key: sa-key.json
The cert-manager-webhook-yandex webhook handles YC DNS API calls. Install it from the cert-manager-webhook-yandex chart.
For AWS Route53:
yamlsolvers:
- dns01:
route53:
region: us-east-1
accessKeyIDSecretRef:
name: route53-credentials
key: access-key-id
secretAccessKeySecretRef:
name: route53-credentials
key: secret-access-key
For Cloudflare:
yamlsolvers:
- dns01:
cloudflare:
email: admin@example.com
apiTokenSecretRef:
name: cloudflare-api-token
key: api-token
Certificate resource
yamlapiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: dev-wildcard
namespace: ingress-apisix
spec:
secretName: dev-test-antonnovikov-com-tls
issuerRef:
name: letsencrypt-prod
kind: ClusterIssuer
commonName: "*.dev.test.antonnovikov.com"
dnsNames:
- "*.dev.test.antonnovikov.com"
- "dev.test.antonnovikov.com"
duration: 2160h # 90 days (Let's Encrypt max)
renewBefore: 360h # renew 15 days before expiry
cert-manager creates the Secret dev-test-antonnovikov-com-tls with tls.crt and tls.key. Ingress or APISIX TLS references this secret.
Distribution to spoke clusters
cert-manager runs only on infra. Spoke clusters need the TLS secret via SealedSecrets:
- cert-manager issues the cert → Secret created in
ingress-apisixon infra - Seal the secret:
kubeseal --context=dev-k8s -o yaml < secret.yaml > sealed.yaml - Commit to
fluxcd/projects/dev/kustomization/spoke/secrets/dev-wildcard-tls.yaml - Flux applies it to the spoke on next reconcile
On renewal (cert-manager auto-renews 15 days before expiry), re-seal and commit the updated secret.
Troubleshooting
bash# Certificate status
kubectl describe certificate dev-wildcard -n ingress-apisix
# CertificateRequest status
kubectl describe certificaterequest -n ingress-apisix
# ACME challenge status
kubectl describe challenge -n ingress-apisix
# Webhook logs (for DNS-01 failures)
kubectl logs -n cert-manager -l app=cert-manager-webhook-yandex
# Check if TXT record was created
dig TXT _acme-challenge.dev.test.antonnovikov.com @8.8.8.8
The most common failure: DNS propagation hasn't reached Let's Encrypt's validators. Check that _acme-challenge.dev.test.antonnovikov.com TXT record exists before the challenge times out (default: 60 seconds).
Certificate lifecycle
Certificate → CertificateRequest → Order → Challenge
If a Certificate object shows False / Ready, check:
kubectl get challenge -n cert-manager— if challenge ispendingfor > 2 min, DNS propagation is slowkubectl get order -n cert-manager— shows ACME order status- cert-manager controller logs:
kubectl logs -n cert-manager deploy/cert-manager
Let's Encrypt rate limits: 50 certificates per registered domain per week. Use letsencrypt-staging issuer during testing.