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.