Kubernetes Ingress-NGINX & Cert-Manager Automated SSL: Step-by-Step Production Runbook
1. The Architecture: How Automated ACME Zero-Touch SSL Works
Managing SSL/TLS certificates manually across dozens of microservices leads to expired certificates, browser security warnings, and costly production downtime. By pairing Ingress-NGINX with Cert-Manager, Kubernetes automatically communicates with Let's Encrypt using the ACME protocol, verifies domain ownership via an HTTP-01 challenge, provisions an X.509 certificate, stores it safely in a Kubernetes Secret, and renews it automatically 30 days before expiration.
Ingress manifest annotated with cluster-issuer: letsencrypt-prod.
Temporary pod created to serve validation token at /.well-known/acme-challenge/.
ACME servers query public port 80 to verify domain DNS ownership.
X.509 cert stored in K8s Secret; Ingress-NGINX hot-reloads TLS instantly.
2. Step 1: Install Ingress-NGINX Controller
Deploy the official Ingress-NGINX controller using Helm:
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update
# Install Ingress-NGINX
helm upgrade --install ingress-nginx ingress-nginx/ingress-nginx \
--namespace ingress-nginx \
--create-namespace \
--set controller.service.annotations."service\.beta\.kubernetes\.io/aws-load-balancer-type"="nlb" \
--wait
# Verify external IP / LoadBalancer hostname
kubectl get svc -n ingress-nginx ingress-nginx-controller
3. Step 2: Install Jetstack Cert-Manager with CRDs
Install Cert-Manager into the cert-manager namespace with Custom Resource Definitions enabled:
helm repo add jetstack https://charts.jetstack.io
helm repo update
helm upgrade --install cert-manager jetstack/cert-manager \
--namespace cert-manager \
--create-namespace \
--version v1.15.3 \
--set crds.enabled=true \
--wait
# Verify pods running
kubectl get pods -n cert-manager
4. Step 3: Configure Let's Encrypt ClusterIssuer
A ClusterIssuer is a cluster-wide resource that can issue certificates in any Kubernetes namespace:
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: devops@yourdomain.com
privateKeySecretRef:
name: letsencrypt-prod-account-key
solvers:
- http01:
ingress:
class: nginx
Apply the manifest and verify the ClusterIssuer status is Ready: True:
kubectl apply -f cluster-issuer.yaml
kubectl get clusterissuer letsencrypt-prod
5. Step 4: Create TLS-Secured Ingress Resource
Annotate your application Ingress to trigger automatic certificate issuance:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: production-api-ingress
namespace: default
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/proxy-body-size: "20m"
spec:
ingressClassName: nginx
tls:
- hosts:
- api.yourdomain.com
secretName: api-tls-cert
rules:
- host: api.yourdomain.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: api-service
port:
number: 80
6. Step 5: Verification & Smoke Testing
Inspect certificate status and confirm issuance:
# Check certificate generation progress
kubectl get certificate api-tls-cert -o wide
# Check CertificateRequest, Order, and Challenge if debugging
kubectl get certificaterequest
kubectl get orders
kubectl get challenges
# Verify live TLS handshake
curl -Iv https://api.yourdomain.com
7. Production Troubleshooting Runbook
- Challenge Fails with 404 Not Found: The DNS record for
api.yourdomain.commust point directly to the LoadBalancer of Ingress-NGINX. If DNS does not resolve to the cluster, Let's Encrypt cannot verify the token. - Rate Limit Exceeded: Let's Encrypt Production has a limit of 5 failed validations per hour per account. Use
letsencrypt-stagingfor testing. - Ingress Class Mismatch: In Kubernetes 1.18+, ensure
spec.ingressClassName: nginxmatches the ClusterIssuer HTTP01 ingress class.