Install cert-manager and Issue TLS Certificates Automatically
Quick answer
Install cert-manager with Helm, configure a Let's Encrypt ClusterIssuer with HTTP-01 validation, and issue your first automatically-renewed TLS certificate — so you never deal with certificate expiry again.
- Step 1: Add the Jetstack Helm repository
- Step 2: Create the cert-manager namespace
- Step 3: Install cert-manager
- Step 4: Verify all pods are running
- Step 5: Create a staging ClusterIssuer
intermediate · 20 min
Before you begin
- A running Kubernetes cluster
- kubectl configured with cluster-admin access
- Helm 3 installed
- An nginx-ingress controller (for HTTP-01 challenge)
- A domain you control with DNS pointing to your ingress
Managing TLS certificates manually — generating CSRs, submitting to a CA, downloading PEM files, creating Secrets, tracking expiry dates — is the kind of toil that adds up. cert-manager eliminates all of it. You declare a Certificate resource, cert-manager handles the ACME challenge with Let's Encrypt, provisions the certificate into a Kubernetes Secret, and renews it 30 days before it expires.
This tutorial installs cert-manager, configures both staging and production Let's Encrypt issuers, and issues a real certificate for a domain you control. If you're deciding whether cert-manager is worth the operational overhead versus a cloud-managed certificate service, see our cert-manager vs Cloud Certs comparison.
Step 1: Add the Jetstack Helm repository
cert-manager is published by Jetstack (now part of Venafi):
helm repo add jetstack https://charts.jetstack.io
helm repo updateStep 2: Create the cert-manager namespace
kubectl create namespace cert-managerStep 3: Install cert-manager
Pin a specific version to avoid unexpected upgrades. The crds.enabled=true flag installs the CRDs (Certificate, Issuer, ClusterIssuer, CertificateRequest) as part of the Helm release:
helm install cert-manager jetstack/cert-manager \
--namespace cert-manager \
--version v1.21.1 \
--set crds.enabled=trueCheck the cert-manager releases page for the latest version before running this. The crds.enabled flag replaced installCRDs starting in v1.15 — older docs may show --set installCRDs=true.
The OCI registry (oci://quay.io/jetstack/charts/cert-manager) is the canonical source of truth for cert-manager charts. The helm repo add method is a delayed mirror and both work for standard installs.
Step 4: Verify all pods are running
Three pods must reach Running status: the controller, the webhook, and the cainjector:
kubectl get pods -n cert-managerExpected output:
NAME READY STATUS RESTARTS
cert-manager-7b4d4b7d8c-x8p2q 1/1 Running 0
cert-manager-cainjector-6b9d7b8d8c-r4n7l 1/1 Running 0
cert-manager-webhook-5c9b9b9b8c-k2j9m 1/1 Running 0
Do not proceed until all three are Running. The webhook must be healthy before issuers will work.
Step 5: Create a staging ClusterIssuer
Always start with Let's Encrypt staging. The staging CA issues untrusted certificates (your browser will warn). Staging has very high rate limits designed for testing — far higher than production — so you can iterate freely without exhausting the 50 certificates/domain/week production limit. (Note: staging is not truly unlimited; it just has much higher thresholds.)
Replace [email protected] with your email address:
1cat <<EOF | kubectl apply -f -
2apiVersion: cert-manager.io/v1
3kind: ClusterIssuer
4metadata:
5 name: letsencrypt-staging
6spec:
7 acme:
8 server: https://acme-staging-v02.api.letsencrypt.org/directory
9 email: [email protected]
10 privateKeySecretRef:
11 name: letsencrypt-staging-key
12 solvers:
13 - http01:
14 ingress:
15 ingressClassName: nginx
16EOFCheck the issuer is ready:
kubectl describe clusterissuer letsencrypt-stagingYou should see Status: True, Type: Ready, and Reason: ACMEAccountRegistered in the conditions. If you see Reason: ErrInitIssuer, check that your email is valid and the ACME server is reachable.
Step 6: Test with a staging certificate
Request a certificate for a domain you control. cert-manager will create an HTTP-01 challenge pod and Ingress, wait for Let's Encrypt to validate it, then provision the certificate into my-app-staging-tls:
1cat <<EOF | kubectl apply -f -
2apiVersion: cert-manager.io/v1
3kind: Certificate
4metadata:
5 name: my-app-staging
6 namespace: default
7spec:
8 secretName: my-app-staging-tls
9 issuerRef:
10 name: letsencrypt-staging
11 kind: ClusterIssuer
12 dnsNames:
13 - my-app.example.com
14EOFWatch the certificate get issued:
kubectl describe certificate my-app-staging -n defaultWait for:
Status:
Conditions:
- Type: Ready
Status: "True"
Message: Certificate is up to date and has not expired
This typically takes 30–90 seconds. If it stalls, check kubectl describe certificaterequest -n default and kubectl describe challenge -n default for error messages.
Step 7: Create the production ClusterIssuer
Once staging works end-to-end, create the production issuer:
1cat <<EOF | kubectl apply -f -
2apiVersion: cert-manager.io/v1
3kind: ClusterIssuer
4metadata:
5 name: letsencrypt-prod
6spec:
7 acme:
8 server: https://acme-v02.api.letsencrypt.org/directory
9 email: [email protected]
10 privateKeySecretRef:
11 name: letsencrypt-prod-key
12 solvers:
13 - http01:
14 ingress:
15 ingressClassName: nginx
16EOFStep 8: Issue a production certificate
Switch the issuerRef to letsencrypt-prod:
1cat <<EOF | kubectl apply -f -
2apiVersion: cert-manager.io/v1
3kind: Certificate
4metadata:
5 name: my-app-tls
6 namespace: default
7spec:
8 secretName: my-app-tls
9 issuerRef:
10 name: letsencrypt-prod
11 kind: ClusterIssuer
12 dnsNames:
13 - my-app.example.com
14EOFStep 9: Use the certificate in an Ingress
Reference the Secret in your Ingress resource. The cert-manager.io/cluster-issuer annotation tells cert-manager to manage this certificate automatically — you don't even need to create a Certificate resource separately:
1apiVersion: networking.k8s.io/v1
2kind: Ingress
3metadata:
4 name: my-app
5 namespace: default
6 annotations:
7 cert-manager.io/cluster-issuer: letsencrypt-prod
8spec:
9 ingressClassName: nginx
10 tls:
11 - hosts:
12 - my-app.example.com
13 secretName: my-app-tls
14 rules:
15 - host: my-app.example.com
16 http:
17 paths:
18 - path: /
19 pathType: Prefix
20 backend:
21 service:
22 name: my-app
23 port:
24 number: 80When you apply this Ingress, cert-manager detects the annotation and automatically creates the Certificate resource, requests it from Let's Encrypt, and keeps it renewed.
What you built
cert-manager is watching your cluster for Certificate and Ingress resources. New certificates are issued within minutes of creation. Renewal happens automatically at 2/3 through each certificate's lifetime — for a standard 90-day Let's Encrypt certificate that is 30 days before expiry. You can override this with spec.renewBefore on the Certificate resource. No cron jobs, no manual certificate management, no expiry surprises. The annotation-based workflow on Ingress means any engineer adding a new service gets TLS automatically without knowing anything about cert-manager.
Frequently Asked Questions
What is the difference between an Issuer and a ClusterIssuer?
An Issuer is namespaced and can only issue certificates in its own namespace. A ClusterIssuer is cluster-scoped and available everywhere. Use a ClusterIssuer for a shared ACME account so you are not duplicating credentials per namespace, and an Issuer when a team should have its own isolated issuance path.
Why install CRDs separately?
You do not have to. cert-manager ships its CRDs as chart templates behind crds.enabled=true, so helm upgrade updates them too — unlike charts using Helm's crds/ directory, which Helm installs once and never upgrades or deletes. Applying them separately with kubectl is the alternative when you want CRD upgrades reviewed as their own step. Either way, read the release notes before crossing a major version, since deleting a CRD deletes every object of that kind.
How do I know issuance is actually working?
Issue a certificate from the staging issuer and watch it become ready, rather than assuming a running pod means a working installation. Most failures are in the challenge path — routing, DNS, credentials — and only appear when something is actually issued.
Does cert-manager renew automatically?
Yes, typically well before expiry, and its failures are quiet: a solver credential that expired, a rate limit hit, or an annotation that stopped matching. Nothing breaks until the certificate does, weeks later. Alert on expiry from a blackbox probe against the live endpoint rather than from cert-manager's own metrics.
Official References
- cert-manager documentation — Issuers, Certificates and the ACME solver flow
- Helm chart template guide — templates, values and the sprig function set
- Helm charts — chart structure, dependencies and hooks
We built Podscape to simplify Kubernetes workflows like this — logs, events, and cluster state in one interface, without switching tools.
Struggling with this in production?
We help teams fix these exact issues. Our engineers have deployed these patterns across production environments at scale.