kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.17.2/cert-manager.yaml*.example.com) REQUIRE the DNS-01 challenge solver -- HTTP-01 cannot validate wildcard domains [src4]privateKeySecretRef Secret is your ACME account identity -- deleting it forces re-registration and may trigger rate limits [src1]revisionHistoryLimit to 1 -- upgrading garbage-collects all stale CertificateRequests [src6]| Resource | Kind | Scope | Purpose | Key Fields |
|---|---|---|---|---|
| Issuer | cert-manager.io/v1 | Namespace | ACME account + solver config for one namespace | spec.acme.server, spec.acme.solvers |
| ClusterIssuer | cert-manager.io/v1 | Cluster-wide | ACME account + solver config for all namespaces | Same as Issuer, but cluster-scoped |
| Certificate | cert-manager.io/v1 | Namespace | Declares desired TLS certificate | spec.dnsNames, spec.secretName, spec.issuerRef |
| CertificateRequest | cert-manager.io/v1 | Namespace | Auto-created; tracks individual issuance attempt | Read-only status resource |
| Order | acme.cert-manager.io/v1 | Namespace | Auto-created; ACME order lifecycle | Tracks challenge state |
| Challenge | acme.cert-manager.io/v1 | Namespace | Auto-created; one per domain to validate | Shows solver status and errors |
| HTTP-01 solver | solver type | N/A | Proves domain ownership via HTTP endpoint | spec.acme.solvers[].http01.ingress |
| DNS-01 solver | solver type | N/A | Proves domain ownership via TXT record | spec.acme.solvers[].dns01.<provider> |
| Staging endpoint | Let's Encrypt | N/A | Untrusted certs, high rate limits | acme-staging-v02.api.letsencrypt.org/directory |
| Production endpoint | Let's Encrypt | N/A | Trusted certs, strict rate limits | acme-v02.api.letsencrypt.org/directory |
| Ingress annotation | cert-manager.io/cluster-issuer | Ingress | Auto-creates Certificate from Ingress TLS section | Triggers ingress-shim |
| Ingress annotation | cert-manager.io/issuer | Ingress | Same, references namespaced Issuer | Must be in same namespace |
| Renewal window | cert-manager default | N/A | Renews 30 days before expiry (LE certs last 90 days) | Configurable via spec.renewBefore |
START: Need TLS certificates on Kubernetes?
├── Single namespace only?
│ ├── YES → Use Issuer (namespace-scoped)
│ └── NO → Use ClusterIssuer (cluster-wide)
├── Need wildcard certificate (*.example.com)?
│ ├── YES → MUST use DNS-01 challenge
│ │ ├── DNS provider supported in-tree?
│ │ │ ├── YES → Configure dns01 solver with provider credentials
│ │ │ └── NO → Use webhook solver from community
│ │ └── Store provider API credentials in a Kubernetes Secret
│ └── NO ↓
├── Cluster publicly accessible on port 80?
│ ├── YES → Use HTTP-01 challenge (simplest setup)
│ └── NO → Use DNS-01 challenge (works behind firewalls/NAT)
├── Want automatic Certificate creation from Ingress?
│ ├── YES → Add cert-manager.io/cluster-issuer annotation to Ingress
│ └── NO → Create Certificate resource manually
└── DEFAULT → Start with staging endpoint, switch to production after validation
Install cert-manager and its CRDs into the cluster. Helm is recommended for production; kubectl apply is simpler for quick setups. [src1]
# Option A: Helm (recommended for production)
helm repo add jetstack https://charts.jetstack.io --force-update
helm install cert-manager jetstack/cert-manager \
--namespace cert-manager \
--create-namespace \
--version v1.17.2 \
--set crds.enabled=true
# Option B: kubectl apply (quick setup)
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.17.2/cert-manager.yaml
Verify: kubectl get pods -n cert-manager -- all three Pods should be Running
Always start with staging to avoid rate limits. Staging issues untrusted certificates but has much higher rate limits. [src1]
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-staging
spec:
acme:
email: [email protected]
server: https://acme-staging-v02.api.letsencrypt.org/directory
privateKeySecretRef:
name: letsencrypt-staging-account-key
solvers:
- http01:
ingress:
ingressClassName: nginx
Verify: kubectl get clusterissuer letsencrypt-staging -o wide -- READY should be True
Switch to production once staging works. This endpoint issues browser-trusted certificates. [src1]
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
email: [email protected]
server: https://acme-v02.api.letsencrypt.org/directory
privateKeySecretRef:
name: letsencrypt-prod-account-key
solvers:
- http01:
ingress:
ingressClassName: nginx
Verify: kubectl describe clusterissuer letsencrypt-prod -- Status should show The ACME account was registered with the ACME server
The ingress-shim sub-component watches for annotated Ingress resources and automatically creates Certificate resources. [src3]
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-app-ingress
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: my-app-service
port:
number: 80
Verify: kubectl get certificate -n default -- READY should become True within 1-2 minutes
Wildcard certificates require DNS-01 validation. Create a Secret with DNS provider API credentials, then configure the solver. [src4]
apiVersion: v1
kind: Secret
metadata:
name: cloudflare-api-token
namespace: cert-manager
type: Opaque
stringData:
api-token: "your-cloudflare-api-token-here"
---
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod-dns
spec:
acme:
email: [email protected]
server: https://acme-v02.api.letsencrypt.org/directory
privateKeySecretRef:
name: letsencrypt-prod-dns-account-key
solvers:
- dns01:
cloudflare:
apiTokenSecretRef:
name: cloudflare-api-token
key: api-token
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: wildcard-example-com
namespace: default
spec:
secretName: wildcard-example-com-tls
issuerRef:
name: letsencrypt-prod-dns
kind: ClusterIssuer
dnsNames:
- "*.example.com"
- "example.com"
Verify: kubectl get challenges -- should show challenges progressing from pending to valid
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod-mixed
spec:
acme:
email: [email protected]
server: https://acme-v02.api.letsencrypt.org/directory
privateKeySecretRef:
name: letsencrypt-prod-mixed-key
solvers:
- selector:
dnsNames:
- "*.example.com"
dns01:
cloudflare:
apiTokenSecretRef:
name: cloudflare-api-token
key: api-token
- selector: {}
http01:
ingress:
ingressClassName: nginx
# Create CNAME: _acme-challenge.example.com -> _acme-challenge.acme.delegated.com
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod-delegated
spec:
acme:
email: [email protected]
server: https://acme-v02.api.letsencrypt.org/directory
privateKeySecretRef:
name: letsencrypt-prod-delegated-key
solvers:
- dns01:
cnameStrategy: Follow
cloudflare:
apiTokenSecretRef:
name: cloudflare-api-token
key: api-token
Full script: setup-cert-manager.sh (37 lines)
# BAD -- hitting production endpoint during testing
# will exhaust rate limits (50 certs/domain/week)
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
# GOOD -- use staging first
spec:
acme:
server: https://acme-staging-v02.api.letsencrypt.org/directory
# BAD -- HTTP-01 CANNOT validate wildcard domains
dnsNames:
- "*.example.com" # Will never be issued via HTTP-01
# GOOD -- wildcard requires DNS-01
solvers:
- dns01:
cloudflare:
apiTokenSecretRef:
name: cloudflare-api-token
key: api-token
# BAD -- annotation present but no tls section
# cert-manager's ingress-shim silently skips this
metadata:
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
rules:
- host: app.example.com
# Missing tls section entirely!
# GOOD -- tls section with secretName triggers cert-manager
spec:
tls:
- hosts:
- app.example.com
secretName: app-example-com-tls
rules:
- host: app.example.com
kubectl describe challenge <name> to see the specific error. [src2]ingressClassName matches your controller, or use acme.cert-manager.io/http01-edit-in-place: "true". [src2]--dns01-recursive-nameservers=8.8.8.8:53,1.1.1.1:53 and --dns01-recursive-nameservers-only. [src4]cert-manager.io/issue-temporary-certificate: "true" annotation. [src2]kubectl delete secret <name> -n cert-manager and re-apply. [src2]kubectl get pods -n cert-manager and check logs. [src2]tls section or empty secretName. Fix: ensure every tls entry has a secretName and hosts. [src3]# Check cert-manager pods are running
kubectl get pods -n cert-manager
# Check ClusterIssuer/Issuer status
kubectl get clusterissuer
kubectl describe clusterissuer letsencrypt-prod
# List all certificates and their READY status
kubectl get certificates --all-namespaces
# Follow issuance chain: Certificate -> CertificateRequest -> Order -> Challenge
kubectl describe certificate <name> -n <namespace>
kubectl get certificaterequest -n <namespace>
kubectl describe order <name> -n <namespace>
kubectl get challenges --all-namespaces
kubectl describe challenge <name> -n <namespace>
# Check TLS secret was created and inspect certificate
kubectl get secret <secretName> -n <namespace> -o jsonpath='{.data.tls\.crt}' | base64 -d | openssl x509 -noout -text
# View cert-manager controller logs
kubectl logs -n cert-manager deploy/cert-manager --tail=100
# Test DNS-01 TXT record propagation
dig _acme-challenge.example.com TXT +short
| Version | Status | Release Date | K8s Support | Breaking Changes |
|---|---|---|---|---|
| v1.20 | Upcoming | Feb 24, 2026 | 1.32-1.35 | TBD |
| v1.19 | Current | Oct 07, 2025 | 1.31-1.35 | None major |
| v1.18 | Supported | Jun 10, 2025 | 1.29-1.33 | revisionHistoryLimit defaults to 1; stale CertificateRequests garbage-collected |
| v1.17 | EOL | ~Feb 2025 | 1.28-1.31 | Structured logging; ValidateCAA deprecated |
| v1.16 | EOL | ~Oct 2024 | 1.27-1.30 | Last version on OperatorHub |
| Use When | Don't Use When | Use Instead |
|---|---|---|
| You need automatic TLS certificates on Kubernetes | You are not running Kubernetes | Certbot standalone or ACME client |
| You want Let's Encrypt auto-renewal (90-day certs) | You have enterprise CA with manual distribution | Your CA's certificate management tool |
| You manage multiple domains/services in a cluster | Single static site with manual cert management | Cloudflare SSL or hosting provider built-in TLS |
| You need wildcard certificates with DNS-01 | Cluster has no outbound internet access | Internal CA (Vault PKI, step-ca) |
| You want annotation-driven TLS on Ingress | Using Gateway API with built-in cert management | Gateway API + TLS passthrough |
revisionHistoryLimit explicitly if you need historical data.privateKeySecretRef secret is your account identity with Let's Encrypt. Back it up for cluster migrations.--filterwin2k flag (common in OpenWRT) drops SOA records, breaking cert-manager DNS zone detection.