A TLS certificate is not just a lock icon; it’s an active, dynamic negotiation between two parties, and the challenge is keeping that negotiation fresh and secure without manual intervention.
Let’s watch cert-manager handle this. Imagine we have a simple Nginx deployment that needs TLS.
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-app
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
# This is where the magic will happen: we'll mount the TLS certs
volumeMounts:
- name: tls-certs
mountPath: "/etc/nginx/ssl"
readOnly: true
volumes:
- name: tls-certs
secret:
secretName: nginx-tls # This is the secret cert-manager will create
And the Nginx configuration to use those certs:
# nginx.conf (simplified for example)
server {
listen 443 ssl;
server_name localhost;
ssl_certificate /etc/nginx/ssl/tls.crt;
ssl_certificate_key /etc/nginx/ssl/tls.key;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
}
Now, how do we tell cert-manager to get us a certificate for nginx-app and put it in that nginx-tls secret? We use an Issuer or ClusterIssuer (for cluster-wide scope) and a Certificate resource.
# issuer.yaml
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-staging
spec:
acme:
server: https://acme-staging-v02.api.letsencrypt.org/directory
email: your-email@example.com
privateKeySecretRef:
name: letsencrypt-staging-private-key
solvers:
- http01:
ingress:
class: nginx # Assuming you have an ingress controller
This ClusterIssuer is configured to talk to Let’s Encrypt’s staging environment (so we don’t burn through rate limits) using the ACME protocol. It will use the HTTP01 challenge, which requires an ingress controller to respond to challenges.
# certificate.yaml
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: nginx-tls-cert
namespace: default # Must match the namespace of the deployment
spec:
secretName: nginx-tls # This is the secret name referenced in the deployment
dnsNames:
- nginx.example.com # The actual domain name(s) the cert should be valid for
issuerRef:
name: letsencrypt-staging
kind: ClusterIssuer
This Certificate resource tells cert-manager: "I need a certificate for nginx.example.com, and I want you to store it in a secret named nginx-tls in the default namespace. Use the letsencrypt-staging ClusterIssuer to get it."
Once you apply these YAMLs (kubectl apply -f issuer.yaml -f certificate.yaml), cert-manager will:
- See the
Certificateresource. - Consult the specified
ClusterIssuer. - Initiate an ACME challenge with the chosen issuer.
- If the challenge passes, it obtains the certificate.
- It then creates or updates the
nginx-tlsSecret in thedefaultnamespace withtls.crtandtls.keyfiles. - Your Nginx deployment, mounted to
/etc/nginx/ssl, will automatically pick up these new files.
The real power is that this isn’t a one-time event. When the certificate approaches expiration (typically 30 days before), cert-manager will automatically renew it and update the nginx-tls secret. Your application never needs to know about the certificate lifecycle.
The most surprising true thing about this setup is that your application never directly interacts with the certificate authority. It only ever sees a Kubernetes Secret, and the rotation mechanism is entirely managed by cert-manager and the Certificate resource’s desired state.
Consider the CertificateRequest resource. When cert-manager needs to get a certificate, it doesn’t directly ask the issuer. Instead, it creates a CertificateRequest object. This object contains the desired certificate signing request (CSR) and the details of the issuer to use. The actual issuer (like an ACME solver or a webhook) then processes this CertificateRequest, fulfills the challenge, and creates a Certificate resource upon success. This separation of concerns allows for modularity and extensibility, enabling custom issuers or complex approval workflows before a certificate is actually issued.
The next concept you’ll want to wrap your head around is how to manage different types of challenges (besides HTTP01), like DNS01, and how to handle private CAs for internal services.