📚Book Signing at KubeCon EU 2026Meet us at Booking.com HQ (Mon 18:30-21:00) & vCluster booth #521 (Tue 24 Mar, 12:30-1:30pm) — free book giveaway!RSVP Booking.com Event
Security intermediate ⏱ 20 minutes K8s 1.21+

K8s Let's Encrypt Ingress with cert-manager

Automate TLS certificates for Kubernetes Ingress using cert-manager and Let's Encrypt. ClusterIssuer setup, HTTP-01 and DNS-01 challenges, and auto-renewal.

By Luca Berton 📖 5 min read

💡 Quick Answer: Install cert-manager, create a ClusterIssuer with Let’s Encrypt, annotate your Ingress with cert-manager.io/cluster-issuer, and cert-manager automatically provisions and renews TLS certificates.

The Problem

You need HTTPS for your Kubernetes services but:

  • Manually managing TLS certificates doesn’t scale
  • Certificates expire and cause outages if not renewed
  • Let’s Encrypt requires ACME challenge automation
  • Different ingress controllers need different configurations

The Solution

Step 1: Install cert-manager

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

# Verify installation
kubectl get pods -n cert-manager
# cert-manager-xxx           Running
# cert-manager-cainjector    Running
# cert-manager-webhook       Running

Or with Helm:

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=true

Step 2: Create ClusterIssuer (Let’s Encrypt)

# letsencrypt-prod.yaml
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: admin@example.com  # Your email for expiry notifications
    privateKeySecretRef:
      name: letsencrypt-prod-key
    solvers:
      - http01:
          ingress:
            class: nginx  # Match your ingress controller
---
# Start with staging to avoid rate limits during testing
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-staging
spec:
  acme:
    server: https://acme-staging-v02.api.letsencrypt.org/directory
    email: admin@example.com
    privateKeySecretRef:
      name: letsencrypt-staging-key
    solvers:
      - http01:
          ingress:
            class: nginx
kubectl apply -f letsencrypt-prod.yaml
kubectl get clusterissuer
# NAME                 READY   AGE
# letsencrypt-prod     True    30s
# letsencrypt-staging  True    30s

Step 3: Create Ingress with TLS

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: myapp
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
  ingressClassName: nginx
  tls:
    - hosts:
        - app.example.com
      secretName: app-example-com-tls  # cert-manager creates this
  rules:
    - host: app.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: myapp
                port:
                  number: 80

Architecture

sequenceDiagram
    participant I as Ingress
    participant CM as cert-manager
    participant LE as Let's Encrypt
    participant DNS as DNS/HTTP

    I->>CM: Ingress with annotation created
    CM->>CM: Create Certificate resource
    CM->>LE: Request certificate (ACME)
    LE->>CM: HTTP-01 challenge token
    CM->>I: Create challenge solver pod/ingress
    LE->>DNS: Verify challenge (GET /.well-known/acme-challenge/xxx)
    DNS->>LE: Challenge response ✓
    LE->>CM: Issue certificate
    CM->>I: Store cert in TLS Secret
    Note over CM: Auto-renews 30 days before expiry

DNS-01 Challenge (Wildcard Certificates)

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-dns
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: admin@example.com
    privateKeySecretRef:
      name: letsencrypt-dns-key
    solvers:
      - dns01:
          cloudflare:
            email: admin@example.com
            apiTokenSecretRef:
              name: cloudflare-api-token
              key: api-token
---
# Wildcard certificate
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: wildcard-example-com
  namespace: default
spec:
  secretName: wildcard-example-com-tls
  issuerRef:
    name: letsencrypt-dns
    kind: ClusterIssuer
  dnsNames:
    - "*.example.com"
    - example.com

Verify Certificate Status

# Check certificate status
kubectl get certificate
# NAME                   READY   SECRET                  AGE
# app-example-com-tls    True    app-example-com-tls     5m

# Detailed status
kubectl describe certificate app-example-com-tls

# Check the actual secret
kubectl get secret app-example-com-tls -o jsonpath='{.data.tls\.crt}' | base64 -d | openssl x509 -text -noout | head -20

# Check cert-manager logs
kubectl logs -n cert-manager -l app=cert-manager --tail=50

Common Issues

IssueCauseFix
Certificate stuck “Issuing”HTTP-01 challenge failingCheck ingress class, firewall port 80
”rate limited” errorToo many requests to prodUse staging first, then switch to prod
Challenge pod not createdWrong ingress class in solverMatch ingress.class to your controller
DNS-01 timeoutAPI token wrong or propagation slowVerify DNS credentials, increase timeout
Certificate not auto-renewingcert-manager pod crashedCheck cert-manager namespace pods
”no matching HTTP01 solver”Solver selector too restrictiveUse broader selector or match domain

Best Practices

  1. Start with staging issuer — switch to production after testing
  2. Use DNS-01 for wildcards — HTTP-01 can’t issue wildcard certs
  3. Monitor certmanager_certificate_expiration_timestamp_seconds — alert at <14 days
  4. One secret per domain — don’t share TLS secrets across namespaces
  5. Set email on ClusterIssuer — Let’s Encrypt sends 20-day expiry warnings

Key Takeaways

  • cert-manager automates the entire TLS lifecycle — provision, validate, renew
  • HTTP-01 is simplest (needs port 80 open); DNS-01 needed for wildcards
  • Annotate Ingress with cert-manager.io/cluster-issuer — cert-manager handles the rest
  • Certificates auto-renew 30 days before expiry — zero manual intervention
  • Always test with staging Let’s Encrypt first to avoid rate limits (5 certs/domain/week)
#cert-manager #letsencrypt #tls #ingress #certificates #security
Luca Berton
Written by Luca Berton

Principal Solutions Architect specializing in Kubernetes, AI/GPU infrastructure, and cloud-native platforms. Author of Kubernetes Recipes and creator of CopyPasteLearn courses.

Kubernetes Recipes book cover

Want More Kubernetes Recipes?

This recipe is from Kubernetes Recipes, our 750-page practical guide with hundreds of production-ready patterns.

Luca Berton Ansible Pilot Ansible by Example Open Empower K8s Recipes Terraform Pilot CopyPasteLearn ProteinLens