cert-manager: Automated TLS Certificate Management in Kubernetes (2026)
cert-manager is the standard Kubernetes add-on for automating TLS certificate issuance and renewal. It integrates with Let's Encrypt, Vault, Venafi, and any ACME-compatible CA to issue certificates as Kubernetes Secret objects — eliminating manual certificate management and expired-cert incidents.
Why cert-manager
Before cert-manager, TLS in Kubernetes meant manually running certbot, copying certificate files into Secrets, and writing cron jobs to renew them before expiry. The failure mode — a forgotten renewal causing an outage — was common.
cert-manager solves this by treating certificates as Kubernetes resources with a controller that continuously reconciles desired state (a Certificate object) with actual state (the TLS Secret). When a cert is 30 days from expiry, cert-manager automatically renews it.
- Works with any ACME CA (Let's Encrypt, ZeroSSL) and private CAs (Vault, Venafi, self-signed).
- Issues certs via HTTP-01 (for single domains) or DNS-01 (for wildcard domains).
- Integrates directly with Ingress controllers via a single annotation.
- Exposes Prometheus metrics and Kubernetes events for monitoring renewal status.
Installing cert-manager
Install cert-manager using its official Helm chart or the single-file manifest:
# Helm installation (recommended)
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 \
--version v1.14.0
# Verify pods are running
kubectl get pods -n cert-manager
# NAME READY STATUS
# cert-manager-7d9f4f8c5-xk9pq 1/1 Running
# cert-manager-cainjector-5c7d8f9b6-m2lrp 1/1 Running
# cert-manager-webhook-6b8d7c4f9-n4qzv 1/1 Running
--set crds.enabled=true flag installs the cert-manager Custom Resource Definitions (Certificate, Issuer, ClusterIssuer, etc.) as part of the Helm release, so they are managed and upgraded with the chart.
Issuers and ClusterIssuers
An Issuer is namespace-scoped and can only issue certificates within its namespace. A ClusterIssuer is cluster-scoped and can issue certificates across all namespaces. For most production setups, ClusterIssuer is the right choice.
cert-manager supports two ACME environments from Let's Encrypt:
- Staging (
https://acme-staging-v02.api.letsencrypt.org/directory) — use for testing; issues untrusted certs but has much higher rate limits. - Production (
https://acme-v02.api.letsencrypt.org/directory) — issues trusted certs; rate-limited to 50 certs per domain per week.
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-account-key
solvers:
- http01:
ingress:
class: nginx
Apply the ClusterIssuer and verify it is ready:
kubectl apply -f clusterissuer.yaml
kubectl get clusterissuer letsencrypt-prod
# NAME READY AGE
# letsencrypt-prod True 30s
HTTP-01 Challenge
HTTP-01 is the simplest ACME challenge. Let's Encrypt sends an HTTP request to http://<domain>/.well-known/acme-challenge/<token> to verify you control the domain. cert-manager automatically creates a temporary Ingress rule and Pod to serve this challenge.
Requirements for HTTP-01:
- Your domain's DNS must resolve to the cluster's Ingress IP.
- Port 80 must be publicly reachable (Let's Encrypt contacts your domain directly).
- Cannot issue wildcard certificates — use DNS-01 for those.
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: example-com-tls
namespace: production
spec:
secretName: example-com-tls-secret
issuerRef:
name: letsencrypt-prod
kind: ClusterIssuer
dnsNames:
- example.com
- www.example.com
DNS-01 Challenge and Wildcard Certs
DNS-01 proves domain ownership by creating a TXT record in your DNS zone. It supports wildcard certificates (*.example.com) and works even when port 80 is not publicly accessible — making it the right choice for internal services and wildcard certs.
Example using Route53 (AWS):
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-dns01
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: admin@example.com
privateKeySecretRef:
name: letsencrypt-dns01-account-key
solvers:
- dns01:
route53:
region: us-east-1
accessKeyIDSecretRef:
name: route53-credentials
key: access-key-id
secretAccessKeySecretRef:
name: route53-credentials
key: secret-access-key
Wildcard Certificate using DNS-01:
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: wildcard-example-com
namespace: production
spec:
secretName: wildcard-example-com-tls
issuerRef:
name: letsencrypt-dns01
kind: ClusterIssuer
dnsNames:
- "*.example.com"
- example.com
Certificate Resource
The Certificate resource is the core cert-manager object. It specifies what cert you want, where to store it, and which issuer to use. cert-manager reconciles this into a Kubernetes TLS Secret:
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: api-tls
namespace: production
spec:
secretName: api-tls-secret # TLS Secret created here
duration: 2160h # 90 days
renewBefore: 720h # Renew 30 days before expiry
subject:
organizations:
- Techoral Inc
isCA: false
privateKey:
algorithm: RSA
encoding: PKCS1
size: 2048
usages:
- server auth
- client auth
dnsNames:
- api.example.com
issuerRef:
name: letsencrypt-prod
kind: ClusterIssuer
group: cert-manager.io
Check certificate status:
kubectl describe certificate api-tls -n production
kubectl get secret api-tls-secret -n production -o yaml
Ingress TLS Annotation
The fastest path to TLS on an Ingress is the cert-manager annotation. cert-manager watches Ingress objects and automatically creates Certificate resources when it sees the annotation:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-app
namespace: production
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
tls:
- hosts:
- app.example.com
secretName: my-app-tls # cert-manager populates this Secret
rules:
- host: app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-app-service
port:
number: 80
tls.secretName.
Automatic Renewal
cert-manager automatically renews certificates before they expire. The renewal window is controlled by renewBefore in the Certificate spec (default: 30 days before expiry). When the renewal window opens, cert-manager re-runs the ACME challenge and updates the Secret with the new certificate — zero downtime, no human intervention.
Monitor renewal status:
# Watch certificate readiness
kubectl get certificates -A -w
# Check events for renewal activity
kubectl describe certificate my-app-tls -n production | grep -A5 Events
# View cert expiry
kubectl get secret my-app-tls -n production -o jsonpath='{.data.tls\.crt}' \
| base64 -d | openssl x509 -noout -dates
Prometheus metrics from cert-manager expose certmanager_certificate_expiration_timestamp_seconds — use this to alert when a cert is unexpectedly close to expiry (indicating a renewal failure).
Troubleshooting
When a certificate is stuck in a pending or failed state, follow this diagnostic chain:
# Step 1: Check the Certificate status
kubectl describe certificate <name> -n <namespace>
# Look for: Ready: False, Message: ..., Reason: ...
# Step 2: Check the CertificateRequest
kubectl get certificaterequest -n <namespace>
kubectl describe certificaterequest <name> -n <namespace>
# Step 3: Check the Order (ACME challenge object)
kubectl get order -n <namespace>
kubectl describe order <name> -n <namespace>
# Step 4: Check the Challenge
kubectl get challenge -n <namespace>
kubectl describe challenge <name> -n <namespace>
# Step 5: Check cert-manager controller logs
kubectl logs -n cert-manager deploy/cert-manager -f
Common failure causes:
- HTTP-01 fails — port 80 blocked by firewall, or Ingress not publicly reachable from Let's Encrypt.
- DNS-01 fails — incorrect IAM permissions for Route53/CloudDNS, or DNS propagation not complete.
- Rate limited — too many certificate requests to production Let's Encrypt. Test with the staging issuer first.
- Wrong issuer name — the annotation or Certificate
issuerRefdoes not match the ClusterIssuer name exactly.
FAQ
Yes. cert-manager has an
SelfSigned issuer type and a CA issuer type that issues certificates from a private root CA stored as a Kubernetes Secret.
Yes. The Vault issuer integrates with HashiCorp Vault's PKI secrets engine. cert-manager authenticates to Vault via Kubernetes service account tokens or AppRole and requests certificates through the Vault PKI API.
Use namespace-scoped
Issuer objects for tenant namespaces so tenants cannot issue certificates from a ClusterIssuer they don't own. Reserve ClusterIssuer for platform-team use.
cert-manager updates the TLS Secret in place. Ingress controllers and services that watch the Secret (NGINX, Traefik, Istio) pick up the new cert automatically — usually within seconds, with no downtime.