Cert-Manager on Kubernetes: Complete Setup Guide for Automated TLS Certificate Management
Cert-Manager is an open-source Kubernetes controller that fully automates the lifecycle of TLS certificates — from initial issuance through validation and renewal — by integrating directly with certificate authorities such as Let's Encrypt, HashiCorp Vault, and private PKI systems. It eliminates manual certificate workflows by treating certificates as native Kubernetes resources, managed declaratively through Custom Resource Definitions (CRDs).
For any production Kubernetes cluster, expired or misconfigured TLS certificates are one of the most common causes of unplanned downtime. Cert-Manager solves this by continuously reconciling certificate state, automatically renewing credentials before expiry, and storing the resulting key material in Kubernetes Secrets that Ingress controllers and workloads can consume immediately.
Why Cert-Manager Is the Standard for Kubernetes TLS Automation
Before Cert-Manager became the de facto solution, teams either scripted certbot renewals on individual nodes, manually rotated certificates across namespaces, or relied on cloud-provider-specific integrations that created vendor lock-in. Cert-Manager unifies all of these approaches under a single, Kubernetes-native control plane.
Key advantages over ad-hoc approaches:
- Declarative management: Certificate intent is expressed as a YAML manifest, version-controlled alongside application code.
- Automatic reconciliation: The controller loop detects drift between desired and actual certificate state and corrects it without human input.
- Multi-issuer support: A single cluster can simultaneously use Let's Encrypt for public-facing services, an internal CA for service-mesh mTLS, and HashiCorp Vault for secrets-sensitive workloads.
- Audit trail: Every
CertificateRequestobject is persisted in the Kubernetes API, giving you a complete issuance history.
Running Kubernetes on a VPS Hosting platform with NVMe-backed storage and full root access gives you the control necessary to configure custom DNS resolvers, manage firewall rules for ACME HTTP-01 challenges, and install cluster-level CRDs without restriction — all prerequisites for a production-grade Cert-Manager deployment.
Core Architecture: How Cert-Manager Works Internally
Understanding Cert-Manager's internal architecture prevents misconfigurations and helps you debug issuance failures quickly.
The Certificate Lifecycle State Machine
Cert-Manager manages certificates through a deterministic state machine. Each Certificate resource transitions through the following states:
- Pending — The
Certificateobject exists but no validCertificateRequesthas been fulfilled. - Issuing — A
CertificateRequesthas been created and submitted to the configured issuer. - Ready — A valid, non-expired certificate is stored in the referenced Kubernetes
Secret. - Renewal Pending — The certificate is within the
renewBeforewindow (default: 30 days before expiry) and a newCertificateRequestis being processed.
The controller watches all Certificate objects cluster-wide and continuously reconciles their state. If a Secret is deleted manually, Cert-Manager detects the missing resource and immediately triggers re-issuance — a critical self-healing behavior that prevents accidental outages.
Core CRD Components
| Resource | Scope | Purpose |
|---|---|---|
| — | — | — |
| `Issuer` | Namespace | Defines a CA configuration for a single namespace |
| `ClusterIssuer` | Cluster-wide | Defines a CA configuration usable by all namespaces |
| `Certificate` | Namespace | Declares the desired TLS certificate and its properties |
| `CertificateRequest` | Namespace | Tracks a single, point-in-time certificate signing request |
| `Order` | Namespace | Represents an ACME order with a CA (e.g., Let's Encrypt) |
| `Challenge` | Namespace | Tracks a single ACME challenge (HTTP-01 or DNS-01) |
The Order and Challenge resources are created automatically by the ACME issuer — you rarely interact with them directly, but inspecting them is the primary debugging path when issuance stalls.
Certificate Storage and Secret Format
Once issued, Cert-Manager writes three data keys into the target Kubernetes Secret:
tls.crt— The full certificate chain (leaf + intermediates), PEM-encoded.tls.key— The private key, PEM-encoded.ca.crt— The issuing CA certificate (populated for internal CAs; may be empty for ACME issuers).
Ingress controllers, service meshes, and application pods mount this Secret directly. The format is compatible with NGINX, Traefik, Istio, Linkerd, and most other Kubernetes-native TLS consumers.
Supported Issuers and When to Use Each
ACME (Let's Encrypt and Alternatives)
The ACME protocol is the most common issuance path. Cert-Manager implements the full RFC 8555 ACME client, supporting both staging and production endpoints.
HTTP-01 challenge: Cert-Manager temporarily serves a token at http://<domain>/.well-known/acme-challenge/<token>. This requires the domain to resolve to a publicly reachable IP on port 80. It works for single-domain and SAN certificates but cannot issue wildcard certificates.
DNS-01 challenge: Cert-Manager creates a _acme-challenge.<domain> TXT record via a DNS provider API. This works behind firewalls, supports wildcard certificates (*.example.com), and is required for any cluster not directly exposed to the internet. Supported DNS providers include Cloudflare, Route53, Google Cloud DNS, Azure DNS, and many others via webhook solvers.
HashiCorp Vault
The Vault issuer integrates with Vault's PKI secrets engine. It is the preferred choice for:
- Internal microservice mTLS where certificates must be short-lived (hours, not months).
- Environments with strict key custody requirements where private keys must never leave Vault.
- Automated certificate rotation tied to Vault's lease renewal system.
Self-Signed and CA Issuers
The SelfSigned issuer generates certificates signed by the certificate's own private key — useful for bootstrapping a root CA within the cluster. The CA issuer uses a Kubernetes Secret containing a CA key pair to sign certificates, making it suitable for internal development environments and service mesh PKI.
Venafi
Venafi integration targets enterprise environments with centralized certificate policy enforcement, compliance auditing, and integration with hardware security modules (HSMs).
Cert-Manager vs. Alternative TLS Automation Approaches
| Approach | Wildcard Support | Kubernetes Native | Multi-CA | Auto-Renewal | Complexity |
|---|---|---|---|---|---|
| — | — | — | — | — | — |
| Cert-Manager | Yes (DNS-01) | Yes (CRDs) | Yes | Yes | Medium |
| Manual Certbot | No (HTTP-01 only) | No | Limited | Scripted | Low–Medium |
| Cloud LB TLS Termination | Varies | No | No | Yes | Low |
| Istio / Service Mesh CA | Internal only | Yes | No | Yes | High |
| HashiCorp Vault (standalone) | Yes | No | Yes | Manual | High |
For teams running Kubernetes on a Dedicated Server or VPS, Cert-Manager is the only approach that is simultaneously Kubernetes-native, supports all major CAs, and requires zero ongoing manual intervention.
Installing Cert-Manager: Production-Ready Setup
Prerequisites
- Kubernetes 1.22+ (1.26+ recommended for full CRD stability)
kubectlconfigured with cluster-admin privileges- Helm 3.x installed
- A registered domain with DNS management access (for DNS-01) or a publicly reachable ingress IP (for HTTP-01)
If you are managing your own domain, Domain Registration through a provider that exposes an API is strongly recommended — it enables fully automated DNS-01 challenge solving without manual intervention.
Step 1: Install CRDs and the Cert-Manager Controller
The recommended production installation method uses Helm with CRDs managed separately to avoid Helm upgrade conflicts:
# Add the Jetstack Helm repository
helm repo add jetstack https://charts.jetstack.io
helm repo update
# Install Cert-Manager with CRDs included
helm install cert-manager jetstack/cert-manager
--namespace cert-manager
--create-namespace
--version v1.14.5
--set installCRDs=true
--set global.leaderElection.namespace=cert-managerVerify all three core pods are running before proceeding:
kubectl get pods --namespace cert-managerExpected output:
NAME READY STATUS RESTARTS AGE
cert-manager-xxxxxxxxx-xxxxx 1/1 Running 0 60s
cert-manager-cainjector-xxxxxxxxx-xxxxx 1/1 Running 0 60s
cert-manager-webhook-xxxxxxxxx-xxxxx 1/1 Running 0 60sThe cainjector injects CA bundle data into webhook configurations. The webhook validates and mutates Cert-Manager CRD objects — if it is not running, all kubectl apply operations on Cert-Manager resources will fail.
Step 2: Configure a ClusterIssuer for Let's Encrypt
Always start with the Let's Encrypt staging environment to avoid hitting production rate limits (50 certificates per registered domain per week):
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-staging
spec:
acme:
server: https://acme-staging-v02.api.letsencrypt.org/directory
email: ops@example.com
privateKeySecretRef:
name: letsencrypt-staging-account-key
solvers:
- http01:
ingress:
class: nginxOnce staging certificates issue successfully, create the production issuer:
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: ops@example.com
privateKeySecretRef:
name: letsencrypt-prod-account-key
solvers:
- http01:
ingress:
class: nginxApply both manifests:
kubectl apply -f clusterissuer-staging.yaml
kubectl apply -f clusterissuer-prod.yamlVerify issuer readiness:
kubectl describe clusterissuer letsencrypt-prodThe Status.Conditions field must show Ready: True. If it shows False, the ACME account registration failed — check the email address and network connectivity from the cert-manager pod.
Step 3: Request a Certificate Explicitly
You can request certificates either by creating a Certificate resource directly or by annotating an Ingress resource. The explicit Certificate approach gives you more control:
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: example-com-tls
namespace: production
spec:
secretName: example-com-tls-secret
issuerRef:
name: letsencrypt-prod
kind: ClusterIssuer
commonName: example.com
dnsNames:
- example.com
- www.example.com
duration: 2160h # 90 days (Let's Encrypt default)
renewBefore: 720h # Renew 30 days before expiryApply and monitor issuance:
kubectl apply -f certificate.yaml
kubectl describe certificate example-com-tls -n productionWatch the CertificateRequest and Order objects for detailed status:
kubectl get certificaterequest -n production
kubectl describe order -n productionStep 4: Ingress Annotation Method (Simplified)
For teams using NGINX Ingress Controller, Cert-Manager can be triggered automatically via an annotation — no separate Certificate manifest required:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: example-ingress
namespace: production
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
ingressClassName: nginx
tls:
- hosts:
- example.com
- www.example.com
secretName: example-com-tls-secret
rules:
- host: example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: example-service
port:
number: 80Cert-Manager detects the annotation and automatically creates the corresponding Certificate resource. This is the most common pattern in GitOps workflows.
Advanced Configuration Patterns
Wildcard Certificates with DNS-01 (Cloudflare Example)
Wildcard certificates require DNS-01 validation. First, create a Secret containing your Cloudflare API token:
kubectl create secret generic cloudflare-api-token
--from-literal=api-token=<YOUR_CLOUDFLARE_API_TOKEN>
--namespace cert-managerConfigure the ClusterIssuer with a DNS-01 solver:
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod-dns
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: ops@example.com
privateKeySecretRef:
name: letsencrypt-prod-dns-account-key
solvers:
- dns01:
cloudflare:
apiTokenSecretRef:
name: cloudflare-api-token
key: api-tokenRequest the wildcard certificate:
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: wildcard-example-com
namespace: production
spec:
secretName: wildcard-example-com-tls
issuerRef:
name: letsencrypt-prod-dns
kind: ClusterIssuer
dnsNames:
- "*.example.com"
- example.comCritical pitfall: The Secret containing the DNS provider credentials must reside in the cert-manager namespace when referenced by a ClusterIssuer. If you place it in another namespace, the controller cannot read it and issuance will silently fail. Check the cert-manager controller logs with kubectl logs -n cert-manager deploy/cert-manager if challenges stall.
Internal PKI with a CA Issuer
For service-to-service mTLS within a cluster, a CA issuer backed by a self-signed root is more appropriate than ACME:
# Step 1: Create a self-signed root CA certificate
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: selfsigned-root
spec:
selfSigned: {}
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: internal-root-ca
namespace: cert-manager
spec:
isCA: true
commonName: internal-root-ca
secretName: internal-root-ca-secret
issuerRef:
name: selfsigned-root
kind: ClusterIssuer
---
# Step 2: Create a CA issuer backed by the root certificate
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: internal-ca-issuer
spec:
ca:
secretName: internal-root-ca-secretServices can now request short-lived certificates from internal-ca-issuer for mTLS without any external CA dependency.
Configuring renewBefore for High-Security Environments
The default renewBefore of 30 days is appropriate for 90-day Let's Encrypt certificates. For short-lived internal certificates (e.g., 24-hour validity), set renewBefore to a fraction of the total duration:
spec:
duration: 24h
renewBefore: 8hCert-Manager will begin renewal 8 hours before expiry, giving the controller three renewal windows within a 24-hour certificate lifetime — a critical safety margin if the cluster experiences transient API server unavailability.
Debugging Common Cert-Manager Failures
Certificate Stuck in "Issuing" State
# Check the Certificate status
kubectl describe certificate <name> -n <namespace>
# Check the associated CertificateRequest
kubectl describe certificaterequest -n <namespace>
# Check the ACME Order
kubectl describe order -n <namespace>
# Check the Challenge
kubectl describe challenge -n <namespace>
# Check controller logs
kubectl logs -n cert-manager deploy/cert-manager --tail=100Common root causes:
- HTTP-01 failure: The Ingress controller is not routing
/.well-known/acme-challenge/traffic correctly. Verify the challengeIngressobject was created and that port 80 is reachable from the internet. Firewalls on the host must allow inbound TCP/80. - DNS-01 failure: The DNS provider API token has insufficient permissions, or DNS propagation has not completed. Let's Encrypt's validation servers may query different resolvers than your local DNS — propagation delays of 60–120 seconds are normal.
- Rate limit exceeded: Let's Encrypt enforces 5 failed validation attempts per domain per hour. After hitting this limit, you must wait before retrying. Always test with the staging endpoint first.
- Webhook not ready: If the cert-manager webhook pod is not running, all CRD operations will fail with a connection refused error. Ensure the webhook service has a valid endpoint.
Verifying a Deployed Certificate
# Check the Secret contents
kubectl get secret example-com-tls-secret -n production -o jsonpath='{.data.tls.crt}' | base64 -d | openssl x509 -noout -text
# Check expiry date specifically
kubectl get secret example-com-tls-secret -n production -o jsonpath='{.data.tls.crt}' | base64 -d | openssl x509 -noout -enddateProduction Hardening Checklist
Deploying Cert-Manager in a production environment requires more than a default Helm install. Apply these hardening measures before going live:
- Resource limits: Set CPU and memory
requestsandlimitson all three Cert-Manager pods to prevent resource contention on shared nodes. - RBAC audit: Cert-Manager requires broad RBAC permissions. Review the installed
ClusterRoleobjects and restrict them to the minimum necessary for your issuer types. - Separate namespace: Always deploy Cert-Manager into its own
cert-managernamespace, isolated from application workloads. - Monitoring: Expose Cert-Manager's Prometheus metrics endpoint (
--metrics-listen-address) and alert oncertmanager_certificate_expiration_timestamp_secondsto catch renewal failures before they impact services. - Backup CRD state: Include
Certificate,ClusterIssuer, andIssuerobjects in your cluster backup strategy. Losing these manifests after a disaster recovery event means manually recreating all certificate configurations. - Staging validation: Never issue your first certificate against a production ACME endpoint. Rate limit violations can block your domain for up to a week.
If you are running multiple Kubernetes clusters — for example, separating staging and production workloads — VPS Control Panels can simplify cluster lifecycle management and give you a unified interface for provisioning the underlying infrastructure each cluster runs on.
For workloads that require GPU-accelerated inference or ML serving behind TLS-terminated endpoints, the same Cert-Manager patterns apply on GPU Hosting infrastructure, where automated certificate management is equally critical for securing model API endpoints.
Practical Decision Matrix: Choosing the Right Issuer
| Scenario | Recommended Issuer | Challenge Type | Wildcard |
|---|---|---|---|
| — | — | — | — |
| Public web app, single domain | `letsencrypt-prod` | HTTP-01 | No |
| Public web app, multiple subdomains | `letsencrypt-prod` | DNS-01 | Yes |
| Air-gapped / private cluster | `CA` or `Vault` | N/A (internal) | Yes |
| Enterprise with compliance requirements | `Venafi` | N/A | Yes |
| Service mesh mTLS (short-lived certs) | `Vault` or `CA` | N/A (internal) | Yes |
| Development / local cluster | `SelfSigned` or `CA` | N/A | Yes |
Key Takeaways
- Install Cert-Manager using Helm with
installCRDs=trueand always validate against the staging ACME endpoint before switching to production. - Use
ClusterIssuerfor multi-namespace clusters; use namespace-scopedIssueronly when you need per-team CA isolation. - DNS-01 is strictly required for wildcard certificates and for clusters not directly reachable from the internet on port 80.
- The
renewBeforefield is not optional in production — set it to at least one-third of the certificate's totalduration. - When issuance stalls, the debugging path is always:
Certificate→CertificateRequest→Order→Challenge→ controller logs. - DNS provider API credentials referenced by a
ClusterIssuermust live in thecert-managernamespace, not in application namespaces. - Monitor
certmanager_certificate_expiration_timestamp_secondsin Prometheus and page on it — silent renewal failures are the most dangerous failure mode. - Back up your
CertificateandClusterIssuermanifests as part of your disaster recovery plan.
Frequently Asked Questions
What is the difference between an Issuer and a ClusterIssuer in Cert-Manager?
An Issuer is namespace-scoped and can only issue certificates within the namespace where it is defined. A ClusterIssuer is cluster-wide and can be referenced by Certificate resources in any namespace. For most production clusters, ClusterIssuer is the correct choice unless you need strict namespace-level CA isolation.
Why is my Cert-Manager certificate stuck in the "Issuing" state?
Inspect the Order and Challenge objects in the same namespace using kubectl describe. The most common causes are: port 80 blocked by a firewall (HTTP-01), incorrect DNS API credentials or propagation delay (DNS-01), Let's Encrypt rate limits exceeded, or the cert-manager webhook pod not running. Always check controller logs with kubectl logs -n cert-manager deploy/cert-manager.
Can Cert-Manager issue wildcard certificates from Let's Encrypt?
Yes, but only using the DNS-01 challenge type. HTTP-01 cannot validate wildcard domains. You must configure a DNS provider integration (Cloudflare, Route53, etc.) in your ClusterIssuer solver configuration and ensure the API credentials have permission to create TXT records on the target domain.
How does Cert-Manager handle certificate renewal automatically?
Cert-Manager's controller continuously compares each Certificate object's expiry against the current time. When the remaining validity falls below the renewBefore threshold (default: 30 days), the controller creates a new CertificateRequest, completes the ACME challenge, and atomically replaces the contents of the target Secret — all without restarting the pods consuming the certificate. Pods that mount the Secret as a volume will see the updated certificate on the next kubelet sync cycle.
Is Cert-Manager suitable for securing internal service-to-service communication, not just public HTTPS?
Yes. Using the CA issuer or HashiCorp Vault issuer, Cert-Manager can issue short-lived certificates for internal mTLS between microservices. This is a common pattern in service mesh deployments where each workload needs a unique identity certificate. The duration and renewBefore fields allow certificates to be as short-lived as minutes, with fully automated rotation.
