How to Deploy cert-manager and Manage SSL Certificates in Kubernetes

Updated on 20 May, 2026
Deploy cert-manager on Kubernetes to automate SSL certificate issuance, renewal, and HTTPS application security.
How to Deploy cert-manager and Manage SSL Certificates in Kubernetes header image

cert-manager is a Kubernetes-native certificate management controller that automates the issuance and renewal of SSL/TLS certificates. It integrates with multiple Certificate Authorities (CAs), including Let's Encrypt, HashiCorp Vault, and private Public Key Infrastructure (PKI) systems. cert-manager stores issued certificates as Kubernetes secrets, enabling seamless integration with Ingress controllers and other cluster workloads for secure TLS connections.

This article explains how to deploy cert-manager in a Kubernetes cluster and manage SSL certificates for securing applications. It covers installation using kubectl or Helm, configuring Issuers and ClusterIssuers, deploying a sample application with Traefik as the Ingress controller, and generating trusted Let's Encrypt SSL certificates.

Prerequisites

Before you begin, you need to:

Install cert-manager

cert-manager requires Custom Resource Definitions (CRDs) to define its certificate-related resources in the cluster. Install the CRDs first, then install cert-manager using either kubectl or Helm.

Install CRDs

  1. Apply the cert-manager CRDs. Replace v1.20.2 with the latest stable version from the cert-manager releases page.

    console
    $ kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.20.2/cert-manager.crds.yaml
    
  2. Verify the CRDs are registered.

    console
    $ kubectl get crds | grep cert-manager.io
    

    The output lists all cert-manager CRDs including certificates.cert-manager.io, issuers.cert-manager.io, and clusterissuers.cert-manager.io.

Install cert-manager with kubectl or Helm

Install the cert-manager controller using either of the following methods.

Using kubectl
  1. Apply the cert-manager manifest. Replace v1.20.2 with the latest stable version from the releases page.

    console
    $ kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.20.2/cert-manager.yaml
    
  2. Wait for the cert-manager pods to be ready.

    console
    $ kubectl get pods -n cert-manager --watch
    

    The output shows all pods with Running status. Press Ctrl+C to exit the watch.

  3. Verify that the cert-manager API is available.

    console
    $ cmctl check api
    

Create an Issuer

cert-manager uses Issuers to sign and generate SSL certificates using a Certificate Authority (CA). A ClusterIssuer can sign certificates in any namespace, while an Issuer can only sign certificates within its own namespace.

Create a ClusterIssuer

A ClusterIssuer is a cluster-wide resource that can issue certificates to any namespace.

  1. Create a cluster-issuer.yaml file.

    console
    $ nano cluster-issuer.yaml
    
  2. Add the following configuration.

    yaml
    apiVersion: cert-manager.io/v1
    kind: ClusterIssuer
    metadata:
      name: example-issuer
    spec:
      ca:
        secretName: example-ca-secret
    

    Save and close the file.

    The above configuration creates a ClusterIssuer that references a CA keypair stored in the example-ca-secret secret. The secret must exist in the cert-manager namespace before the ClusterIssuer can issue certificates.

  3. Apply the configuration.

    console
    $ kubectl apply -f cluster-issuer.yaml
    
  4. Verify the ClusterIssuer is created.

    console
    $ kubectl get clusterissuers
    

    The output shows example-issuer with False in the READY column until the referenced secret is created.

Create an Issuer

An Issuer is a namespace-scoped resource that can only issue certificates within its namespace.

  1. Create an issuer.yaml file.

    console
    $ nano issuer.yaml
    
  2. Add the following configuration. Replace default with your target namespace if different.

    yaml
    apiVersion: cert-manager.io/v1
    kind: Issuer
    metadata:
      name: ca-issuer
      namespace: default
    spec:
      ca:
        secretName: ca-key-pair
    

    Save and close the file.

    The above configuration creates an Issuer in the default namespace that references the ca-key-pair secret for signing certificates.

  3. Apply the configuration.

    console
    $ kubectl apply -f issuer.yaml
    
  4. Verify the Issuer is created.

    console
    $ kubectl get issuers -n default
    

    The output shows ca-issuer with False in the READY column until the referenced secret is created.

Note
Use a ClusterIssuer when you need to issue certificates across multiple namespaces. Use an Issuer when certificates should be scoped to a single namespace for isolation.

Create a Sample Application to Secure with SSL Certificates

Deploy a sample application with an Ingress controller to demonstrate SSL certificate usage. Traefik serves as the Ingress controller to handle incoming HTTPS traffic.

  1. Add the Traefik Helm repository.

    console
    $ helm repo add traefik https://traefik.github.io/charts
    
  2. Update the local Helm repository index.

    console
    $ helm repo update
    
  3. Install Traefik.

    console
    $ helm install traefik traefik/traefik --wait \
      --set ingressRoute.dashboard.enabled=true \
      --set ingressRoute.dashboard.matchRule='Host(`dashboard.localhost`)' \
      --set ingressRoute.dashboard.entryPoints={web}
    
  4. Wait at least 3 minutes for the LoadBalancer to provision, then retrieve the external IP address.

    console
    $ kubectl get services traefik
    

    The output displays the EXTERNAL-IP column with your public IP address. If it shows <pending>, wait a few more minutes.

  5. Log in to your DNS provider (such as Vultr DNS) and create a DNS A record pointing your domain (for example, certman.example.com) to the LoadBalancer's external IP address.

  6. Create a sample-app.yaml file to define the application deployment and service.

    console
    $ nano sample-app.yaml
    
  7. Add the following configuration.

    yaml
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: sample-app
      namespace: default
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: sample-app
      template:
        metadata:
          labels:
            app: sample-app
        spec:
          containers:
            - name: sample-app
              image: hashicorp/http-echo:latest
              args:
                - "-text=Greetings from Vultr!"
              ports:
                - containerPort: 5678
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: sample-app
      namespace: default
    spec:
      selector:
        app: sample-app
      ports:
        - port: 80
          targetPort: 5678
    

    Save and close the file.

    The above configuration deploys a simple HTTP echo application that displays "Greetings from Vultr!" when accessed.

  8. Apply the configuration.

    console
    $ kubectl apply -f sample-app.yaml
    
  9. Verify the deployment is running.

    console
    $ kubectl get deployments -n default
    

    The output shows sample-app with 1/1 in the READY column.

Generate Let's Encrypt SSL Certificates

cert-manager supports Automated Certificate Management Environment (ACME) providers like Let's Encrypt for issuing trusted SSL certificates. The HTTP-01 challenge verifies domain ownership by responding to requests on a well-known path.

  1. Create a letsencrypt-issuer.yaml file.

    console
    $ nano letsencrypt-issuer.yaml
    
  2. Add the following configuration. Replace admin@example.com with your active email address.

    yaml
    apiVersion: cert-manager.io/v1
    kind: ClusterIssuer
    metadata:
      name: letsencrypt-prod
    spec:
      acme:
        email: admin@example.com
        server: https://acme-v02.api.letsencrypt.org/directory
        privateKeySecretRef:
          name: letsencrypt-prod-key
        solvers:
        - http01:
            ingress:
              ingressClassName: traefik
    

    Save and close the file.

    The above configuration creates a ClusterIssuer that:

    • Uses Let's Encrypt as the ACME provider.
    • Stores the account private key in a secret named letsencrypt-prod-key.
    • Uses HTTP-01 challenges with Traefik as the Ingress solver.
  3. Apply the configuration.

    console
    $ kubectl apply -f letsencrypt-issuer.yaml
    
  4. Verify the ClusterIssuer is ready.

    console
    $ kubectl get clusterissuers letsencrypt-prod
    

    The output shows True in the READY column.

  5. Create a certificate.yaml file to request an SSL certificate. Replace certman.example.com with your domain.

    console
    $ nano certificate.yaml
    
  6. Add the following configuration.

    yaml
    apiVersion: cert-manager.io/v1
    kind: Certificate
    metadata:
      name: certman-example-cert
      namespace: default
    spec:
      secretName: certman-example-tls
      dnsNames:
        - certman.example.com
      issuerRef:
        name: letsencrypt-prod
        kind: ClusterIssuer
        group: cert-manager.io
    

    Save and close the file.

  7. Apply the configuration.

    console
    $ kubectl apply -f certificate.yaml
    

    Certificate generation may take up to 3-5 minutes while cert-manager completes the ACME challenge. Wait for the certificate to be ready before proceeding.

  8. Verify the certificate is issued.

    console
    $ kubectl get certificates -n default
    

    The output shows certman-example-cert with True in the READY column.

  9. Create a sample-app-ingress.yaml file to route traffic to the application with TLS.

    console
    $ nano sample-app-ingress.yaml
    
  10. Add the following configuration. Replace certman.example.com with your actual domain.

    yaml
    apiVersion: traefik.io/v1alpha1
    kind: IngressRoute
    metadata:
      name: sample-app-route
      namespace: default
    spec:
      entryPoints:
        - websecure
      routes:
        - match: Host(`certman.example.com`)
          kind: Rule
          services:
            - name: sample-app
              port: 80
      tls:
        secretName: certman-example-tls
    

    Save and close the file.

    The above configuration routes HTTPS traffic for your domain to the sample application using the generated TLS certificate.

  11. Apply the configuration.

    console
    $ kubectl apply -f sample-app-ingress.yaml
    
  12. Open a web browser and navigate to https://certman.example.com. Replace certman.example.com with your configured domain.

    The browser displays "Greetings from Vultr!" with a valid SSL certificate indicated by the lock icon.

Test cert-manager

Validate the cert-manager deployment by testing certificate operations.

  1. Check the certificate status using cmctl.

    console
    $ cmctl status certificate certman-example-cert -n default
    

    The output displays detailed certificate information including the issuer, expiration date, and secret name.

  2. Inspect the certificate details.

    console
    $ kubectl describe certificate certman-example-cert -n default
    

    The output shows the certificate events including issuance and any renewal activity.

  3. Test manual certificate renewal.

    console
    $ cmctl renew certman-example-cert -n default
    
  4. Verify the certificate secret contains the TLS data.

    console
    $ kubectl get secret certman-example-tls -n default -o jsonpath='{.data.tls\.crt}' | base64 -d | openssl x509 -noout -subject -issuer
    

Troubleshooting

The following sections cover common cert-manager issues and their solutions.

Certificate stuck in "Issuing" state

Check the certificate request status and events.

console
$ kubectl describe certificaterequest -n default
$ kubectl get challenges -n default

Common causes include:

  • DNS not propagated: Verify your domain resolves to the LoadBalancer IP using dig certman.example.com.
  • HTTP-01 challenge failing: Verify Traefik is running and port 80 is accessible from the internet.
  • Rate limits: Let's Encrypt enforces rate limits. Check the Let's Encrypt rate limits documentation.

ClusterIssuer shows "Not Ready"

Check the ClusterIssuer status and events.

console
$ kubectl describe clusterissuer letsencrypt-prod

Common causes include:

  • Invalid email address: ACME requires a valid email for account registration.
  • Network issues: Verify the cluster can reach acme-v02.api.letsencrypt.org.

cert-manager pods not starting

Check the pod logs for errors.

console
$ kubectl logs -n cert-manager -l app=cert-manager
$ kubectl logs -n cert-manager -l app=cainjector
$ kubectl logs -n cert-manager -l app=webhook

Common causes include:

  • Insufficient resources: Verify nodes have available CPU and memory.
  • CRD installation issues: Reinstall cert-manager with CRDs using --set crds.enabled=true.

Certificate renewal failures

cert-manager automatically renews certificates 30 days before expiration. Check renewal status.

console
$ cmctl status certificate certman-example-cert -n default

Force a renewal if needed.

console
$ cmctl renew certman-example-cert -n default

Conclusion

You have deployed cert-manager in a Kubernetes cluster and configured it to issue SSL certificates using Let's Encrypt. The setup includes a ClusterIssuer for ACME certificate management, a sample application secured with TLS, and Traefik as the Ingress controller for routing HTTPS traffic. cert-manager automatically renews certificates before expiration, ensuring continuous TLS protection for your applications. For advanced configurations including DNS01 challenges, Vault integration, and certificate policies, refer to the official cert-manager documentation.

Comments