DEV Community

Cover image for Deploying cert-manager and Managing SSL Certificates in Kubernetes
Sanskriti Harmukh for Vultr

Posted on with Aashish Chaurasiya • Originally published at docs.vultr.com

Deploying cert-manager and Managing SSL Certificates in Kubernetes

cert-manager is the standard Kubernetes add-on for automated TLS certificate issuance and renewal. It integrates with ACME-based authorities such as Let's Encrypt and exposes Certificates, Issuers, and ClusterIssuers as native Kubernetes resources. This guide installs cert-manager on a multi-node Kubernetes cluster, configures a Let's Encrypt ClusterIssuer, and issues a renewing certificate for a sample Traefik-routed application. By the end, you'll have cert-manager managing trusted HTTPS for any Ingress in your cluster.

Prerequisite: Kubernetes cluster (v1.31+) with 3+ nodes and at least 4 GB RAM per node. kubectl, helm, and cmctl installed on your workstation. A registered domain you can point at the cluster's LoadBalancer.


Install cert-manager

1. Apply the cert-manager CRDs:

$ kubectl apply -f https://github.com/cert-manager/cert-manager/releases/latest/download/cert-manager.crds.yaml
Enter fullscreen mode Exit fullscreen mode

2. Install the cert-manager controller via Helm (without re-installing the CRDs):

$ 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=false
Enter fullscreen mode Exit fullscreen mode

3. Confirm the install is healthy:

$ kubectl get pods -n cert-manager
$ cmctl check api
Enter fullscreen mode Exit fullscreen mode

The cmctl check api command should print The cert-manager API is ready.


Create a Let's Encrypt ClusterIssuer

1. Save the ClusterIssuer manifest:

$ nano letsencrypt-clusterissuer.yaml
Enter fullscreen mode Exit fullscreen mode
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-account-key
    solvers:
      - http01:
          ingress:
            class: traefik
Enter fullscreen mode Exit fullscreen mode

2. Apply it and confirm it's ready:

$ kubectl apply -f letsencrypt-clusterissuer.yaml
$ kubectl get clusterissuer letsencrypt-prod
Enter fullscreen mode Exit fullscreen mode

The READY column should read True.


Deploy a Sample HTTP App Behind Traefik

1. Save the sample app + Ingress manifest:

$ nano sample-app.yaml
Enter fullscreen mode Exit fullscreen mode
apiVersion: apps/v1
kind: Deployment
metadata:
  name: echo
spec:
  replicas: 1
  selector:
    matchLabels: {app: echo}
  template:
    metadata:
      labels: {app: echo}
    spec:
      containers:
        - name: echo
          image: traefik/whoami
          ports:
            - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: echo
spec:
  selector:
    app: echo
  ports:
    - port: 80
      targetPort: 80
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: echo
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
  ingressClassName: traefik
  rules:
    - host: echo.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: echo
                port:
                  number: 80
  tls:
    - hosts:
        - echo.example.com
      secretName: echo-tls
Enter fullscreen mode Exit fullscreen mode

2. Apply the manifest:

$ kubectl apply -f sample-app.yaml
Enter fullscreen mode Exit fullscreen mode

3. Point a DNS A record for echo.example.com at the Traefik LoadBalancer's external IP:

$ kubectl get svc -n traefik
Enter fullscreen mode Exit fullscreen mode

Verify Certificate Issuance

1. Watch the certificate progress:

$ kubectl get certificate
$ kubectl describe certificate echo-tls
Enter fullscreen mode Exit fullscreen mode

After 3–5 minutes the certificate's READY column flips to True.

2. Browse the app:

Open https://echo.example.com — the certificate chain shows Let's Encrypt as the issuer.


Manage Renewals

cert-manager renews certificates automatically 30 days before expiry. To trigger an early renewal:

$ cmctl renew echo-tls
Enter fullscreen mode Exit fullscreen mode

To inspect any certificate's current state:

$ kubectl describe certificate echo-tls
Enter fullscreen mode Exit fullscreen mode

Troubleshooting

  • DNS not propagated: the HTTP-01 challenge fails until the A record resolves. Wait for DNS, then kubectl delete order --all to retry.
  • Rate-limited: Let's Encrypt limits issuance per domain. Use the staging issuer (https://acme-staging-v02.api.letsencrypt.org/directory) while iterating.
  • Ingress class mismatch: solvers[].http01.ingress.class must match the cluster's IngressClass.

Next Steps

cert-manager is issuing trusted TLS for any Ingress in the cluster. From here you can:

  • Add a staging ClusterIssuer for safe rollout of new config changes
  • Move to the DNS-01 solver to issue wildcard certificates
  • Annotate every public Ingress with cert-manager.io/cluster-issuer for one-click HTTPS

For the full guide with additional tips, visit the original article on Vultr Docs.

Top comments (0)