cert-manager is the standard way to get and renew TLS certificates in Kubernetes. It watches your Ingress resources (or explicit Certificate objects), requests certificates from Let’s Encrypt (or other ACME CAs, or your own CA), stores them as Kubernetes Secrets, and renews them automatically before they expire.
Prerequisite: an Ingress controller exposing ports 80/443. See Kubernetes ingress.
Install with Helm
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
kubectl get pods -n cert-manager
See Helm tutorial.
Create ClusterIssuers (staging and production)
Start with Let’s Encrypt staging, which has generous rate limits, while you test:
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-staging
spec:
acme:
server: https://acme-staging-v02.api.letsencrypt.org/directory
email: [email protected]
privateKeySecretRef:
name: letsencrypt-staging-key
solvers:
- http01:
ingress:
ingressClassName: nginx
---
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-key
solvers:
- http01:
ingress:
ingressClassName: nginx
kubectl apply -f issuers.yaml
kubectl get clusterissuer
Set ingressClassName to match your controller (for example traefik on k3s).
Get a certificate for an Ingress
Add an annotation and a tls section:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: web
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
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: web, port: { number: 80 } }
cert-manager creates a Certificate, solves the challenge, and stores the certificate in the Secret app-example-com-tls, which the Ingress controller then uses.
kubectl get certificate
kubectl describe certificate app-example-com-tls
HTTP-01 vs DNS-01
| Challenge | How | Use when |
|---|---|---|
| HTTP-01 | Let’s Encrypt fetches a token over port 80 | Normal public sites; DNS already points to the cluster |
| DNS-01 | cert-manager creates a TXT record via your DNS provider’s API | Wildcards (*.example.com), internal services, port 80 not reachable |
DNS-01 example with Cloudflare (API token stored in a Secret):
solvers:
- dns01:
cloudflare:
apiTokenSecretRef:
name: cloudflare-api-token
key: api-token
Many DNS providers are supported directly, or via webhooks. Compare: Certbot wildcard certificates.
Renewals
Certificates are renewed automatically, by default 30 days before expiry for 90-day certificates. Nothing to schedule. Monitor them anyway:
kubectl get certificates -A
cmctl status certificate app-example-com-tls # cert-manager CLI
Troubleshooting
Follow the chain: Certificate → CertificateRequest → Order → Challenge:
kubectl describe certificate app-example-com-tls
kubectl get challenges -A
kubectl describe challenge <name>
kubectl logs -n cert-manager deploy/cert-manager
Common causes:
- DNS doesn’t point to the cluster yet (HTTP-01 fails).
- Port 80 is blocked, or redirected in a way that breaks
/.well-known/acme-challenge/. - Wrong
ingressClassNamein the issuer. - Rate limits from too many production attempts. Test with staging first.
Quick reference
| Task | Command / field |
|---|---|
| Install | helm install cert-manager jetstack/cert-manager … --set crds.enabled=true |
| Issuer | ClusterIssuer (staging, prod) |
| Use on Ingress | cert-manager.io/cluster-issuer: letsencrypt-prod + tls: |
| Wildcards | DNS-01 solver |
| Status | kubectl get certificate, kubectl get challenges |
Related: Let’s Encrypt with Certbot (Nginx) and k3s.