# cert-manager with Let's Encrypt: Certificate, CertificateRequest, Order and Challenge

> Every object cert-manager creates to get a Let's Encrypt certificate through an HTTP-01 challenge: ClusterIssuer, Certificate, CertificateRequest with its CSR, ACME Order, Challenge, solver Pod and the final TLS Secret.

Interactive graph: https://k8wiz.com/graphs/cert-manager-acme-http01

You write two things: a ClusterIssuer that knows how to talk to Let's Encrypt, and an annotation on your Ingress. From there cert-manager creates a chain of objects, each owned by the one before it, until a signed certificate lands in a Secret.

Knowing that chain is how you debug a certificate that never becomes ready. Start at the Certificate, follow it down to the CertificateRequest, the Order and the Challenge, and the first one that isn't ready has the error in its status.

## Resources

### ClusterIssuer letsencrypt-prod

you write it, cert-manager.io/v1

Cluster-scoped, so Certificates in any namespace can use it. It names the ACME server, the email for expiry notices, the Secret for the account key, and how to solve challenges: HTTP-01 through Ingresses of class `traefik`.

### Secret letsencrypt-prod-account-key

created by a controller, v1, namespace cert-manager

The ACME account's private key. cert-manager generates it and registers the account the first time the ClusterIssuer is used. If it's deleted, cert-manager registers a new account on the next use, which is harmless.

### ACME server Let's Encrypt

outside the cluster

Issues the certificate once you prove you control the domain. For HTTP-01 it fetches `http://shop.example.com/.well-known/acme-challenge/<token>` over the public internet, on port 80.

### Ingress shop

you write it, networking.k8s.io/v1, namespace shop

Your app's Ingress. The `cert-manager.io/cluster-issuer` annotation tells cert-manager's ingress-shim to create a Certificate for every host listed under `tls`, stored in the named Secret.

### Certificate shop-tls

created by a controller, cert-manager.io/v1, namespace shop

Created by ingress-shim from the Ingress, and owned by it. You can also write Certificates yourself. It says which names you want, which issuer to use and which Secret to fill, and it renews the certificate when two thirds of its lifetime have passed.

### CertificateRequest shop-tls-1

created by a controller, cert-manager.io/v1, namespace shop

One per issuance, numbered by revision. `spec.request` holds a PEM certificate signing request (CSR) made from a new private key. It has to be approved before an issuer signs it, and cert-manager's built-in approver does that for its own issuers.

### Order shop-tls-1-2389721634

created by a controller, acme.cert-manager.io/v1, namespace shop

The ACME order for the names in the CSR. Let's Encrypt answers with one authorization per name, and cert-manager creates a Challenge for each. When they all pass, the Order is finalized with the CSR and the signed certificate comes back.

### Challenge shop-tls-1-2389721634-310…

created by a controller, acme.cert-manager.io/v1, namespace shop

One per domain. It records the token and key the ACME server expects to find, and its `status.reason` is where most failures show up, for example a 404 or a timeout from Let's Encrypt's side.

### Pod cm-acme-http-solver-x7k2p

created by a controller, v1, namespace shop

A tiny web server that answers the challenge. cert-manager also creates a Service and an Ingress of class `traefik` that route `/.well-known/acme-challenge/<token>` for `shop.example.com` to it, and deletes all three when the challenge is done.

### Secret shop-tls

created by a controller, v1, namespace shop

Written by cert-manager with `tls.crt` (the certificate and chain) and `tls.key`. The Ingress controller reads it to serve HTTPS. cert-manager updates it in place on every renewal, and the controller picks up the change.

## How the resources connect

- ClusterIssuer letsencrypt-prod → Secret letsencrypt-prod-account-key: privateKeySecretRef (references)
- ClusterIssuer letsencrypt-prod → ACME server Let's Encrypt: registers account (traffic)
- Ingress shop → ClusterIssuer letsencrypt-prod: cluster-issuer annotation (references)
- Ingress shop → Certificate shop-tls: ingress-shim creates (creates)
- Certificate shop-tls → ClusterIssuer letsencrypt-prod: issuerRef (references)
- Certificate shop-tls → CertificateRequest shop-tls-1: creates (CSR) (creates)
- CertificateRequest shop-tls-1 → Order shop-tls-1-2389721634: creates (creates)
- Order shop-tls-1-2389721634 → Challenge shop-tls-1-2389721634-310…: one per domain (creates)
- Order shop-tls-1-2389721634 → ACME server Let's Encrypt: new order, finalize (traffic)
- Challenge shop-tls-1-2389721634-310… → Pod cm-acme-http-solver-x7k2p: creates (creates)
- ACME server Let's Encrypt → Pod cm-acme-http-solver-x7k2p: GET /.well-known/acme-challenge (traffic)
- Certificate shop-tls → Secret shop-tls: writes tls.crt, tls.key (creates)
- Ingress shop → Secret shop-tls: tls.secretName (references)

## How it works

1. **The ClusterIssuer registers an ACME account.** When the ClusterIssuer is first used, cert-manager generates a private key, stores it in `letsencrypt-prod-account-key` and registers an account with Let's Encrypt. The issuer reports `Ready` once that works.
2. **The Ingress annotation creates a Certificate.** ingress-shim sees `cert-manager.io/cluster-issuer` on the Ingress and creates a Certificate named after the `tls.secretName`, for the hosts listed there. Delete the Ingress and the Certificate goes with it.
3. **The Certificate creates a CertificateRequest.** The Secret doesn't exist yet, so the Certificate needs issuing. cert-manager generates a private key, builds a CSR for `shop.example.com` and puts it in a CertificateRequest. The request is approved, then handed to the ACME issuer.
4. **The ACME issuer places an Order.** For an ACME issuer, the CertificateRequest becomes an Order. Let's Encrypt returns an authorization for the one name, and the Order creates one Challenge for it.
5. **The solver answers the challenge.** The Challenge starts a solver Pod and an Ingress that serves the token at `/.well-known/acme-challenge/`. cert-manager first checks that the URL works from inside the cluster, then tells Let's Encrypt to validate. Let's Encrypt fetches it over port 80.
6. **The certificate lands in the Secret.** With the challenge passed, the Order is finalized with the CSR and Let's Encrypt returns the signed certificate. cert-manager writes it and the key to `shop-tls`, the Certificate turns `Ready`, and the Ingress controller starts serving it. About 60 days later the same chain runs again for the renewal.

## Common mistakes

- Use the staging server, `https://acme-staging-v02.api.letsencrypt.org/directory`, while you get things working. Production has strict rate limits, such as 5 duplicate certificates per week for the same set of names.
- HTTP-01 needs port 80 open to the internet and DNS for the name already pointing at your load balancer. Behind a private load balancer, or for a wildcard name, use DNS-01 instead.
- A Certificate that never becomes ready says why somewhere down the chain. Run `kubectl describe certificate`, then `certificaterequest`, `order` and `challenge` in the same namespace. With the cmctl plugin, `kubectl cert-manager status certificate shop-tls -n shop` shows the whole chain at once.
- A ClusterIssuer and an Issuer are different kinds. An Ingress annotated with `cert-manager.io/issuer` looks for a namespaced Issuer, and fails if only a ClusterIssuer of that name exists.
- ingress-shim only acts on Ingresses with a `tls` section. The annotation alone, without `spec.tls` and a `secretName`, creates nothing.

## The manifests you write

```yaml
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: ops@example.com
    privateKeySecretRef:
      name: letsencrypt-prod-account-key
    solvers:
      - http01:
          ingress:
            ingressClassName: traefik
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: shop
  namespace: shop
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
  ingressClassName: traefik
  tls:
    - hosts: [shop.example.com]
      secretName: shop-tls
  rules:
    - host: shop.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: shop
                port:
                  number: 80
```
