15%

Save 15% on All Hosting Services

Test your skills and get Discount on any hosting plan

Use code:

Skills
Get Started
22.10.2024

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 CertificateRequest object 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:

  1. Pending — The Certificate object exists but no valid CertificateRequest has been fulfilled.
  2. Issuing — A CertificateRequest has been created and submitted to the configured issuer.
  3. Ready — A valid, non-expired certificate is stored in the referenced Kubernetes Secret.
  4. Renewal Pending — The certificate is within the renewBefore window (default: 30 days before expiry) and a new CertificateRequest is 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

ResourceScopePurpose
`Issuer`NamespaceDefines a CA configuration for a single namespace
`ClusterIssuer`Cluster-wideDefines a CA configuration usable by all namespaces
`Certificate`NamespaceDeclares the desired TLS certificate and its properties
`CertificateRequest`NamespaceTracks a single, point-in-time certificate signing request
`Order`NamespaceRepresents an ACME order with a CA (e.g., Let's Encrypt)
`Challenge`NamespaceTracks 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

ApproachWildcard SupportKubernetes NativeMulti-CAAuto-RenewalComplexity
Cert-ManagerYes (DNS-01)Yes (CRDs)YesYesMedium
Manual CertbotNo (HTTP-01 only)NoLimitedScriptedLow–Medium
Cloud LB TLS TerminationVariesNoNoYesLow
Istio / Service Mesh CAInternal onlyYesNoYesHigh
HashiCorp Vault (standalone)YesNoYesManualHigh

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)
  • kubectl configured 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-manager

Verify all three core pods are running before proceeding:

kubectl get pods --namespace cert-manager

Expected 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          60s

The 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: nginx

Once 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: nginx

Apply both manifests:

kubectl apply -f clusterissuer-staging.yaml
kubectl apply -f clusterissuer-prod.yaml

Verify issuer readiness:

kubectl describe clusterissuer letsencrypt-prod

The 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 expiry

Apply and monitor issuance:

kubectl apply -f certificate.yaml
kubectl describe certificate example-com-tls -n production

Watch the CertificateRequest and Order objects for detailed status:

kubectl get certificaterequest -n production
kubectl describe order -n production

Step 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: 80

Cert-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-manager

Configure 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-token

Request 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.com

Critical 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-secret

Services 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: 8h

Cert-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=100

Common root causes:

  • HTTP-01 failure: The Ingress controller is not routing /.well-known/acme-challenge/ traffic correctly. Verify the challenge Ingress object 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 -enddate

Production 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 requests and limits on all three Cert-Manager pods to prevent resource contention on shared nodes.
  • RBAC audit: Cert-Manager requires broad RBAC permissions. Review the installed ClusterRole objects and restrict them to the minimum necessary for your issuer types.
  • Separate namespace: Always deploy Cert-Manager into its own cert-manager namespace, isolated from application workloads.
  • Monitoring: Expose Cert-Manager's Prometheus metrics endpoint (--metrics-listen-address) and alert on certmanager_certificate_expiration_timestamp_seconds to catch renewal failures before they impact services.
  • Backup CRD state: Include Certificate, ClusterIssuer, and Issuer objects 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

ScenarioRecommended IssuerChallenge TypeWildcard
Public web app, single domain`letsencrypt-prod`HTTP-01No
Public web app, multiple subdomains`letsencrypt-prod`DNS-01Yes
Air-gapped / private cluster`CA` or `Vault`N/A (internal)Yes
Enterprise with compliance requirements`Venafi`N/AYes
Service mesh mTLS (short-lived certs)`Vault` or `CA`N/A (internal)Yes
Development / local cluster`SelfSigned` or `CA`N/AYes

Key Takeaways

  • Install Cert-Manager using Helm with installCRDs=true and always validate against the staging ACME endpoint before switching to production.
  • Use ClusterIssuer for multi-namespace clusters; use namespace-scoped Issuer only 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 renewBefore field is not optional in production — set it to at least one-third of the certificate's total duration.
  • When issuance stalls, the debugging path is always: CertificateCertificateRequestOrderChallenge → controller logs.
  • DNS provider API credentials referenced by a ClusterIssuer must live in the cert-manager namespace, not in application namespaces.
  • Monitor certmanager_certificate_expiration_timestamp_seconds in Prometheus and page on it — silent renewal failures are the most dangerous failure mode.
  • Back up your Certificate and ClusterIssuer manifests 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.

15%

Save 15% on All Hosting Services

Test your skills and get Discount on any hosting plan

Use code:

Skills
Get Started