Table of Contents
Deploy
...
Understanding
Cert-manager’s certificates controller watches this CRD. When it sees a new Certificate, it begins the issuing workflow. Internally, cert-manager converts your Certificate into a more specific CertificateRequest object, representing a single signing operation. This object asks the issuer (Let’s Encrypt, via cert-manager’s ACME client) to sign a CSR.
Then cert-manager’s ACME client (in the controller) contacts Let’s Encrypt. It connects to the ACME server defined in your ClusterIssuer.
server: https://acme-v02.api.letsencrypt.org/directory
Cert-manager models ACME challenges as Kubernetes resources:
- Order — represents Let’s Encrypt’s pending order for one or more domain names.
- Challenge — represents the actual proof task. For HTTP-01 method, place a file at a URL.
The Challenge controller inside cert-manager now deploys a temporary HTTP solver stack:
- Pod — a minimal web server serving the challenge token from the
.well-known/acme-challenge/path. - Service — exposing that pod on port 8089 (ClusterIP or NodePort).
- Ingress — routing
http://<your-domain>/.well-known/acme-challenge/<token>to that Service.
After the Ingress and pod are ready, cert-manager performs a self-check:
GET http://domain.com/.well-known/acme-challenge/<token>
If it gets 200 OK and the correct token content, it marks the challenge ready. Let’s Encrypt then performs the same HTTP GET request from their own infrastructure. If they see the same 200 OK response, they mark the challenge as valid.
Once Let’s Encrypt reports the challenge valid, cert-manager automatically finalizes the ACME order:
- Sends the CSR from the CertificateRequest.
- Receives the signed certificate chain.
- Writes the resulting PEM files into the Kubernetes Secret you defined.
Once the certificate is issued, cert-manager automatically deletes:
- The Challenge resource
- The temporary solver Ingress, Service, and Pod
- Leaving only the Certificate, CertificateRequest, Order, and the resulting TLS Secret.
DNS-01
...
HTTP-01
This method is good when you do not own a domain.
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-http
spec:
acme:
email: you@example.com
server: https://acme-v02.api.letsencrypt.org/directory
privateKeySecretRef:
name: letsencrypt-http-key
solvers:
- http01:
ingress:
class: nginx
Certificate:
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: foreign-domain-cert
namespace: app-dev # The same namespace where your application ingress declaration was created.
spec:
secretName: foreign-domain-tls
duration: 2160h
renewBefore: 360h
dnsNames:
- example.com
- '*.example.com'
issuerRef:
name: letsencrypt-http
kind: ClusterIssuer
Debugging HTTP-01
kubectl get challenge -A
kubectl get certificate -A
kubectl get ingressclass
kubectl describe certificate <CERTIFICATE_NAME> -n <NAMESPACE>
Verify DNS resolution:
host domain.com
Search for cm-acme-http-solver-XXX solver ingress and services:
kubectl -n <NAMESPACE> get ingress
kubectl -n <NAMESPACE> get svc
kubectl -n <NAMESPACE> get ingress cm-acme-http-solver-XXX -o yaml
kubectl -n <NAMESPACE> get endpoints cm-acme-http-solver-XXX -o yaml
kubectl -n <NAMESPACE> get pods -l acme.cert-manager.io/http01-solver=true -o wide
Check cert manager logs:
kubectl -n cert-manager logs -f <CERT_MANAGER_POD>