Skip to main content

cert-manager

Purpose: Automated TLS certificate management

Version: 1.17.2

Namespace: cert-manager

Description

Manages certificates from Let's Encrypt using DNS-01 challenges with Cloudflare. Automates certificate issuance and renewal.

Key features:

  • Automatic certificate issuance from Let's Encrypt
  • DNS-01 challenge validation via Cloudflare API
  • Automatic renewal (30 days before expiry)
  • Kubernetes Secret storage
  • Ingress integration via annotations

Installation

Installed via ArgoCD from Helm chart: jetstack/cert-manager

Configuration file: cluster/dev/config.yaml

Configuration

ClusterIssuer

The ClusterIssuer is created automatically during bootstrap:

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: admin@ssdk8s.xyz
privateKeySecretRef:
name: letsencrypt-prod
solvers:
- dns01:
cloudflare:
apiTokenSecretRef:
name: cloudflare-api-token
key: api-token

Cloudflare Secret

The Cloudflare API token secret is created by the bootstrap script:

kubectl create secret generic cloudflare-api-token \
-n cert-manager \
--from-literal=api-token=YOUR_CF_API_TOKEN
warning

The Cloudflare API token must have minimal permissions: Zone:DNS:Edit only. Do not use a token with broader permissions.

Usage

Annotations for Ingress

All ingresses should use cert-manager for TLS:

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

All ingresses MUST use cert-manager for TLS. Do not use ssl-passthrough unless absolutely required.

Certificate Resource (Optional)

For more control, create a Certificate resource:

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: my-app-tls
namespace: default
spec:
secretName: my-app-tls
issuerRef:
name: letsencrypt-prod
kind: ClusterIssuer
dnsNames:
- myapp.ssdk8s.xyz
duration: 2160h # 90 days
renewBefore: 720h # 30 days before expiry

Components

ComponentDeploymentPurpose
cert-managercert-managerMain controller
cert-manager-cainjectorcert-manager-cainjectorInjects CA certs into CRDs
cert-manager-webhookcert-manager-webhookValidation webhook for Certificate resources

Certificate Lifecycle

PropertyValue
IssuerLet's Encrypt Production
Duration90 days (default)
RenewalAutomatically at 30 days before expiry
ChallengeDNS-01 via Cloudflare API
Key AlgorithmRSA 2048-bit
Rate Limits50 certificates per week per registered domain

DNS-01 Challenge Flow

Troubleshooting

Check Certificate Status

# List all certificates
kubectl get certificates -A

# Describe specific certificate
kubectl describe certificate <name> -n <namespace>

# Check certificate events
kubectl get events -n cert-manager --field-selector involvedObject.kind=Certificate

Check Challenge Status

# List all challenges
kubectl get challenges -A

# Describe specific challenge
kubectl describe challenge <name> -n <namespace>

# Check challenge logs
kubectl logs -n cert-manager -l app.kubernetes.io/name=cert-manager | grep -i challenge

Check Order Status

# List certificate orders
kubectl get certificateorders -A

# Describe order
kubectl describe certificateorder <name> -n <namespace>

Check Issuer Status

kubectl get clusterissuer letsencrypt-prod -o yaml
kubectl describe clusterissuer letsencrypt-prod

Common Issues

  1. Cloudflare API token invalid

    • Verify token has Zone:DNS:Edit permissions
    • Check token hasn't expired
    • Ensure correct zone is being used
  2. DNS propagation delay

    • Wait 2-5 minutes for TXT record propagation
    • Check with: dig TXT _acme-challenge.yourdomain.com
  3. Rate limiting

    • Let's Encrypt limits: 50 certs/week per domain
    • Use staging issuer for testing: https://acme-staging-v02.api.letsencrypt.org/directory
  4. Certificate not renewing

    • Check renewBefore setting (should be ~30 days)
    • Verify cert-manager pods are running
    • Check cert-manager logs for errors
  5. Webhook connection refused

    • Check webhook pod status: kubectl get pods -n cert-manager -l app.kubernetes.io/name=cert-manager-webhook
    • Verify webhook service: kubectl get svc -n cert-manager cert-manager-webhook

Debug Commands

# Check cert-manager pods
kubectl get pods -n cert-manager

# View cert-manager logs
kubectl logs -n cert-manager -l app.kubernetes.io/name=cert-manager --tail=100

# View webhook logs
kubectl logs -n cert-manager -l app.kubernetes.io/name=cert-manager-webhook --tail=50

# Check DNS resolution
dig TXT _acme-challenge.yourdomain.com @8.8.8.8

# Test Cloudflare API (replace TOKEN and ZONE_ID)
curl -X GET "https://api.cloudflare.com/client/v4/zones/ZONE_ID/dns_records" \
-H "Authorization: Bearer TOKEN"

References