cert-manager Troubleshooting: Orders, Challenges, and Renewal Failures

Updated on Sep 5, 2026
Mila H
9 MINS READ
Table of Contents
cert-manager Troubleshooting

If your TLS certificate is stuck, expired, or never issued, the fix is hiding inside one Kubernetes resource. Such as the Certificate, the CertificateRequest, the Order, or the Challenge. This guide provides cert-manager troubleshooting for ACME challenge problems step by step, using the actual resource states and events.

If you have not set up cert-manager yet, you can follow our cert-manager setup guide for K3s.

How cert-manager Issues a Certificate

Before you fix anything, you need to know the chain of objects cert-manager creates. This is the map you will use for the rest of this cert-manager troubleshooting guide.

  1. You create a Certificate resource, or an Ingress annotation creates one for you.
  2. cert-manager creates a CertificateRequest from that Certificate.
  3. For ACME issuers like Let's Encrypt, the CertificateRequest creates an Order.
  4. The Order creates one Challenge per domain name, HTTP-01 or DNS-01.
  5. When the Challenge passes, the Order finalizes, and a signed certificate is saved into a Kubernetes Secret.

If a certificate is stuck, you can follow this chain top to bottom with kubectl describe until you find the object with the error.

Check the Certificate Object First

You must start every diagnosis session at the top of the resource chain, with the Certificate object itself. For this purpose, run:

Bash
kubectl get certificate -A

In the output, look at the READY column. If it says False, describe it to see the conditions and events:

Bash
kubectl describe certificate <cert-name> -n <namespace>

Common conditions you will see include:

  • Issuing=True: A new certificate is being requested right now.
  • Ready=False, Reason=DoesNotExist: No Secret exists yet; check the CertificateRequest.
  • Ready=False, Reason=Expired: The old certificate expired, and renewal failed; check the Order and Challenge.

Also, you can use cmctl for a full status chain in one command:

Bash
cmctl status certificate <cert-name> -n <namespace>

This prints the Certificate, its CertificateRequest, Order, and Challenge together. It saves you from running four separate kubectl describe commands.

CertificateRequest Stuck or Denied

If the Certificate is not ready, you must check the CertificateRequest it created. To do this, run:

Bash
kubectl get certificaterequest -n <namespace>kubectl describe certificaterequest <name> -n <namespace>

Look at the conditions field for these common problems:

  • Denied=True: An approver, like an Approval policy, rejected the request. Check your RBAC and approval policies.
  • InvalidRequest=True: The certificate signer refused the request because of a bad private key type or an unsupported field. Read the message text in the event.
  • No CertificateRequest at all: This means the Issuer or ClusterIssuer referenced in the Certificate spec does not exist or has a typo in its name.

If you deleted a CertificateRequest by mistake, cert-manager will create a new one, because the Certificate resource is the source of truth.

Order Resource Failures

An Order is only created when you use an ACME issuer, like Let's Encrypt. Most DNS and network problems show up right here, so this is a key step in cert-manager troubleshooting ACME challenge work. Run the commands below:

Bash
kubectl get order -n <namespace>kubectl describe order <order-name> -n <namespace>

Watch the State field:

  • pending: Waiting on Challenges to complete.
  • errored: The ACME server rejected the order due to a bad domain name or account problem.
  • invalid: One or more Challenges failed permanently.

If the state is stuck on pending for more than a few minutes, move to the Challenge resource in the next step.

Challenge Resource Failures: HTTP-01 and DNS-01

This is the most important step in cert-manager troubleshooting. Almost every "certificate never issued" problem comes back to this point.

Bash
kubectl get challenge -n <namespace>kubectl describe challenge <challenge-name> -n <namespace>

The Reason field in the events tells you exactly what Let's Encrypt saw when it tried to validate your domain.

HTTP-01 Reachability Checks

For HTTP-01, Let's Encrypt makes an HTTP request to http://<your-domain>/.well-known/acme-challenge/<token> on port 80. If this fails, check:

Bash
# Confirm the challenge path answers from the public internetcurl -v http://<your-domain>/.well-known/acme-challenge/<token> # Confirm DNS actually points to your ingress/load balancer IPdig +short <your-domain> # Check the ACME solver pod cert-manager set up temporarilykubectl get pods -n <namespace> | grep acme

Common causes include a firewall blocking port 80, a CDN like Cloudflare proxying traffic before the challenge pod can respond, or an Ingress controller that is not routing the /.well-known/acme-challenge/ path correctly.

If you use Cloudflare, temporarily set the DNS record to "DNS only" (grey cloud) while the challenge runs, or switch to DNS-01 instead.

DNS-01 Propagation Checks

For DNS-01, cert-manager creates a TXT record and waits for it to be visible everywhere before telling Let's Encrypt to check it.

Bash
# Check the TXT record from a public resolverdig +short TXT _acme-challenge.<your-domain> @8.8.8.8dig +short TXT _acme-challenge.<your-domain> @1.1.1.1 # Check cert-manager's own self-check logskubectl logs -n cert-manager deploy/cert-manager -f | grep -i dns

If the TXT record does not appear, you must check two things. Your DNS API token needs edit access, not just read, and the domain in your Issuer must match your real DNS zone. cert-manager retries for a few minutes on its own. If it still fails after 10 minutes, the token is usually the problem.

Webhook Connectivity Errors

Some cert-manager problems have nothing to do with ACME. Instead, they come from the internal webhook, which is a component that checks cert-manager's own resources.

You will see an error like Internal error occurred: failed calling webhook "webhook.cert-manager.io" or context deadline exceeded.

First, confirm the webhook Pod is running with the command below:

Bash
kubectl get pods -n cert-manager -l app=webhook

Then run the command below to test if the API server can actually reach it:

Bash
kubectl -n cert-manager port-forward deploy/cert-manager-webhook 10250# In a second terminalcurl -k https://localhost:10250/healthz

If you get x509: certificate signed by unknown authority, the problem is the caBundle in the webhook configuration. You must check that cainjector is running and has no errors, then confirm the CA bundle matches:

Bash
kubectl get validatingwebhookconfigurations cert-manager-webhook -o yaml | grep caBundlekubectl -n cert-manager get secret cert-manager-webhook-ca -o yaml

A timeout error, context deadline exceeded, means a network problem. This happens in clusters with strict network policies or in private GKE clusters that block traffic from the API server to Pods on certain ports. As a temporary fix, raise webhook.timeoutSeconds to 30 in your Helm values while you sort out the network issue.

Rate Limit Errors from Let's Encrypt

If your Order or Challenge event shows too many certificates already issued or too many new orders recently, you have hit a Let's Encrypt rate limit. As of the latest published limits:

  • New orders per account: 300 every 3 hours.
  • Certificates per registered domain: 50 every 7 days.
  • Certificates for the same set of hostnames: 5 every 7 days.
  • Authorization failures per identifier per account: 5 per hour.

Canceling a certificate does not free up your limit; it's already used. Two easy fixes include using Let's Encrypt's staging URL (https://acme-staging-v02.api.letsencrypt.org/directory) while testing, then switch to production once it works. Also, avoid deleting and recreating the same Certificate again and again, since each try uses up your weekly quota.

Wrong or Misconfigured Issuer

Many cert-manager troubleshooting tickets turn out to be a simple mismatch between the Certificate and its Issuer. Run the commands below:

Bash
kubectl get issuer -n <namespace>kubectl get clusterissuerkubectl describe clusterissuer <name>

Check three things, including the kind field in the Certificate spec matches the Issuer or ClusterIssuer correctly, the name is spelled right, and the Issuer itself shows Ready=True in its own status. 

An Issuer stuck at Ready=False means a bad ACME account registration or an unreachable DNS provider API.

Secret Ownership and Failed Renewals

Sometimes the Certificate and Challenge look healthy, but the Secret never updates, or a renewal silently fails. Run the following command:

Bash
kubectl get secret <secret-name> -n <namespace> -o yaml | grep -A5 annotations

Look for the cert-manager.io/certificate-name label on the Secret. If it's missing or wrong, another tool or a manual kubectl apply overwrote it. By default, cert-manager won't touch a Secret it doesn't own.

To fix this, delete the bad Secret and let cert-manager make a new one, or set secretTemplate ownership in the Certificate spec.

To force an immediate renewal instead of waiting for the scheduled time, you can run:

Bash
cmctl renew <cert-name> -n <namespace>

If renewal keeps failing after that, delete the CertificateRequest to force a clean retry:

Bash
kubectl delete certificaterequest <name> -n <namespace>

Useful cmctl Diagnostic Commands

If you have not already installed cmctl, you can use the commands below to install the latest cmctl binary:

Bash
CMCTL_VERSION=$(curl -s https://api.github.com/repos/cert-manager/cmctl/releases/latest | grep '"tag_name"' | cut -d'"' -f4)ARCH=$(dpkg --print-architecture)curl -fsSL "https://github.com/cert-manager/cmctl/releases/download/${CMCTL_VERSION}/cmctl_linux_${ARCH}" -o /tmp/cmctlsudo install -o root -g root -m 0755 /tmp/cmctl /usr/local/bin/cmctlcmctl version

Here are some useful cmctl diagnostic commands that you can use for cert-manager troubleshooting:

Bash
cmctl check api                              # confirm cert-manager API is reachablecmctl status certificate <name> -n <ns>      # full chain: Certificate, CertificateRequest, Order, Challengecmctl renew <name> -n <ns>                   # force renewal nowcmctl x509 view-secret <secret-name> -n <ns> # inspect the actual cert in a Secret

Upgrading to the Latest Stable cert-manager

If you suspect your cert-manager version itself is the problem, you can upgrade to the current stable release, which is currently v1.21.1:

Bash
helm repo add jetstack https://charts.jetstack.io --force-update helm upgrade --install \  cert-manager oci://quay.io/jetstack/charts/cert-manager \  --version v1.21.1 \  --namespace cert-manager \  --create-namespace \  --set crds.enabled=true

After upgrading, confirm every Pod is healthy before testing new certificates:

Bash
kubectl get pods -n cert-managercmctl check api

Conclusion

Certificate problems aren't random. Each one sits at a clear point in the Certificate → CertificateRequest → Order → Challenge chain, and the events tell you what went wrong. Once you're comfortable with reading kubectl describe and cmctl status, cert-manager troubleshooting takes minutes. Keep DNS credentials updated, watch your Let's Encrypt limits, and check the webhook after any network change.

Also, unreliable infrastructure causes ACME failures that look like cert-manager bugs but are really connectivity problems. Keep your certificate automation on a stable Kubernetes node with reliable DNS and public connectivity, so HTTP-01 and DNS-01 checks pass on the first try.

For more detailed information on reading Certificate, CertificateRequest, and Issuer events, check the official cert-manager docs.

The Issuer or ClusterIssuer name in your Certificate spec is probably wrong or missing. Check that the name and kind match exactly.

A firewall, CDN proxy, or Ingress rule is blocking the /.well-known/acme-challenge/ path on port 80. Test it with curl directly.

Another process may have taken ownership of the Secret. Check the cert-manager.io/certificate-name annotation and delete the Secret to let cert-manager recreate it.

No, but it makes cert-manager diagnosis much faster than running separate kubectl describe commands for each resource.