cert-manager for Automatic TLS in Kubernetes
cert-manager is a Kubernetes-native certificate management tool that automates provisioning and renewal of TLS certificates. This guide covers Helm installation, configuring Issuers and ClusterIssuers, using Let's Encrypt for free certificates, managing Certificate resources, and implementing automatic TLS for ingress on your VPS and baremetal Kubernetes infrastructure.
Table of Contents
- cert-manager Overview
- Installation
- Issuers and ClusterIssuers
- Certificate Resources
- Let's Encrypt Integration
- Ingress Integration
- Certificate Renewal
- Practical Examples
- Conclusion
cert-manager Overview
What is cert-manager?
cert-manager is a Kubernetes operator that:
- Automates certificate provisioning from CA providers
- Manages certificate renewal
- Injects certificates into secrets
- Validates certificates
- Supports multiple CAs (Let's Encrypt, Vault, HashiCorp, ACME)
Key Components
cert-manager controller: Main controller managing certificates
Webhook: Validates certificate resources
CA Injector: Injects CA certificates into webhooks
Installation
Prerequisites
- Kubernetes v1.16+
- kubectl configured
- Helm 3+
Installing cert-manager with Helm
# Add Helm repository
helm repo add jetstack https://charts.jetstack.io
helm repo update
# Create namespace
kubectl create namespace cert-manager
# Install CRDs
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.1/cert-manager.crds.yaml
# Install cert-manager
helm install cert-manager jetstack/cert-manager \
-n cert-manager \
--set installCRDs=true \
--set global.leaderElection.namespace=cert-manager
Wait for installation:
kubectl rollout status deployment/cert-manager -n cert-manager
kubectl rollout status deployment/cert-manager-webhook -n cert-manager
Verify installation:
kubectl get pods -n cert-manager
kubectl api-resources | grep cert-manager
Helm Configuration
Production values file:
# cert-manager-values.yaml
global:
leaderElection:
namespace: cert-manager
cert-manager:
serviceAccount:
create: true
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 200m
memory: 256Mi
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app.kubernetes.io/name
operator: In
values:
- cert-manager
topologyKey: kubernetes.io/hostname
webhook:
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 200m
memory: 256Mi
cainjector:
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 200m
memory: 256Mi
Install with custom values:
helm install cert-manager jetstack/cert-manager \
-n cert-manager \
--create-namespace \
-f cert-manager-values.yaml
Issuers and ClusterIssuers
Issuer vs ClusterIssuer
Issuer: Namespace-scoped certificate issuer
ClusterIssuer: Cluster-wide certificate issuer
ACME Issuer with Let's Encrypt
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-key
solvers:
- http01:
ingress:
class: nginx
- dns01:
route53:
region: us-east-1
hostedZoneID: Z1234567890ABC
accessKeyID: AKIAIOSFODNN7EXAMPLE
secretAccessKeySecretRef:
name: aws-route53-secret
key: secret-access-key
Staging Let's Encrypt Issuer
For testing (raises rate limit):
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-staging
spec:
acme:
server: https://acme-staging-v02.api.letsencrypt.org/directory
email: [email protected]
privateKeySecretRef:
name: letsencrypt-staging-key
solvers:
- http01:
ingress:
class: nginx
Vault Issuer
For internal certificate management:
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: vault-issuer
spec:
vault:
server: https://vault.example.com:8200
path: pki/sign/kubernetes
auth:
kubernetes:
mountPath: /v1/auth/kubernetes
role: cert-manager
Self-Signed Issuer
For development:
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: self-signed
spec:
selfSigned: {}
Certificate Resources
Certificate Resource
Explicitly request a certificate:
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: my-app-cert
namespace: production
spec:
secretName: my-app-tls
commonName: my-app.example.com
dnsNames:
- my-app.example.com
- www.my-app.example.com
issuerRef:
name: letsencrypt-prod
kind: ClusterIssuer
renewBefore: 720h # Renew 30 days before expiry
duration: 2160h # 90 days validity
Create certificate:
kubectl apply -f certificate.yaml
kubectl get certificate -n production
kubectl describe certificate my-app-cert -n production
Certificate with Wildcard
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: wildcard-cert
namespace: production
spec:
secretName: wildcard-tls
commonName: "*.example.com"
dnsNames:
- "*.example.com"
- example.com
issuerRef:
name: letsencrypt-prod
kind: ClusterIssuer
Certificate with Custom Duration
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: long-lived-cert
spec:
secretName: long-lived-tls
commonName: api.example.com
duration: 8760h # 1 year
renewBefore: 720h # Renew 30 days before expiry
issuerRef:
name: vault-issuer
kind: ClusterIssuer
Let's Encrypt Integration
HTTP-01 Challenge
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-http
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: [email protected]
privateKeySecretRef:
name: letsencrypt-key
solvers:
- http01:
ingress:
class: nginx
DNS-01 Challenge
For wildcard certificates:
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-key
solvers:
- dns01:
cloudflare:
email: [email protected]
apiTokenSecretRef:
name: cloudflare-api-token
key: api-token
Create Cloudflare secret:
kubectl create secret generic cloudflare-api-token \
-n cert-manager \
--from-literal=api-token=YOUR_CLOUDFLARE_API_TOKEN
Ingress Integration
Automatic Certificate Creation
Add annotation to ingress:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-app
namespace: production
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
ingressClassName: nginx
tls:
- hosts:
- my-app.example.com
secretName: my-app-tls
rules:
- host: my-app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-app
port:
number: 80
Multiple Domains
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: multi-domain
namespace: production
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
ingressClassName: nginx
tls:
- hosts:
- example.com
- www.example.com
secretName: example-tls
- hosts:
- api.example.com
secretName: api-tls
rules:
- host: example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web
port:
number: 80
- host: www.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web
port:
number: 80
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: api
port:
number: 8080
Certificate Renewal
Automatic Renewal
cert-manager automatically renews certificates before expiry. Configuration:
spec:
renewBefore: 720h # Renew 30 days before expiry
duration: 2160h # Valid for 90 days
Manual Renewal Trigger
# Delete the certificate to trigger immediate renewal
kubectl delete secret my-app-tls -n production
# cert-manager will recreate the certificate
kubectl get certificate -n production -w
Monitoring Certificate Expiry
# View certificate details
kubectl get certificate -n production -o wide
# Check expiration dates
kubectl get secret -n production -o json | \
jq '.items[] | select(.type=="kubernetes.io/tls") | {name:.metadata.name, cert:.data."tls.crt"}' | \
while read cert; do
echo $cert | jq -r '.cert' | base64 -d | openssl x509 -noout -dates
done
Practical Examples
Example: Complete Ingress with Auto TLS
---
# ClusterIssuer for Let's Encrypt
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-key
solvers:
- http01:
ingress:
class: nginx
---
# Namespace with cert-manager webhook enabled
apiVersion: v1
kind: Namespace
metadata:
name: production
labels:
cert-manager.io/inject-ca-certificates: "true"
---
# Certificate
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: my-app-cert
namespace: production
spec:
secretName: my-app-tls
commonName: my-app.example.com
dnsNames:
- my-app.example.com
- www.my-app.example.com
issuerRef:
name: letsencrypt-prod
kind: ClusterIssuer
renewBefore: 720h
---
# Ingress using certificate
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-app
namespace: production
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
ingressClassName: nginx
tls:
- hosts:
- my-app.example.com
- www.my-app.example.com
secretName: my-app-tls
rules:
- host: my-app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-app
port:
number: 80
- host: www.my-app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-app
port:
number: 80
Example: Wildcard Certificate with DNS Challenge
---
# Cloudflare secret for DNS validation
apiVersion: v1
kind: Secret
metadata:
name: cloudflare-api-token
namespace: cert-manager
type: Opaque
stringData:
api-token: YOUR_CLOUDFLARE_API_TOKEN
---
# ClusterIssuer with DNS 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-key
solvers:
- dns01:
cloudflare:
email: [email protected]
apiTokenSecretRef:
name: cloudflare-api-token
key: api-token
---
# Wildcard certificate
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: wildcard-cert
namespace: production
spec:
secretName: wildcard-tls
commonName: "*.example.com"
dnsNames:
- "*.example.com"
- example.com
issuerRef:
name: letsencrypt-dns
kind: ClusterIssuer
renewBefore: 720h
Conclusion
cert-manager automates TLS certificate management in Kubernetes, eliminating manual certificate renewal and providing automatic provisioning from trusted CAs like Let's Encrypt. By implementing cert-manager with proper Issuer configuration, automatic ingress integration, and renewal policies, you ensure your applications always have valid TLS certificates. Start with a Let's Encrypt staging issuer for testing, migrate to production when comfortable, and monitor certificate expiry dates. The combination of cert-manager and nginx-ingress provides a complete, automated TLS solution for your VPS and baremetal Kubernetes infrastructure.


