# Fix a cert-manager Certificate that never becomes Ready

> A Gateway serves no HTTPS because its Certificate is stuck. Trace it to the CertificateRequest and the CA issuer, fix three faults and call it with curl.

Level: Advanced · Topic: Networking · About 18 minutes
Try it in the browser: https://k8wiz.com/playground/fix-a-cert-manager-certificate-that-is-not-ready

The playground is a simulated Kubernetes cluster that runs in the browser. kubectl output and errors follow the real ones.

## The situation

The platform team runs an internal CA with cert-manager. The `public` Gateway in `infra` has an HTTPS listener for `shop.internal.example` that reads its certificate from the Secret `shop-tls`. That Secret doesn't exist, because the Certificate that should create it has never become Ready.

Follow the chain cert-manager builds: Certificate, CertificateRequest, issuer. Three things are wrong along it. The last one only shows when you make the HTTPS request.

The CA bootstrap manifests are in the editor as `ca.yaml`. When everything works, call the shop with `curl --cacert`, never with `-k`.

## Objectives

1. Trace the Certificate to the reason it isn't issued (`kubectl describe certificaterequest -n infra`, then the issuer it names)
2. ClusterIssuer internal-ca is Ready
3. Certificate shop-tls is Ready, signed by internal-ca
4. The certificate is valid for shop.internal.example
5. Call the shop over HTTPS, trusting the internal CA (`curl --cacert ca.crt https://shop.internal.example/`, with `ca.crt` taken from the Secret)

## Hints

1. `kubectl describe certificate shop-tls` only says it is issuing. The detail is one level down: `kubectl get certificaterequest -n infra` and `kubectl describe certificaterequest shop-tls-1 -n infra` say `Referenced "Issuer" not found`.
2. `issuerRef.kind` defaults to `Issuer`, a namespaced kind. `internal-ca` is a ClusterIssuer: `kubectl patch certificate shop-tls --type=merge -p '{"spec":{"issuerRef":{"kind":"ClusterIssuer"}}}'`.
3. Now the request waits on the issuer. `kubectl describe clusterissuer internal-ca` says the Secret `internal-ca-keypair` is not found. A ClusterIssuer reads its Secrets from cert-manager's own namespace, `cert-manager`, and the CA Certificate in `ca.yaml` creates it in `infra`.
4. Change `namespace: infra` to `namespace: cert-manager` in `ca.yaml` and `kubectl apply -f ca.yaml`. The copy in `infra` can be deleted.
5. The Certificate becomes Ready, yet `curl` reports that no subject name matches. `kubectl get certificate shop-tls -o jsonpath='{.spec.dnsNames}'` shows `shop.example.internal`; the Gateway listens for `shop.internal.example`. Patch `spec.dnsNames` and cert-manager issues a new revision.
6. `kubectl get secret shop-tls -o jsonpath='{.data.ca\.crt}' | base64 -d > ca.crt`, then `curl --cacert ca.crt https://shop.internal.example/`.

## Solution

```bash
kubectl get certificates,certificaterequests -n infra
```
```bash
kubectl describe certificaterequest -n infra
```
```bash
kubectl patch certificate shop-tls --type=merge -p '{"spec":{"issuerRef":{"kind":"ClusterIssuer"}}}'
```
```bash
kubectl describe clusterissuer internal-ca
```
```bash
sed -i 's/namespace: infra/namespace: cert-manager/' ca.yaml
```
```bash
kubectl apply -f ca.yaml
```
```bash
kubectl patch certificate shop-tls --type=merge -p '{"spec":{"dnsNames":["shop.internal.example"]}}'
```
```bash
kubectl wait --for=condition=Ready certificate/shop-tls --timeout=60s
```
```bash
sleep 5
```
```bash
kubectl get secret shop-tls -o jsonpath='{.data.ca\.crt}' | base64 -d > ca.crt
```
```bash
curl --cacert ca.crt https://shop.internal.example/
```

## What it teaches

- cert-manager works through a chain: a Certificate creates a CertificateRequest, and the issuer signs it. The Certificate's status stays vague; the CertificateRequest and the issuer carry the real message.
- `issuerRef.kind` defaults to `Issuer`. Naming a ClusterIssuer without the kind looks for a namespaced Issuer that doesn't exist.
- A ClusterIssuer has no namespace, so it reads Secrets from the cluster resource namespace, which is `cert-manager` unless the controller was started with another one.
- The usual private CA setup is a self-signed ClusterIssuer, a CA Certificate with `isCA: true`, and a CA issuer that points at that Certificate's Secret.
- A Ready certificate can still be for the wrong name. Clients check the SANs against the host they call, and a private CA has to be handed to them, here from `ca.crt` in the Secret.
