cert-manager Certificate Not Ready: Causes and Fixes
cert-manager Certificate stuck in a non-Ready state is a common Kubernetes TLS issue. This guide covers every root cause — DNS challenges, RBAC, rate limits, and issuer problems — with step-by-step fixes.
You installed cert-manager, created a Certificate resource, and your pod is waiting for TLS to come up. But the Certificate stays False. The Ingress shows no TLS secret. Your HTTPS doesn't work.
This is one of the most frustrating Kubernetes issues because the failure chain is long: cert-manager → ACME challenge → DNS → Let's Encrypt → certificate. Any link can break silently.
This guide walks through every common cause and how to fix it.
Step 1: Understand the Chain
Before debugging, understand how cert-manager issues a certificate:
Certificate → CertificateRequest → Order → Challenge
│ │ │ │
▼ ▼ ▼ ▼
You create cert-manager ACME DNS-01 or
this creates this server HTTP-01
- You create a
Certificate(or cert-manager creates one via Ingress annotation) - cert-manager creates a
CertificateRequest - cert-manager creates an ACME
Orderwith Let's Encrypt - Let's Encrypt issues a
Challenge(HTTP-01 or DNS-01) - cert-manager satisfies the challenge
- Let's Encrypt issues the certificate
- cert-manager stores it as a Kubernetes Secret
Any step can fail. The key is knowing where.
Step 2: Run These Diagnostic Commands
Always run these in order:
# 1. Check the Certificate status
kubectl describe certificate <cert-name> -n <namespace>
# 2. Check the CertificateRequest
kubectl describe certificaterequest -n <namespace>
# 3. Check the Order (ACME)
kubectl describe order -n <namespace>
# 4. Check the Challenge
kubectl describe challenge -n <namespace>
# 5. Check cert-manager controller logs
kubectl logs -n cert-manager -l app=cert-manager --tail=100
# 6. Check cert-manager webhook logs
kubectl logs -n cert-manager -l app=webhook --tail=50The describe output on each resource has an Events or Status.Conditions section. Read it carefully — it points directly to the failure.
Cause 1: HTTP-01 Challenge Failing (Wrong Ingress or Network)
HTTP-01 is the most common challenge type. Let's Encrypt sends an HTTP request to http://your-domain.com/.well-known/acme-challenge/<token> and expects a specific response.
Symptoms
$ kubectl describe challenge
Status:
Presented: false
Reason: Waiting for HTTP-01 challenge propagation
State: pending
Events:
Warning PresentError 2m cert-manager Error presenting challenge:
service "cm-acme-http-solver-xxxxx" already exists
Or:
Error: 403 Forbidden. The value provided for the challenge was incorrect.
Diagnoses
Check if the solver pod is running:
kubectl get pods -n <namespace> | grep cm-acme-http-solverIt should show Running. If it's not there or it's in Pending, the challenge can't be served.
Check if the Ingress for the solver was created:
kubectl get ingress -n <namespace> | grep cm-acmeTest the challenge URL manually:
# Get the challenge token path
kubectl describe challenge -n <namespace>
# Look for: "Token: abc123..."
# Test from outside your cluster
curl http://your-domain.com/.well-known/acme-challenge/abc123Fixes
Fix 1: Ensure your Ingress allows HTTP traffic
cert-manager needs to serve HTTP on port 80. If you're redirecting all HTTP to HTTPS, the challenge fails.
# nginx ingress — allow HTTP for ACME challenge
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-app
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "false" # temporarily disable
cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
tls:
- hosts:
- myapp.example.com
secretName: myapp-tls
rules:
- host: myapp.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-app
port:
number: 80Fix 2: Check your Ingress class annotation
metadata:
annotations:
kubernetes.io/ingress.class: "nginx" # or your ingress class
cert-manager.io/cluster-issuer: "letsencrypt-prod"Fix 3: Verify DNS points to your cluster's external IP
# Check what IP your domain resolves to
nslookup myapp.example.com
# Check your LoadBalancer IP
kubectl get svc -n ingress-nginxThey must match.
Cause 2: DNS-01 Challenge Failing
For wildcard certificates (e.g., *.example.com), you must use DNS-01 challenges. cert-manager creates a TXT record in your DNS zone.
Symptoms
Events:
Warning PresentError 5m cert-manager Error presenting challenge:
Failed to create TXT record: AccessDenied
Fix: Check API credentials for your DNS provider
For Route53:
apiVersion: v1
kind: Secret
metadata:
name: route53-credentials
namespace: cert-manager
type: Opaque
stringData:
secret-access-key: "<your-aws-secret-key>"
---
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:
- dns01:
route53:
region: us-east-1
hostedZoneID: Z1234567890
accessKeyID: AKIAIOSFODNN7EXAMPLE
secretAccessKeySecretRef:
name: route53-credentials
key: secret-access-keyFor Cloudflare:
solvers:
- dns01:
cloudflare:
email: admin@example.com
apiTokenSecretRef:
name: cloudflare-api-token
key: api-tokenVerify cert-manager can reach the DNS API by checking logs:
kubectl logs -n cert-manager -l app=cert-manager | grep -i "dns01\|route53\|cloudflare"Cause 3: Wrong or Missing ClusterIssuer
Symptoms
Events:
Warning IssuerNotFound 2m cert-manager
Referenced ClusterIssuer "letsencrypt-prod" not found
Or the Issuer is in a different namespace:
Warning IssuerNotFound Referenced Issuer does not exist
Fix
Check your issuer exists:
kubectl get clusterissuer
kubectl get issuer -n <namespace>A correct ClusterIssuer for production:
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 # must be a real email
privateKeySecretRef:
name: letsencrypt-prod-account-key
solvers:
- http01:
ingress:
class: nginx # must match your ingress controller classCommon mistake: Using letsencrypt-staging in production (certificates aren't trusted by browsers).
| Issuer | URL | Trusted by browsers |
|---|---|---|
| staging | https://acme-staging-v02.api.letsencrypt.org/directory | No |
| prod | https://acme-v02.api.letsencrypt.org/directory | Yes |
Cause 4: Let's Encrypt Rate Limits
Let's Encrypt has strict rate limits. If you've been doing a lot of testing:
- 5 duplicate certificates per week
- 50 certificates per registered domain per week
- 5 failures per account per hostname per hour
Symptoms
Error: too many certificates already issued for exact set of domains
Error: too many failed authorizations recently
Fix
Switch to the staging issuer 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: admin@example.com
privateKeySecretRef:
name: letsencrypt-staging-key
solvers:
- http01:
ingress:
class: nginxUse staging until your setup works. Then switch to letsencrypt-prod. Staging issues unlimited certificates (but they're not browser-trusted, only for testing).
Cause 5: RBAC Issues
cert-manager needs RBAC permissions to create secrets, ingresses, and manage ACME resources.
Symptoms
Events:
Warning ErrInitIssuer cert-manager Error initializing issuer:
error getting secret: secrets "letsencrypt-prod-account-key" is forbidden:
User "system:serviceaccount:cert-manager:cert-manager" cannot get resource "secrets"
Fix
This usually means cert-manager wasn't installed with cluster-level permissions. Reinstall with Helm:
helm install cert-manager jetstack/cert-manager \
--namespace cert-manager \
--create-namespace \
--version v1.14.0 \
--set installCRDs=true \
--set global.leaderElection.namespace=cert-managerIf you installed manually, verify the ClusterRoleBinding exists:
kubectl get clusterrolebinding | grep cert-managerCause 6: Webhook Not Ready
cert-manager has a validating webhook. If it's not ready, certificate creation fails silently.
Symptoms
Error from server (InternalError): error when creating "certificate.yaml":
Internal error occurred: failed calling webhook "webhook.cert-manager.io":
Post "https://cert-manager-webhook.cert-manager.svc:443/...": dial tcp: ...
connection refused
Fix
Check the webhook pod:
kubectl get pods -n cert-manager
kubectl logs -n cert-manager -l app=webhook --tail=50If the webhook pod is unhealthy, restart it:
kubectl rollout restart deployment cert-manager-webhook -n cert-managerWait 30 seconds, then try creating the Certificate again.
Quick Reference: Debug Checklist
# 1. Certificate status
kubectl get certificate -n <namespace>
kubectl describe certificate <name> -n <namespace>
# 2. CertificateRequest
kubectl get certificaterequest -n <namespace>
kubectl describe certificaterequest <name> -n <namespace>
# 3. ACME Order
kubectl get order -n <namespace>
kubectl describe order <name> -n <namespace>
# 4. Challenge (the actual HTTP or DNS verification)
kubectl get challenge -n <namespace>
kubectl describe challenge <name> -n <namespace>
# 5. cert-manager logs
kubectl logs -n cert-manager deployment/cert-manager --tail=100
# 6. Verify TLS secret was created
kubectl get secret <secret-name> -n <namespace>
kubectl describe secret <secret-name> -n <namespace>
# 7. Check certificate details once issued
kubectl get secret <secret-name> -n <namespace> -o jsonpath='{.data.tls\.crt}' | \
base64 -d | openssl x509 -noout -dates -subjectFull Working Setup (Copy-Paste)
# 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 \
--version v1.14.0 \
--set installCRDs=true# 1. ClusterIssuer
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: your-email@example.com
privateKeySecretRef:
name: letsencrypt-prod-key
solvers:
- http01:
ingress:
class: nginx
---
# 2. Ingress with TLS 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:
ingressClassName: nginx
tls:
- hosts:
- myapp.example.com
secretName: myapp-tls-secret
rules:
- host: myapp.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-app
port:
number: 80cert-manager will automatically detect the annotation and issue a certificate. Check kubectl get certificate -n production after a few minutes.
Learn More
Want hands-on Kubernetes security labs covering cert-manager, Vault, Network Policies, and more? KodeKloud's Kubernetes security courses give you real cluster environments — not just documentation reading.
Stay ahead of the curve
Get the latest DevOps, Kubernetes, AWS, and AI/ML guides delivered straight to your inbox. No spam — just practical engineering content.
Related Articles
AWS EKS Pods Stuck in Pending State: Causes and Fixes
Pods stuck in Pending on EKS are caused by a handful of known issues — insufficient node capacity, taint mismatches, PVC problems, and more. Here's how to diagnose and fix each one.
AWS IAM Permission Denied Errors — How to Fix Every Variant (2026)
Getting 'Access Denied' or 'is not authorized to perform' errors in AWS? Here's how to diagnose and fix every IAM permission issue — EC2, EKS, Lambda, S3, and CLI.
Build a DevSecOps Pipeline with Trivy, SonarQube, and OPA from Scratch (2026)
Step-by-step project walkthrough: add security scanning, code quality gates, and policy enforcement to a GitHub Actions pipeline. Real configs, production-ready.