Certificate expiry is the outage everyone has had. It’s fully predictable, it’s written down somewhere, and it still takes the site down at 3am because the person who renewed it last time changed teams.

cert-manager removes the human from the loop: you declare that an Ingress needs TLS, and it obtains the certificate from Let’s Encrypt (or your internal CA), stores it as a Secret, and renews it before it expires. CNCF graduated, and close to standard equipment in a Kubernetes cluster.

Install

  helm repo add jetstack https://charts.jetstack.io
helm install cert-manager jetstack/cert-manager \
  --namespace cert-manager --create-namespace \
  --set crds.enabled=true
  

An issuer, once

  apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: ops@example.com
    privateKeySecretRef:
      name: letsencrypt-prod-account-key
    solvers:
      - http01:
          ingress:
            ingressClassName: nginx
  

Then every certificate is one annotation on the Ingress:

  metadata:
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
  tls:
    - hosts: [app.example.com]
      secretName: app-example-com-tls
  rules:
    - host: app.example.com
      # ...
  

That’s it. cert-manager sees the annotation, requests the certificate, proves you control the domain, writes the Secret, and renews at two-thirds of the lifetime. Nothing to diarise.

Use the staging issuer first

Let’s Encrypt rate-limits certificate issuance per domain per week, and a misconfiguration can burn that budget in an afternoon. Point a second ClusterIssuer at https://acme-staging-v02.api.letsencrypt.org/directory, get the flow working there — the certificate won’t be trusted by browsers, which is fine — and switch to production once it works.

Debugging, in the right order

Every failed issuance leaves a trail of objects, and reading them in order finds the problem in about a minute:

  kubectl describe certificate app-example-com-tls
kubectl describe certificaterequest <name>
kubectl describe order <name>
kubectl describe challenge <name>      # the actual error usually lives here
  

Nine times out of ten the challenge failure is one of: DNS not pointing at the ingress yet, the HTTP-01 path blocked by a redirect to HTTPS, or a network policy stopping Let’s Encrypt from reaching the solver pod. Wildcards can’t use HTTP-01 at all — those need the DNS-01 solver and credentials for your DNS provider.

Beyond public certificates

The same controller issues from an internal CA (Issuer of kind CA) or from Vault, which is how internal service-to-service TLS gets automated. If you go on to run a service mesh like Istio, the mesh handles workload identity itself — cert-manager stays responsible for the certificates at the edge.

Next

Traffic inside the cluster needs the same care as traffic at the edge → Istio

Last updated 25 Aug 2026, 00:00 UTC. history