Kubernetes cert-manager: Automate TLS Certificates
Set up cert-manager in Kubernetes to automate TLS certificate provisioning with Let's Encrypt. Covers Issuers, Certificates, Ingress integration.
What you'll learn
- ✓Install and configure cert-manager in your cluster
- ✓Create Issuers and ClusterIssuers for Let's Encrypt
- ✓Automate TLS certificates for Ingress resources
- ✓Troubleshoot common certificate issues
Prerequisites
- •A running Kubernetes cluster
- •kubectl configured and working
- •Basic Ingress knowledge — see /blog/kubernetes-ingress-basics
TLS certificates are a requirement for any production web service. Managing them manually — generating, deploying, renewing, and rotating — is tedious and error-prone. cert-manager automates the entire lifecycle. It provisions certificates from authorities like Let’s Encrypt, stores them as Kubernetes Secrets, and renews them before they expire.
Once set up, you add a single annotation to your Ingress and cert-manager handles the rest.
How cert-manager Works
cert-manager runs as a set of controllers in your cluster. The workflow is:
- You create a
Certificateresource (or annotate an Ingress). - cert-manager creates an
Orderto request the certificate from a CA. - The CA issues a
Challenge(HTTP-01 or DNS-01) to verify domain ownership. - cert-manager solves the challenge automatically.
- The signed certificate is stored in a Kubernetes Secret.
- cert-manager renews the certificate before it expires (default: 30 days before expiry).
Installation
Install cert-manager with its CRDs:
# Install via kubectl
# kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.15.0/cert-manager.yaml
# Or via Helm
# helm repo add jetstack https://charts.jetstack.io
# helm repo update
# helm install cert-manager jetstack/cert-manager \
# --namespace cert-manager \
# --create-namespace \
# --set crds.enabled=true
Verify the installation:
# kubectl get pods -n cert-manager
# NAME READY STATUS RESTARTS AGE
# cert-manager-7f4d5b5b9-xxxxx 1/1 Running 0 60s
# cert-manager-cainjector-6b8d7c5d5-xxxxx 1/1 Running 0 60s
# cert-manager-webhook-8b5f7c6d4-xxxxx 1/1 Running 0 60s
All three pods must be running: the main controller, the CA injector, and the webhook.
Setting Up Issuers
An Issuer tells cert-manager where to get certificates. There are two scopes:
- Issuer — works in a single namespace.
- ClusterIssuer — works across all namespaces.
For most setups, use a ClusterIssuer.
Let’s Encrypt with HTTP-01
HTTP-01 challenges work by placing a file at http://yourdomain/.well-known/acme-challenge/. Your Ingress controller must be reachable from the internet.
# cluster-issuer-staging.yaml
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-staging
spec:
acme:
email: your-email@example.com
server: https://acme-staging-v02.api.letsencrypt.org/directory
privateKeySecretRef:
name: letsencrypt-staging-key
solvers:
- http01:
ingress:
class: nginx
# cluster-issuer-production.yaml
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
email: your-email@example.com
server: https://acme-v02.api.letsencrypt.org/directory
privateKeySecretRef:
name: letsencrypt-prod-key
solvers:
- http01:
ingress:
class: nginx
Always start with the staging issuer. Let’s Encrypt has rate limits on production, and you do not want to hit them while testing.
# kubectl apply -f cluster-issuer-staging.yaml
# kubectl apply -f cluster-issuer-production.yaml
# kubectl get clusterissuer
Let’s Encrypt with DNS-01
DNS-01 challenges verify domain ownership by creating a DNS TXT record. This works for wildcard certificates and domains not reachable from the internet.
# cluster-issuer-dns.yaml
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-dns
spec:
acme:
email: your-email@example.com
server: https://acme-v02.api.letsencrypt.org/directory
privateKeySecretRef:
name: letsencrypt-dns-key
solvers:
- dns01:
cloudflare:
email: your-email@example.com
apiTokenSecretRef:
name: cloudflare-api-token
key: api-token
Create the API token secret:
apiVersion: v1
kind: Secret
metadata:
name: cloudflare-api-token
namespace: cert-manager
type: Opaque
stringData:
api-token: your-cloudflare-api-token
cert-manager supports many DNS providers: AWS Route 53, Google Cloud DNS, Azure DNS, Cloudflare, DigitalOcean, and more.
Requesting Certificates via Ingress
The easiest way to get a certificate is to annotate your Ingress:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: web-app
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
ingressClassName: nginx
tls:
- hosts:
- app.example.com
secretName: app-example-com-tls
rules:
- host: app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web-app
port:
number: 80
When you apply this Ingress, cert-manager automatically:
- Creates a
Certificateresource. - Initiates the ACME challenge.
- Stores the signed certificate in the
app-example-com-tlsSecret. - Your Ingress controller picks up the Secret and serves TLS.
Multiple Domains
spec:
tls:
- hosts:
- app.example.com
- www.example.com
secretName: example-com-tls
- hosts:
- api.example.com
secretName: api-example-com-tls
Each tls entry gets its own certificate and Secret.
Requesting Certificates Directly
For more control, create a Certificate resource explicitly:
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: app-example-com
namespace: default
spec:
secretName: app-example-com-tls
issuerRef:
name: letsencrypt-prod
kind: ClusterIssuer
dnsNames:
- app.example.com
- www.example.com
duration: 2160h # 90 days
renewBefore: 720h # Renew 30 days before expiry
Wildcard Certificates
Wildcard certificates require DNS-01 challenges:
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: wildcard-example-com
namespace: default
spec:
secretName: wildcard-example-com-tls
issuerRef:
name: letsencrypt-dns
kind: ClusterIssuer
dnsNames:
- "*.example.com"
- example.com
The bare domain example.com is listed separately because a wildcard only covers subdomains, not the root.
Checking Certificate Status
# View all certificates
# kubectl get certificates -A
# Detailed status
# kubectl describe certificate app-example-com
# Check the certificate secret
# kubectl get secret app-example-com-tls -o yaml
# View certificate details
# kubectl get secret app-example-com-tls -o jsonpath='{.data.tls\.crt}' | base64 -d | openssl x509 -text -noout
Key status fields to check:
# kubectl get certificate app-example-com -o yaml
# status:
# conditions:
# - type: Ready
# status: "True"
# message: Certificate is up to date and has not expired
# notAfter: "2026-10-05T12:00:00Z"
# notBefore: "2026-07-07T12:00:00Z"
# renewalTime: "2026-09-05T12:00:00Z"
Troubleshooting
When a certificate is not ready, follow the chain: Certificate -> CertificateRequest -> Order -> Challenge.
# Check the certificate
# kubectl describe certificate app-example-com
# Check the certificate request
# kubectl get certificaterequest -n default
# kubectl describe certificaterequest app-example-com-xxxxx
# Check the order
# kubectl get order -n default
# kubectl describe order app-example-com-xxxxx
# Check the challenge
# kubectl get challenge -n default
# kubectl describe challenge app-example-com-xxxxx
Common Issues
Challenge stuck in pending:
The HTTP-01 challenge needs the solver pod to be reachable at port 80. Check:
- Is your Ingress controller running?
- Are DNS records pointing to your load balancer?
- Are there firewall rules blocking port 80?
# Check solver pod
# kubectl get pods -n default -l acme.cert-manager.io/http01-solver=true
# kubectl logs <solver-pod-name>
Rate limiting:
Let’s Encrypt limits production certificate issuance to 50 per registered domain per week. Use the staging issuer for testing:
# Switch to staging for testing
annotations:
cert-manager.io/cluster-issuer: letsencrypt-staging
Permission errors with DNS-01:
Ensure your cloud provider API credentials have the right permissions to create DNS records. For AWS Route 53, the IAM policy needs route53:ChangeResourceRecordSets and route53:GetChange.
Webhook timeout:
If cert-manager webhook is not ready, certificate creation fails. Check webhook logs:
# kubectl logs -n cert-manager deploy/cert-manager-webhook
Certificate for Non-Ingress Workloads
You can use cert-manager certificates with any workload that needs TLS:
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: grpc-server-cert
namespace: default
spec:
secretName: grpc-server-tls
issuerRef:
name: letsencrypt-prod
kind: ClusterIssuer
dnsNames:
- grpc.example.com
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: grpc-server
spec:
template:
spec:
containers:
- name: grpc-server
image: myregistry/grpc-server:v1
volumeMounts:
- name: tls
mountPath: /etc/tls
readOnly: true
volumes:
- name: tls
secret:
secretName: grpc-server-tls
The application reads /etc/tls/tls.crt and /etc/tls/tls.key to serve TLS.
Self-Signed and Internal CA
For internal services that do not need a public CA:
# Self-signed issuer for bootstrapping
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: selfsigned
spec:
selfSigned: {}
---
# Create a CA certificate
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: internal-ca
namespace: cert-manager
spec:
isCA: true
commonName: internal-ca
secretName: internal-ca-key
issuerRef:
name: selfsigned
kind: ClusterIssuer
duration: 87600h # 10 years
---
# Use the CA to issue certificates
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: internal-ca-issuer
spec:
ca:
secretName: internal-ca-key
Now you can issue certificates signed by your internal CA for service-to-service TLS.
Wrapping Up
cert-manager eliminates the manual work of TLS certificate management in Kubernetes. The setup path is straightforward:
- Install cert-manager into your cluster.
- Create a ClusterIssuer for Let’s Encrypt (start with staging).
- Annotate your Ingress resources or create Certificate objects.
- cert-manager handles issuance, storage, and renewal automatically.
Key points to remember:
- Use HTTP-01 for standard web services reachable from the internet.
- Use DNS-01 for wildcard certificates or internal services.
- Always test with the staging issuer before switching to production.
- Follow the Certificate -> CertificateRequest -> Order -> Challenge chain when troubleshooting.
- cert-manager renews certificates automatically, but monitor the Ready condition to catch issues early.
Related articles
- Kubernetes Kubernetes Network Policies: Pod-to-Pod Security Guide
Lock down pod communication with Kubernetes NetworkPolicies. Learn ingress and egress rules, label selectors, and namespace isolation.
- Kubernetes RBAC in Kubernetes: A Practical Guide
A practical guide to Kubernetes RBAC: Roles, ClusterRoles, RoleBindings, ClusterRoleBindings, ServiceAccounts, and real-world patterns for securing cluster access.
- Kubernetes Kubernetes ConfigMaps and Secrets Tutorial
A practical walkthrough of ConfigMaps and Secrets in Kubernetes, including how to inject them as environment variables, mount as files, and rotate safely.
- Kubernetes Kubernetes RBAC Cheatsheet: Roles, Bindings, and Service Accounts
A concise tour of Kubernetes RBAC: roles vs cluster roles, bindings, service accounts, and patterns that scale without becoming a permissions zoo.