If you run anything on Kubernetes, you've probably had this moment: a certificate quietly expires, an Ingress starts throwing browser warnings, and someone has to dig out the one person who knows how it was set up.
You can stop that from ever happening. cert-manager is a Kubernetes add-on that issues certificates, stores them as Secrets and renews them before they expire. In this post, we'll set it up so you can automate SSL certificate renewal for good.
Why SSL automation matters more every year
Certificate lifetimes keep getting shorter. Publicly trusted certificates are capped at 200 days now, 100 days from March 2027 and 47 days from March 2029. Free certificates are moving too, with Let's Encrypt shortening its default lifetime from 90 days to 64 in February 2027 and 45 in February 2028.
Renewing by hand at that pace doesn't scale. SSL automation is how teams keep up.
What you'll need
- A running Kubernetes cluster and kubectl access
- Helm 3
- An Ingress controller (the examples use NGINX)
- A domain pointing at your cluster
- About 15 minutes
Step 1: Install cert-manager
helm repo add jetstack https://charts.jetstack.io
helm repo update
helm install cert-manager jetstack/cert-manager \
--namespace cert-manager \
--create-namespace \
--set crds.enabled=true
Pin a chart version with --version in production so upgrades are deliberate. Then check that the pods are running:
kubectl get pods -n cert-manager
You should see three pods in Running state: cert-manager, cainjector and webhook.
Step 2: Create an Issuer
An issuer tells cert-manager where to get certificates. We'll use a ClusterIssuer, which works across all namespaces. Start with the staging server so you don't hit rate limits while testing:
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-staging
spec:
acme:
server: https://acme-staging-v02.api.letsencrypt.org/directory
email: you@example.com
privateKeySecretRef:
name: letsencrypt-staging-key
solvers:
- http01:
ingress:
ingressClassName: nginx
Apply it:
kubectl apply -f clusterissuer-staging.yaml
When it works, create a second issuer named letsencrypt-prod with the production server URL: https://acme-v02.api.letsencrypt.org/directory.
Step 3: Request a certificate from an Ingress
The simplest way is to add an annotation and a tls section to your Ingress:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-app
annotations:
cert-manager.io/cluster-issuer: letsencrypt-staging
spec:
ingressClassName: nginx
tls:
- hosts:
- app.example.com
secretName: app-example-com-tls
rules:
- host: app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-app
port:
number: 80
cert-manager sees the annotation, creates a Certificate, completes the validation challenge and stores the result in the secret named app-example-com-tls. Your Ingress controller picks it up automatically.
Prefer explicit control? Create the Certificate yourself:
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: app-example-com
namespace: default
spec:
secretName: app-example-com-tls
duration: 2160h # 90 days
renewBefore: 360h # renew 15 days before expiry
dnsNames:
- app.example.com
issuerRef:
name: letsencrypt-staging
kind: ClusterIssuer
The renewBefore field is what makes renewal automatic. If you leave it out, cert-manager chooses a sensible default and renews when about two thirds of the lifetime has passed.
Step 4: Check that it worked
kubectl get certificate
kubectl describe certificate app-example-com
Look for Ready: True. If it's stuck, trace the chain of resources:
kubectl get certificaterequest,order,challenge
You can also read the expiry date straight from the Secret:
kubectl get secret app-example-com-tls -o jsonpath='{.data.tls.crt}' \
| base64 -d | openssl x509 -noout -dates
Once staging works, switch the issuer name to letsencrypt-prod, delete the old Secret and let cert-manager reissue it.
Wildcard certificates with DNS-01
HTTP-01 can't issue wildcards. For *.example.com, use the DNS-01 solver. Here's a Cloudflare example:
solvers:
- dns01: cloudflare: apiTokenSecretRef: name: cloudflare-api-token key: api-token
Create the token Secret first, with permission to edit DNS records for your zone only. Other providers, such as Route 53 and Google Cloud DNS, are supported too.
Using a commercial certificate authority
You don't have to use a free CA. cert-manager works with any CA that supports ACME. Many commercial CAs require External Account Binding (EAB), which links your cert-manager account to your CA account:
spec:
acme:
server: https://YOUR-CA-ACME-DIRECTORY-URL
email: you@example.com
privateKeySecretRef:
name: commercial-ca-account-key
externalAccountBinding:
keyID: YOUR_KEY_ID
keySecretRef:
name: eab-hmac-secret
key: secret
solvers:
- http01:
ingress:
ingressClassName: nginx
A commercial CA typically adds things free certificates don't: OV or EV validation, a warranty and a support team. [Add: Certera's ACME or API details, if supported, and a link to the setup guide.]
Don't skip monitoring
Automation can still fail silently, for example when DNS changes or an API token expires. cert-manager exposes Prometheus metrics, including certmanager_certificate_expiration_timestamp_seconds. This alert fires when a certificate has less than 7 days left:
- alert: CertificateExpiringSoon expr: (certmanager_certificate_expiration_timestamp_seconds - time()) < 7 * 24 * 3600 for: 15m labels: severity: warning annotations: summary: "Certificate {{ $labels.name }} expires in under 7 days"
Treat this as your safety net. If it ever fires, something in your SSL automation broke and needs a human.
Common problems and quick fixes
- Challenge stuck in pending: check that the domain's DNS points at your Ingress and that port 80 is reachable from the internet.
- Wrong Ingress class: make sure ingressClassName in the solver matches your controller.
- Rate limit errors: use the staging issuer while testing.
- Certificate stays False: run kubectl describe on the Order and Challenge resources. The error message is usually clear.
- Need to force a renewal: cmctl renew app-example-com
Wrapping up
With cert-manager you declare what you want, and the cluster handles issuing and renewing. That's the point of SSL automation: you set it up once and stop thinking about expiry dates.
If you're managing certificates across many domains or clients and want OV/EV validation, a warranty and a support team, take a look at what we offer at Certera.
Have questions or a setup that doesn't fit this guide? Drop a comment and I'll help.
Top comments (0)