k8s-cert-manager-troubleshooting · git:20261024.482781a · 2026-10-24 · sha256 c98fe3ca0d53975b
k8s-cert-manager-troubleshooting git:20261024.482781aA
Immutable. This exact content never changes and is served at /api/v1/blob/c98fe3ca0d53975b.
---
name: k8s-cert-manager-troubleshooting
description: >-
Use when cert-manager does not issue or renew a certificate in Kubernetes: a
Certificate that stays READY False, a CertificateRequest that is Pending, Denied or
Failed, an Issuer or ClusterIssuer that is not Ready ("Error getting keypair for CA
issuer"), "Referenced \"Issuer\" not found", ACME Orders or Challenges stuck pending
(HTTP-01, DNS-01, rate limits), a certificate that expired or did not renew, or
"failed calling webhook \"webhook.cert-manager.io\"" when applying resources.
Investigates with kubectl get, describe, events and logs, and proposes fixes. Reads
first and asks before changing anything.
license: Apache-2.0
metadata:
verified-with: "kubernetes 1.37, cert-manager 1.21"
verified-on: "2026-10-24"
---
# cert-manager troubleshooting
Find out why cert-manager does not issue or renew a certificate, and propose a fix.
**Read first, ask before changing.** Run the investigation commands freely. Run a
command under an "Ask first" line only after the user approves that exact command; if
your credentials do not allow it, give it to the user to run. Never run a "Proposed
fix"; hand it to the user.
cert-manager works in a chain. A **Certificate** says what is wanted. cert-manager
creates a **CertificateRequest** for each issuance, which an **Issuer** (one namespace)
or **ClusterIssuer** (whole cluster) signs. For ACME issuers (Let's Encrypt and others)
the request also gets an **Order** with one **Challenge** per domain. Follow the chain
from the Certificate and stop at the first object that is not Ready.
The commands use placeholders: `my-namespace`, `my-app-tls` (the Certificate),
`my-issuer` and `my-cluster-issuer`. Replace them with the real names. The `cert-manager`
namespace and Deployment names are those of the default install; a Helm install may use
other names (find them with the first command of step 5).
Never read Secrets: not the certificate's Secret, not the key pair of a CA issuer, not
an ACME account key, not DNS provider credentials. The status of the cert-manager
resources says whether a Secret is missing or wrong. For the same reason do not use
`cmctl status certificate` or `cmctl inspect secret`: both read the certificate's Secret.
## When to use
- A Certificate shows `READY False`, or an Ingress or Gateway still serves an old or
self-signed certificate.
- A CertificateRequest is Pending, Denied or Failed; an Order or Challenge stays pending.
- An Issuer or ClusterIssuer is not Ready.
- A certificate expired or did not renew.
- Creating or changing any cert-manager resource fails with
`failed calling webhook "webhook.cert-manager.io"`.
Hand-offs, by skill name. When the evidence points elsewhere, continue with that skill;
if it is not installed, keep going with the steps here and tell the user which skill
would help:
- The cert-manager pods themselves crash or stay Pending: `k8s-workload-troubleshooting`.
- An HTTP-01 challenge cannot be reached through the Ingress, Gateway or Service, or a
NetworkPolicy blocks it: `k8s-network-troubleshooting`.
## Investigate
Work through the steps in order. Stop when you have the cause and its evidence.
**1. See the Certificate**: Ready, the issuer it points at, and its conditions and
events. `Issuing` `True` means an issuance is in progress.
```bash
kubectl get certificate -n my-namespace
kubectl describe certificate my-app-tls -n my-namespace
kubectl get certificate my-app-tls -n my-namespace -o jsonpath='{.spec.issuerRef}{"\n"}{.status.conditions}{"\n"}'
```
**2. See its CertificateRequests.** The newest one says why issuance waits or failed:
the `Ready` condition's reason and message, and `Approved` or `Denied`.
```bash
kubectl get certificaterequest -n my-namespace
kubectl describe certificaterequest -n my-namespace
kubectl events -n my-namespace
```
**3. See the issuer** the request names. An Issuer lives in the Certificate's
namespace; a ClusterIssuer has no namespace. The built-in `view` role cannot read
ClusterIssuers; if the command is forbidden, say so, and read the issuer's state from
the CertificateRequest message ("Referenced issuer does not have a Ready status
condition" means it exists and is not Ready).
```bash
kubectl get issuer -n my-namespace
kubectl describe issuer my-issuer -n my-namespace
kubectl get clusterissuer
kubectl describe clusterissuer my-cluster-issuer
```
**4. For an ACME issuer, see the Order and the Challenges.** A Challenge's `State` and
`Reason` say what the ACME server or cert-manager's own self-check saw.
```bash
kubectl get orders -n my-namespace
kubectl describe order -n my-namespace
kubectl get challenges -n my-namespace
kubectl describe challenge -n my-namespace
```
**5. If nothing progresses, or creating a resource fails, check cert-manager itself**:
its three Deployments, the webhook's endpoints and the controller's logs.
```bash
kubectl get deployments -A -l app.kubernetes.io/instance=cert-manager
kubectl get pods -n cert-manager -o wide
kubectl get endpointslices -n cert-manager -l kubernetes.io/service-name=cert-manager-webhook
kubectl logs -n cert-manager deployment/cert-manager --tail=100 | grep my-app-tls
kubectl logs -n cert-manager deployment/cert-manager-webhook --tail=50
```
**6. Open the matching reference** and follow its steps:
| What you see | Reference |
|---|---|
| `IssuerNotFound`, `IssuerNotReady`, `ErrGetKeyPair`, `Denied`, `Failed`, a request that never comes | [references/issuers-and-requests.md](references/issuers-and-requests.md) |
| Order or Challenge `pending`, `invalid` or `errored`, HTTP-01 or DNS-01 self-check errors, rate limits | [references/acme.md](references/acme.md) |
| `failed calling webhook "webhook.cert-manager.io"`, `x509` errors from the webhook, cainjector | [references/webhook.md](references/webhook.md) |
| Expired or not renewed, `Renewing`, a Certificate that reissues again and again | [references/renewal.md](references/renewal.md) |
## Read the results
What the common findings mean (cert-manager 1.21):
- **`Issuing certificate as Secret does not exist`** on a new Certificate is the normal
first step, not the cause. Look one step down the chain: the CertificateRequest.
- **`Referenced "Issuer" not found: issuer.cert-manager.io "<name>" not found`** (reason
`Pending`, event `IssuerNotFound`): no Issuer of that name in the Certificate's
namespace. `issuerRef.kind` defaults to `Issuer`, so a Certificate meant for a
ClusterIssuer that leaves out `kind: ClusterIssuer` gets this message. Compare
`spec.issuerRef` with the issuers that exist.
- **`Referenced issuer does not have a Ready status condition`** (event
`IssuerNotReady`): the issuer exists but is not Ready. Its own condition says why.
- **A CA Issuer with `Error getting keypair for CA issuer: secrets "<name>" not found`**
(reason `ErrGetKeyPair`): the Secret named in `spec.ca.secretName` does not exist in
the issuer's namespace (for a ClusterIssuer, in the cluster resource namespace, by
default `cert-manager`). Confirm with the user rather than reading Secrets.
- **`Denied`**: an approval policy (for example approver-policy) refused the request;
cert-manager's own approver only approves. Fix the policy or the request.
- **A failed request** is retried with a backoff that starts at one hour, so a fixed
cause can look unfixed for a while. `Issuing` `False` with "has failed to complete and
will be retried" says so.
- **`failed calling webhook "webhook.cert-manager.io"`** on create or update: the API
server cannot reach cert-manager's webhook. Nothing in that namespace is wrong yet;
see [references/webhook.md](references/webhook.md).
State the cause in one sentence, then the evidence you saw (the condition reason and
message, the issuerRef, the issuer's state), then the proposed fix.
## Proposed fix
Show each fix to the user before running it: what it changes and how to undo it.
Patching a Certificate's `issuerRef` and scaling a cert-manager Deployment are
reversible, so they go under "Ask first" and run only after the user says yes. Creating
or changing Secrets, issuers and webhook configurations, and deleting resources, are
never run by you: hand them to the user as proposed fixes and say why. Prefer fixing
the source the resources are deployed from (manifest, Helm values, GitOps repository):
the next sync would undo a patch. Write each undo with the values you found.
Point a Certificate at the ClusterIssuer it was meant to use:
> Ask first: the agent runs this only after the user approves it.
```bash
kubectl patch certificate my-app-tls -n my-namespace --type=merge -p '{"spec": {"issuerRef": {"kind": "ClusterIssuer"}}}'
# Undo: kubectl patch certificate my-app-tls -n my-namespace --type=json -p '[{"op": "remove", "path": "/spec/issuerRef/kind"}]'
```
cert-manager replaces the waiting CertificateRequest with a new one right after the
change.
Bring back a webhook that was scaled to zero (write the undo with the replicas you
found):
> Ask first: the agent runs this only after the user approves it.
```bash
kubectl scale deployment/cert-manager-webhook -n cert-manager --replicas=1
# Undo: kubectl scale deployment/cert-manager-webhook -n cert-manager --replicas=0
```
A CA Issuer whose key pair is missing needs that Secret created from the CA's
certificate and key, in the issuer's namespace, under the name in `spec.ca.secretName`.
Only the user has the CA files, and you never handle Secrets, so the user creates it
with their usual tooling; or, if the issuer names the wrong Secret, the user fixes
`spec.ca.secretName` in the source. cert-manager retries the issuer by itself once the
Secret exists.
## Sources
Checked against the cert-manager 1.21 documentation and source (v1.21.3):
- [Troubleshooting](https://cert-manager.io/docs/troubleshooting/) and
[Troubleshooting ACME](https://cert-manager.io/docs/troubleshooting/acme/): the
resource chain and the commands to follow it.
- [Troubleshooting the webhook](https://cert-manager.io/docs/troubleshooting/webhook/):
the `failed calling webhook` errors and their causes.
- [Certificate resource](https://cert-manager.io/docs/usage/certificate/),
[CertificateRequest](https://cert-manager.io/docs/usage/certificaterequest/) and
[Issuer configuration](https://cert-manager.io/docs/configuration/): `issuerRef`,
conditions, approval, renewal.
- [CA issuer](https://cert-manager.io/docs/configuration/ca/): `spec.ca.secretName` and
the cluster resource namespace.
- [Release notes 1.18 to 1.21](https://cert-manager.io/docs/releases/): renewal policy,
HTTP-01 path type, retry backoff.
- Condition reasons and messages from the source:
[certificaterequests/sync.go](https://github.com/cert-manager/cert-manager/blob/v1.21.3/pkg/controller/certificaterequests/sync.go),
[issuer/ca/setup.go](https://github.com/cert-manager/cert-manager/blob/v1.21.3/pkg/issuer/ca/setup.go),
[policies/checks.go](https://github.com/cert-manager/cert-manager/blob/v1.21.3/internal/controller/certificates/policies/checks.go).
Messages quoted here were also seen on a Kubernetes 1.37 kind cluster with
cert-manager 1.21.3.
- `cmctl status certificate` reads the certificate's Secret
([certificate.go](https://github.com/cert-manager/cmctl/blob/v2.6.1/pkg/status/certificate/certificate.go)),
which is why this skill does not use it.