A TLS certificate expiring on a Sunday night is one of the rare incidents that hits everyone at once, with no gradual warning: the day before, everything works; the next day, every browser shows a security warning. cert-manager exists specifically to take that expiry date off the list of things a team has to watch manually.

What an Ingress does, and does not do on its own

An Ingress resource describes HTTP(S) routing rules: which hostname and path should reach which internal Service. It does nothing on its own; it needs an Ingress Controller (nginx-ingress, Traefik, or another) running in the cluster to translate those rules into actual configuration.

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

The cert-manager.io/cluster-issuer annotation is the only link between the Ingress and TLS automation: without it, the tls field does nothing, since no process would ever populate the app-tls secret it references.

What cert-manager actually automates

cert-manager watches Ingress resources (or dedicated Certificate resources) and, as soon as it finds one annotated with a valid ClusterIssuer, automatically triggers a certificate request against the configured authority, typically Let’s Encrypt via the ACME protocol. The resulting certificate is stored as a Kubernetes Secret, and cert-manager tracks its expiry date to trigger renewal well ahead of the deadline, with no human intervention.

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

The HTTP-01 trap: a challenge that assumes DNS already points there

The simplest validation method, HTTP-01, has Let’s Encrypt make an HTTP request to the domain being certified, on a specific path (/.well-known/acme-challenge/...), to verify the requester actually controls that domain. cert-manager temporarily creates a dedicated Ingress and pod to answer that request. The trap: this validation assumes the domain’s DNS already points at the cluster at request time. On infrastructure still mid-migration, with a DNS API token that does not cover the relevant zone, or with a default-deny NetworkPolicy blocking the solver’s ephemeral pods, the certificate stays stuck pending indefinitely without any error message that makes the actual cause obvious at a glance (kubectl describe challenge remains the most direct command for finding the real cause).

The alternative, DNS-01, validates by creating a temporary TXT record with the DNS provider instead of exposing an HTTP path: it works even when the domain does not yet point at the cluster, which makes it better suited to wildcard certificates or in-progress migrations, at the cost of a DNS API access that needs to be scoped correctly.

Where this fits

Exposing a service over HTTPS without a certificate that expires on a public holiday is part of the foundation laid during a Kubernetes migration: versioned, automated, with no recurring manual step. It is also a mechanism this site actually uses for its own certificate.

What to remember

An Ingress routes traffic but manages no certificate on its own; cert-manager fills that gap by automating the request and renewal through Let’s Encrypt. HTTP-01 is the simplest method but assumes DNS already points at the cluster, which makes it fragile mid-migration; DNS-01 removes that constraint at the cost of a correctly-scoped DNS API access.