Short answer

cert-manager is a Kubernetes controller that automates TLS certificates. You define an Issuer (or ClusterIssuer) that knows how certificates are issued — typically via ACME against Let's Encrypt — and a Certificate resource describing which domains you want covered. cert-manager issues the certificate, stores it in a Kubernetes Secret, and renews it automatically before expiry.

The four resource types

  • Issuer — issues certificates in a single namespace.
  • ClusterIssuer — like Issuer, but applies to the whole cluster. Choose this if several namespaces should share the same ACME account.
  • Certificate — your desired state: which dnsNames, which issuer, and the name of the Secret the result goes into.
  • Secret — the generated TLS secret (tls.crt + tls.key) that your ingress and pods use.

1. ClusterIssuer with ACME HTTP-01

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:
            class: nginx

The HTTP-01 solver makes cert-manager temporarily create a pod and an ingress route for /.well-known/acme-challenge/… so Let's Encrypt can validate the domain. It requires the cluster to be publicly reachable on port 80.

2. The Certificate resource

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: example-tls
  namespace: web
spec:
  secretName: example-tls
  duration: 2160h     # 90 days
  renewBefore: 360h   # renew 15 days before expiry
  dnsNames:
    - example.com
    - www.example.com
  issuerRef:
    name: letsencrypt-prod
    kind: ClusterIssuer

When you apply this, cert-manager creates a CertificateRequest, runs the challenge, and writes the result to the example-tls Secret. renewBefore controls when the renewal happens.

3. Wildcards require DNS-01

HTTP-01 cannot issue wildcards. For *.example.com you use a DNS-01 solver, e.g. via Cloudflare:

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-dns
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: ops@example.com
    privateKeySecretRef:
      name: letsencrypt-dns-account-key
    solvers:
      - dns01:
          cloudflare:
            apiTokenSecretRef:
              name: cloudflare-api-token
              key: api-token

cert-manager creates the _acme-challenge TXT record itself via the DNS provider's API and cleans it up again afterwards.

Use the certificate in an Ingress

You can also let cert-manager issue automatically from an annotation on an Ingress — then you do not need to write the Certificate resource yourself:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: web
  namespace: web
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
  tls:
    - hosts: [example.com, www.example.com]
      secretName: example-tls
  rules:
    - host: example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: web
                port:
                  number: 80

Troubleshooting: why is the certificate not Ready?

Follow the chain Certificate → CertificateRequest → Order → Challenge:

kubectl describe certificate example-tls -n web
kubectl get challenges -A
kubectl describe order -n web

The common causes are a blocked HTTP-01 route, a wrong DNS token, or hitting Let's Encrypt's rate limits by testing against prod instead of the staging server. See also the ACME pillar for the challenge types and mTLS in Kubernetes if you issue client certificates to services.

cert-manager automates — but does not see the cluster from outside

cert-manager knows a Secret was renewed, but not whether the certificate is actually served correctly to the outside world — whether the chain is complete, whether an old ingress still points at an expired Secret, whether an app that cached the certificate at startup needs a rolling restart, or whether an endpoint entirely outside the cluster has been forgotten. CertControl scans all of your endpoints from the outside and brings them together in one place, so cert-manager-managed and non-automated certificates are caught by the same monitoring.

Frequently asked questions

What is the difference between Issuer and ClusterIssuer?

An Issuer only issues certificates in its own namespace. A ClusterIssuer is cluster-wide and can be used from all namespaces. Choose ClusterIssuer if several namespaces should share the same ACME account.

Where does the certificate end up?

In a Kubernetes Secret of type kubernetes.io/tls with the fields tls.crt and tls.key. Your ingress and pods reference that Secret via secretName.

Can cert-manager issue wildcard certificates?

Yes, but only with a DNS-01 solver, because wildcards require DNS validation. HTTP-01 cannot issue wildcards.

How do I troubleshoot a certificate stuck in 'not ready'?

Follow the chain with kubectl describe on the Certificate, then CertificateRequest, Order and Challenge. The messages there usually point to a blocked challenge route, a wrong DNS token or a hit rate limit.

How do I avoid Let's Encrypt's rate limits during testing?

Use Let's Encrypt's staging server (acme-staging-v02) in a separate ClusterIssuer while testing. Staging has far higher limits and the certificates are untrusted — perfect for validating the setup.