CrtMgr Blog - SSL/TLS Certificate Management Insights

Expert insights on SSL/TLS certificate management, types, deployment, and best practices

Automating SSL Certificates in Kubernetes with cert-manager

Kubernetes cert-manager Automation Let's Encrypt SSL DevOps

If you’ve ever managed SSL certificates in a Kubernetes environment, you probably know it can be a real headache. Manual certificate renewals, Secret configuration, synchronization with Ingress… Fortunately, there’s cert-manager - a tool that automates this entire process and lets you finally sleep peacefully.

What Is cert-manager?

cert-manager is a native Kubernetes controller that automates the management and issuance of TLS certificates from various sources. It works as a Kubernetes API extension, adding Custom Resources that allow you to define certificates declaratively.

Key Advantages

Full Automation

  • Automatic certificate issuance
  • Automatic renewal before expiration
  • Integration with Kubernetes Ingress
  • Zero manual intervention

Support for Multiple Sources

  • Let’s Encrypt (ACME)
  • HashiCorp Vault
  • Venafi
  • Private CA (self-signed)
  • External issuers (custom integrations)

Kubernetes-Native

  • Declarative configuration
  • CRDs (Custom Resource Definitions)
  • kubectl integration
  • GitOps ready

Installing cert-manager

Prerequisites

Before installation, make sure you have:

  • Kubernetes cluster (version 1.22+)
  • kubectl configured
  • Cluster admin rights
  • Helm 3.x (optional, but recommended)

Installation via Helm

The simplest way is to use Helm:

# Add Helm repository
helm repo add jetstack https://charts.jetstack.io
helm repo update

# Install CRDs
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.3/cert-manager.crds.yaml

# Install cert-manager
helm install cert-manager jetstack/cert-manager \
  --namespace cert-manager \
  --create-namespace \
  --version v1.13.3

# Verify installation
kubectl get pods -n cert-manager

Installation via kubectl

If you prefer pure YAML:

# Install cert-manager with all components
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.3/cert-manager.yaml

# Check status
kubectl get pods -n cert-manager
kubectl get crds | grep cert-manager

After installation, you should see three pods:

  • cert-manager
  • cert-manager-cainjector
  • cert-manager-webhook

Configuring Let’s Encrypt

ClusterIssuer vs Issuer

cert-manager offers two types of resources for issuing certificates:

Issuer - works within a single namespace ClusterIssuer - works cluster-wide (recommended)

Configuring ClusterIssuer for Let’s Encrypt

Let’s start with the staging environment (for testing):

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-staging
spec:
  acme:
    # Let's Encrypt staging server (higher limits, test certificates)
    server: https://acme-staging-v02.api.letsencrypt.org/directory
    email: [email protected]  # Your email for notifications
    
    # Secret to store ACME private key
    privateKeySecretRef:
      name: letsencrypt-staging
    
    # HTTP-01 challenge
    solvers:
    - http01:
        ingress:
          class: nginx  # or traefik, depending on Ingress Controller

For production:

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: [email protected]
    privateKeySecretRef:
      name: letsencrypt-prod
    solvers:
    - http01:
        ingress:
          class: nginx

Apply the configuration:

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

# Check status
kubectl get clusterissuer
kubectl describe clusterissuer letsencrypt-prod

Automatic Certificates for Ingress

Basic Ingress Configuration

The simplest way is to add annotations to Ingress:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: myapp-ingress
  annotations:
    cert-manager.io/cluster-issuer: "letsencrypt-prod"
    # Optional: HTTP -> HTTPS redirect
    nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
spec:
  ingressClassName: nginx
  tls:
  - hosts:
    - myapp.example.com
    secretName: myapp-tls  # cert-manager will create this Secret automatically
  rules:
  - host: myapp.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: myapp-service
            port:
              number: 80

After applying:

kubectl apply -f ingress.yaml

# Monitor certificate issuance process
kubectl get certificate
kubectl describe certificate myapp-tls

# Check events
kubectl get events --sort-by='.lastTimestamp'

Manual Certificate Management

You can also create a Certificate resource directly:

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: myapp-cert
  namespace: default
spec:
  secretName: myapp-tls
  issuerRef:
    name: letsencrypt-prod
    kind: ClusterIssuer
  commonName: myapp.example.com
  dnsNames:
  - myapp.example.com
  - www.myapp.example.com
  # Automatic renewal 30 days before expiration
  renewBefore: 720h  # 30 days

DNS-01 Challenge for Wildcard Certificates

For wildcard certificates (*.example.com) you must use DNS-01 challenge:

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-dns
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: [email protected]
    privateKeySecretRef:
      name: letsencrypt-dns
    solvers:
    - dns01:
        cloudflare:
          email: [email protected]
          apiTokenSecretRef:
            name: cloudflare-api-token
            key: api-token

Create Secret with Cloudflare API token:

kubectl create secret generic cloudflare-api-token \
  --from-literal=api-token='your-cloudflare-api-token'

Certificate for wildcard:

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: wildcard-cert
spec:
  secretName: wildcard-tls
  issuerRef:
    name: letsencrypt-dns
    kind: ClusterIssuer
  dnsNames:
  - "example.com"
  - "*.example.com"

Monitoring and Debugging

Checking Certificate Status

# List all certificates
kubectl get certificate --all-namespaces

# Certificate details
kubectl describe certificate myapp-tls

# CertificateRequest status
kubectl get certificaterequest
kubectl describe certificaterequest <name>

# Order status (ACME)
kubectl get order
kubectl describe order <name>

# Challenge status
kubectl get challenge
kubectl describe challenge <name>

cert-manager Logs

# Main controller
kubectl logs -n cert-manager deploy/cert-manager

# Webhook
kubectl logs -n cert-manager deploy/cert-manager-webhook

# CA injector
kubectl logs -n cert-manager deploy/cert-manager-cainjector

# Follow logs in real-time
kubectl logs -n cert-manager deploy/cert-manager -f

Common Issues

Issue 1: Certificate in “Pending” state

# Check CertificateRequest
kubectl get certificaterequest
kubectl describe certificaterequest <name>

# Check Order
kubectl get order
kubectl describe order <name>

Common causes:

  • Incorrect DNS configuration
  • Domain not accessible by Let’s Encrypt
  • Ingress Controller issues

Issue 2: HTTP-01 challenge fails

# Check if challenge pod was created
kubectl get pods | grep cm-acme-http-solver

# Check if Ingress for challenge was created
kubectl get ingress

Solution:

  • Check if domain points to cluster
  • Check if port 80 is open
  • Check Ingress Controller logs

Issue 3: Too many requests (rate limiting)

Let’s Encrypt has limits:

  • 50 certificates per domain weekly
  • 5 duplicates weekly

Solution:

  • Use staging during testing
  • Plan production deployments

Integration with Monitoring

Prometheus Metrics

cert-manager exports metrics:

apiVersion: v1
kind: Service
metadata:
  name: cert-manager-metrics
  namespace: cert-manager
  labels:
    app: cert-manager
spec:
  ports:
  - name: metrics
    port: 9402
    targetPort: 9402
  selector:
    app: cert-manager

ServiceMonitor for Prometheus Operator:

apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: cert-manager
  namespace: cert-manager
spec:
  selector:
    matchLabels:
      app: cert-manager
  endpoints:
  - port: metrics
    interval: 30s

Grafana Dashboard

Import dashboard with ID: 11001 (cert-manager overview)

Key metrics:

  • certmanager_certificate_expiration_timestamp_seconds - expiration date
  • certmanager_certificate_ready_status - readiness status
  • certmanager_http_acme_client_request_count - ACME requests
  • certmanager_controller_sync_call_count - sync calls

Alerting

Example Prometheus alert:

groups:
- name: cert-manager
  rules:
  - alert: CertificateExpiryIn7Days
    expr: (certmanager_certificate_expiration_timestamp_seconds - time()) / 86400 < 7
    for: 1h
    labels:
      severity: warning
    annotations:
      summary: "Certificate {{ $labels.name }} expires in less than 7 days"
      
  - alert: CertificateNotReady
    expr: certmanager_certificate_ready_status == 0
    for: 10m
    labels:
      severity: critical
    annotations:
      summary: "Certificate {{ $labels.name }} is not ready"

Advanced Configurations

Private CA (Private Certificate Authority)

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: private-ca
spec:
  ca:
    secretName: ca-key-pair

Creating CA:

# Generate CA key and certificate
openssl genrsa -out ca.key 4096
openssl req -new -x509 -key ca.key -out ca.crt -days 3650

# Create Secret
kubectl create secret tls ca-key-pair \
  --cert=ca.crt \
  --key=ca.key \
  --namespace=cert-manager

HashiCorp Vault Integration

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: vault-issuer
spec:
  vault:
    server: https://vault.example.com
    path: pki/sign/example-dot-com
    auth:
      kubernetes:
        mountPath: /v1/auth/kubernetes
        role: cert-manager
        secretRef:
          name: cert-manager-vault-token
          key: token

Certificate Policies

Enforcing organizational standards:

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: myapp-cert
spec:
  secretName: myapp-tls
  issuerRef:
    name: letsencrypt-prod
    kind: ClusterIssuer
  dnsNames:
  - myapp.example.com
  # Minimum key length
  privateKey:
    algorithm: RSA
    size: 4096
  # Additional extensions
  usages:
  - digital signature
  - key encipherment
  - server auth
  - client auth

Best Practices

1. Use Namespace for cert-manager

# Everything in dedicated namespace
kubectl create namespace cert-manager

2. Resource Limits

resources:
  limits:
    cpu: 100m
    memory: 128Mi
  requests:
    cpu: 50m
    memory: 64Mi

3. Backup Secrets

# Backup certificates
kubectl get secrets -n default -o yaml > certificates-backup.yaml

# Or use Velero for automatic backups

4. Staging before Production

Always test on Let’s Encrypt staging before production.

5. Monitoring and Alerting

Monitor:

  • Expiration dates
  • Certificate status
  • Renewal errors

6. Documentation

Maintain documentation:

  • List of used issuers
  • Manual renewal procedures (in case of failure)
  • Contacts and escalation

Migration from Existing Certificates

Import Existing Certificate

# Create Secret with existing certificate
kubectl create secret tls existing-cert \
  --cert=path/to/cert.crt \
  --key=path/to/cert.key

# Create Certificate managed by cert-manager
kubectl apply -f - <<EOF
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: existing-cert
spec:
  secretName: existing-cert
  issuerRef:
    name: letsencrypt-prod
    kind: ClusterIssuer
  dnsNames:
  - example.com
EOF

Gradual Migration

  1. Install cert-manager alongside existing solution
  2. Configure ClusterIssuers
  3. Test on staging environment
  4. Migrate individual applications
  5. Monitor and validate
  6. Remove old solution

Case Study: Migrating 50+ Applications

Challenge: Company had 50+ applications in Kubernetes, each with manually managed certificates.

Solution:

  1. cert-manager installation
  2. ClusterIssuers configuration (prod + staging)
  3. Migration script for all Ingress
  4. Automatic annotation addition
  5. Grafana monitoring

Results:

  • 95% reduction in certificate management time
  • 0 incidents with expired certificates in a year
  • Unified approach to SSL/TLS
  • Full renewal automation

Migration script code:

#!/bin/bash
# migrate-ingresses.sh

for ingress in $(kubectl get ingress -A -o json | jq -r '.items[] | "\(.metadata.namespace)/\(.metadata.name)"'); do
  namespace=$(echo $ingress | cut -d/ -f1)
  name=$(echo $ingress | cut -d/ -f2)
  
  echo "Migrating $namespace/$name"
  
  kubectl annotate ingress $name -n $namespace \
    cert-manager.io/cluster-issuer=letsencrypt-prod \
    --overwrite
    
  echo "Done: $namespace/$name"
done

Summary

cert-manager is a must-have for every Kubernetes cluster handling HTTPS traffic. Certificate management automation:

  • Eliminates risk of expired certificates
  • Reduces ops work by 90%+
  • Provides consistent approach to SSL/TLS
  • Integrates with Kubernetes ecosystem

Quick Start - Checklist

  • Install cert-manager via Helm
  • Configure ClusterIssuer for Let’s Encrypt staging
  • Test on sample application
  • Configure production ClusterIssuer
  • Add annotations to Ingress
  • Configure monitoring (Grafana + Prometheus)
  • Set up alerts for expiring certificates
  • Document configuration

Next Steps

  1. Explore external issuers for custom CAs
  2. Integrate with Vault for sensitive environments
  3. Automate certificate backups
  4. Consider certificate policies for compliance

Want to make sure all your certificates are monitored? Try CrtMgr - a simple tool for tracking SSL/TLS certificates with automatic alerts that works perfectly with cert-manager.


Automation is key. With cert-manager and proper monitoring, you’ll never lose sleep over expired certificates again.

Related Articles