cert-manager: Automated TLS Certificates That Actually Renew
Let Kubernetes do the certificate paperwork so you never get the expiry email again

Everyone who has run a website for more than a year has lived this particular horror. You get an email. The subject line contains the word “expires.” You ignore it because you’re busy. Then on a Sunday morning, the morning, your site is throwing certificate warnings to every visitor and you’re SSH’d in trying to remember how certbot works while your coffee goes cold. TLS certificates expire on a schedule that is precisely engineered to be longer than your memory and shorter than your attention span.
cert-manager exists to end this. It’s a Kubernetes controller that treats certificates as first-class resources: you declare the certificate you want, and it obtains it, stores it, and — crucially — renews it before it expires, forever, without you. The “actually renews” in the title is doing real work, because the renewal is the part everyone else’s automation quietly skips.
1 The mental model
cert-manager adds a handful of custom resources to your cluster. The two you’ll touch most are Issuer (or ClusterIssuer, its cluster-wide sibling) and Certificate. An Issuer describes how certificates are obtained — which certificate authority, which account, which challenge method. A Certificate describes what you want — the domain names, where to store the resulting secret, how long before expiry to renew.
cert-manager watches these, talks to the CA on your behalf, completes whatever proof-of-ownership dance is required, and drops a standard Kubernetes TLS secret into your namespace. Your ingress controller mounts that secret like any other. Nothing downstream needs to know cert-manager exists.
2 A Let’s Encrypt issuer
The overwhelmingly common case is Let’s Encrypt with an HTTP-01 challenge, where the CA proves you control a domain by fetching a token over HTTP. Here’s a ClusterIssuer pointed at Let’s Encrypt’s production endpoint:
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: [email protected]
privateKeySecretRef:
name: letsencrypt-prod-account-key
solvers:
- http01:
ingress:
class: nginxTwo things I beg you to do. First, start against the staging endpoint (acme-staging-v02) until your config is right — production Let’s Encrypt has rate limits, and you can lock yourself out for a week by hammering it with a misconfigured loop. Second, that email is where expiry warnings go if cert-manager ever stops working, so use a real one.
3 You usually don’t write Certificates by hand
You can request a Certificate resource explicitly, but in practice you let your ingress do it for you with annotations. Add one annotation and a tls block, and cert-manager notices, creates the Certificate behind the scenes, solves the challenge, and populates the secret:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: blog
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
ingressClassName: nginx
tls:
- hosts:
- blog.example.com
secretName: blog-tls
rules:
- host: blog.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: blog
port:
number: 80Apply that and watch the machinery turn. cert-manager creates a temporary Ingress route to answer the challenge, Let’s Encrypt fetches the token, the certificate is issued, and blog-tls appears. From here on it’s hands-off — and you can watch the state with kubectl describe certificate blog-tls, which spells out exactly which stage it’s at if something stalls.
4 The renewal is the whole point
A Certificate from Let’s Encrypt lasts 90 days. cert-manager renews it at roughly the two-thirds mark — around 30 days before expiry by default — completely automatically. It re-runs the challenge, gets a fresh certificate, updates the secret in place, and your ingress controller picks up the new secret. You do nothing. You receive no email. You forget certificates exist, which is the correct relationship to have with them.
This is the difference between cert-manager and a cron job calling certbot. The cron job works until the day the renewal command silently fails and nobody notices for a month. cert-manager surfaces failures as events and conditions on the resource, retries with backoff, and integrates with monitoring so a stuck renewal can page you instead of ambushing you.
5 DNS-01 for the wildcard crowd
If you want a wildcard certificate, or your services aren’t reachable from the public internet for an HTTP challenge, you switch to a DNS-01 solver. cert-manager proves ownership by creating a TXT record via your DNS provider’s API. It’s more setup — you’re handing it API credentials — but it’s the only way to get *.example.com and it works for internal-only services. Most managed DNS providers have a supported solver.
6 Is it worth it?
For a single static box, honestly, plain certbot with its systemd timer is fine and cert-manager is overkill. But the moment you’re on Kubernetes with more than one or two TLS endpoints, cert-manager stops being optional. It’s the kind of infrastructure that earns its keep by being boring: you install it once, wire your ingress to it, and then certificates simply stop being a thing you think about. I’ve had clusters running for years where I genuinely could not tell you when the certificates renew, because I have never once had to care. That is exactly the outcome you want, and it’s worth the afternoon.




