Get cert-manager running in 5 minutes — let’s dive in
I migrated a Kubernetes cluster to cert-manager last month and honestly have no desire to go back to managing certificates manually. Before that, I was using Certbot with a cron job for renewals. That works fine with just a few domains — but once you’re dealing with dozens of Ingresses, things get messy fast: syncing certs across multiple nodes, tracking expiry dates per domain, handling cron jobs that don’t fire on time.
cert-manager solves exactly that problem: it runs inside the cluster, automatically watches for certificates nearing expiry (30 days out), renews them, and injects them into Kubernetes Secrets. No SSH into servers, no cron jobs, nothing to remember.
Step 1: Install cert-manager with 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
Check that the pods are running:
kubectl get pods -n cert-manager
You should see 3 pods: cert-manager, cert-manager-cainjector, and cert-manager-webhook. All must be in Running state before proceeding.
Step 2: Create a ClusterIssuer for Let’s Encrypt
There are 2 environments: staging (for testing — certs aren’t trusted by browsers) and production. I strongly recommend testing with staging first — hitting Let’s Encrypt rate limits is a real pain and completely avoidable.
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: [email protected]
privateKeySecretRef:
name: letsencrypt-prod-key
solvers:
- http01:
ingress:
class: nginx
kubectl apply -f clusterissuer.yaml
kubectl get clusterissuer
Step 3: Annotate your Ingress to auto-provision certs
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: myapp-ingress
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
tls:
- hosts:
- myapp.example.com
secretName: myapp-tls
rules:
- host: myapp.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: myapp-service
port:
number: 80
Once applied, cert-manager will automatically create a Certificate resource, complete the ACME challenge, and store the cert in the Secret myapp-tls. This typically takes 1–2 minutes. Track the progress with:
kubectl describe certificate myapp-tls -n default
kubectl get certificaterequest -n default
Under the hood — what cert-manager is actually doing
When you annotate an Ingress with cert-manager.io/cluster-issuer, cert-manager creates a chain of resources in sequence:
- Certificate — Defines the cert to create: domains, secret name, issuer
- CertificateRequest — Submits a CSR (Certificate Signing Request) to Let’s Encrypt
- Order — Tracks the ACME order with the CA
- Challenge — Handles the HTTP-01 or DNS-01 challenge to prove domain ownership
My favorite part: cert-manager automatically renews certs when they have 30 days remaining — fully configurable via renewBefore. I once forgot about a Certbot cert on a staging server for two weeks with no one to remind me, and only discovered it had expired when another service’s webhook started failing. That doesn’t happen with cert-manager anymore.
HTTP-01 vs DNS-01 Challenge
HTTP-01 works for most use cases: cert-manager creates a temporary endpoint on your domain for Let’s Encrypt to verify. Requires the domain to point to the cluster and port 80 to be open from the internet.
DNS-01 is used for wildcard certs or securing internal services that aren’t exposed publicly: cert-manager creates a TXT record with your DNS provider. Requires additional integration with Cloudflare, Route53, or other DNS providers.
Advanced: Wildcard certs and securing internal services
Wildcard Certificate with DNS-01 (Cloudflare)
This is the setup I use in production. First, create an API token on Cloudflare with Zone:DNS:Edit permission scoped to a specific zone — don’t use a Global API Key since it has overly broad permissions and introduces unnecessary risk.
I often use the password generator at toolcraft.app when I need to generate random tokens for service accounts. The tool runs entirely in the browser — no risk of credentials passing through a third-party server.
kubectl create secret generic cloudflare-api-token \
--from-literal=api-token=YOUR_CLOUDFLARE_TOKEN \
-n cert-manager
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-dns
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: [email protected]
privateKeySecretRef:
name: letsencrypt-dns-key
solvers:
- dns01:
cloudflare:
apiTokenSecretRef:
name: cloudflare-api-token
key: api-token
Create a wildcard cert shared across all subdomains:
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"
TLS for internal services without a public IP
This is where cert-manager truly shines. Prometheus, Grafana, and ArgoCD are typically only accessible via VPN or a bastion host — but you should still use HTTPS to prevent credentials from being sniffed on the internal network.
DNS-01 only requires verification via a DNS record, not HTTP access from the outside — meaning internal services can still receive valid certificates:
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: grafana-internal-tls
namespace: monitoring
spec:
secretName: grafana-tls
issuerRef:
name: letsencrypt-dns
kind: ClusterIssuer
dnsNames:
- "grafana.internal.example.com"
Grafana’s Ingress gets valid HTTPS even without exposing port 80/443 to the internet. Credentials won’t be sniffed on the internal network, and browsers won’t show self-signed certificate warnings.
Practical tips from real-world production experience
1. Always test with staging first
Let’s Encrypt production has rate limits: 5 certificate failures/hour/domain, 50 certificates/domain/week. Hitting a rate limit locks your domain for a week with no new certs being issued. Create a separate staging ClusterIssuer by swapping the server URL:
server: https://acme-staging-v02.api.letsencrypt.org/directory
2. Debugging when a cert isn’t being issued
90% of the issues I’ve encountered come down to: (1) domain not pointing to the cluster correctly, (2) wrong ingress class name, (3) firewall blocking port 80 from the internet. Trace through the resource chain in this order:
# Inspect each step in the chain
kubectl describe certificate <name> -n <namespace>
kubectl describe certificaterequest <name> -n <namespace>
kubectl describe order <name> -n <namespace>
kubectl describe challenge <name> -n <namespace>
# View cert-manager logs
kubectl logs -n cert-manager deploy/cert-manager -f
3. Monitor certificate expiry with Prometheus
cert-manager exposes Prometheus metrics out of the box. Alert when a cert has fewer than 7 days remaining — cert-manager auto-renews, but an alert gives you an extra safety net:
certmanager_certificate_expiration_timestamp_seconds
certmanager_certificate_ready_status
4. Back up the Kubernetes Secrets containing certs
cert-manager stores private keys in Kubernetes Secrets. Lesson learned the hard way: I once recreated a namespace during cleanup, the Secret containing the cert was deleted along with it, and it took about 10 minutes of downtime waiting for a new cert to be issued. Since then I always use Velero to back up Secrets regularly, or at minimum export the Secret before doing anything involving the namespace:
kubectl get secret myapp-tls -n default -o yaml > myapp-tls-backup.yaml
5. Reuse wildcard certs across multiple Ingresses
Once you have a wildcard cert, reference the same Secret in multiple Ingresses rather than issuing a separate cert per subdomain — this reduces Let’s Encrypt API calls and makes management simpler:
spec:
tls:
- hosts:
- api.example.com
secretName: wildcard-example-com-tls # Reuse the Secret from the Certificate created earlier
I’m currently running cert-manager on a cluster with around 30 Ingresses and haven’t had to manually touch a single certificate. No matter how many more Ingresses get added, there’s zero extra management overhead. If you’re running Kubernetes and still have a cert renewal cron job somewhere — now is the time to change that.

