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:

  1. cert-manager issues the cert → Secret created in ingress-apisix on infra
  2. Seal the secret: kubeseal --context=dev-k8s -o yaml < secret.yaml > sealed.yaml
  3. Commit to fluxcd/projects/dev/kustomization/spoke/secrets/dev-wildcard-tls.yaml
  4. 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:

  1. kubectl get challenge -n cert-manager — if challenge is pending for > 2 min, DNS propagation is slow
  2. kubectl get order -n cert-manager — shows ACME order status
  3. 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.