Setup Kubernetes Ingress NGINX with cert-manager for automated SSL certificates

Intermediate 35 min Apr 11, 2026 26 views
Ubuntu 24.04 Debian 12 AlmaLinux 9 Rocky Linux 9

Learn to deploy NGINX Ingress Controller with cert-manager for automatic SSL certificate provisioning and renewal using Let's Encrypt in production Kubernetes clusters.

Prerequisites

  • Running Kubernetes cluster with kubectl access
  • Domain name with DNS management access
  • LoadBalancer support (cloud) or NodePort access (bare metal)

What this solves

Managing SSL certificates manually in Kubernetes is time-consuming and error-prone. This tutorial shows you how to deploy NGINX Ingress Controller with cert-manager to automatically provision, renew, and manage SSL certificates from Let's Encrypt for your Kubernetes applications.

Step-by-step installation

Install Helm package manager

Helm simplifies Kubernetes application deployment and management. We'll use it to install both NGINX Ingress Controller and cert-manager.

curl https://baltocdn.com/helm/signing.asc | gpg --dearmor | sudo tee /usr/share/keyrings/helm.gpg > /dev/null
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/helm.gpg] https://baltocdn.com/helm/stable/debian/ all main" | sudo tee /etc/apt/sources.list.d/helm-stable-debian.list
sudo apt update
sudo apt install -y helm
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash

Add required Helm repositories

Add the official repositories for NGINX Ingress Controller and cert-manager to your Helm configuration.

helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo add jetstack https://charts.jetstack.io
helm repo update

Install NGINX Ingress Controller

Deploy the NGINX Ingress Controller with LoadBalancer service type for cloud environments or NodePort for bare metal clusters.

kubectl create namespace ingress-nginx
helm install ingress-nginx ingress-nginx/ingress-nginx \
  --namespace ingress-nginx \
  --set controller.replicaCount=2 \
  --set controller.nodeSelector."kubernetes\.io/os"=linux \
  --set defaultBackend.nodeSelector."kubernetes\.io/os"=linux \
  --set controller.admissionWebhooks.patch.nodeSelector."kubernetes\.io/os"=linux

Verify NGINX Ingress Controller deployment

Check that the ingress controller pods are running and the LoadBalancer service has an external IP assigned.

kubectl get pods -n ingress-nginx
kubectl get svc -n ingress-nginx
Note: On bare metal clusters, the LoadBalancer service will show EXTERNAL-IP as pending. Use NodePort service type instead by adding --set controller.service.type=NodePort to the helm install command.

Install cert-manager CRDs

Install the Custom Resource Definitions (CRDs) required by cert-manager before installing the main application.

kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.2/cert-manager.crds.yaml

Install cert-manager

Deploy cert-manager in its own namespace with webhook validation enabled for secure certificate management.

kubectl create namespace cert-manager
helm install cert-manager jetstack/cert-manager \
  --namespace cert-manager \
  --version v1.13.2 \
  --set installCRDs=false \
  --set nodeSelector."kubernetes\.io/os"=linux

Create ClusterIssuer for Let's Encrypt staging

Create a staging ClusterIssuer to test certificate issuance without hitting Let's Encrypt rate limits during development.

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
    solvers:
    - http01:
        ingress:
          class: nginx
kubectl apply -f letsencrypt-staging.yaml

Create ClusterIssuer for Let's Encrypt production

Create a production ClusterIssuer for real SSL certificates. Only use this after testing with the staging issuer.

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-production
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: admin@example.com
    privateKeySecretRef:
      name: letsencrypt-production
    solvers:
    - http01:
        ingress:
          class: nginx
kubectl apply -f letsencrypt-production.yaml
Warning: Replace admin@example.com with your actual email address. Let's Encrypt uses this for certificate expiration notifications.

Configure DNS-01 challenge solver (optional)

For wildcard certificates or when HTTP-01 validation isn't possible, configure DNS-01 challenge solver with your DNS provider. This example shows Cloudflare configuration.

apiVersion: v1
kind: Secret
metadata:
  name: cloudflare-api-token-secret
  namespace: cert-manager
type: Opaque
stringData:
  api-token: your-cloudflare-api-token
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-dns01
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: admin@example.com
    privateKeySecretRef:
      name: letsencrypt-dns01
    solvers:
    - dns01:
        cloudflare:
          apiTokenSecretRef:
            name: cloudflare-api-token-secret
            key: api-token
kubectl apply -f dns01-cloudflare-secret.yaml
kubectl apply -f dns01-clusterissuer.yaml

Create test application with automatic SSL

Deploy a test application with an Ingress resource that automatically provisions an SSL certificate using the staging issuer.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: test-app
  namespace: default
spec:
  replicas: 2
  selector:
    matchLabels:
      app: test-app
  template:
    metadata:
      labels:
        app: test-app
    spec:
      containers:
      - name: nginx
        image: nginx:alpine
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: test-app-service
  namespace: default
spec:
  selector:
    app: test-app
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: test-app-ingress
  namespace: default
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-staging
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
  ingressClassName: nginx
  tls:
  - hosts:
    - test.example.com
    secretName: test-app-tls
  rules:
  - host: test.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: test-app-service
            port:
              number: 80
kubectl apply -f test-app.yaml

Configure automatic certificate renewal monitoring

Create a monitoring setup to track certificate renewal status and alert on failures using Prometheus metrics.

apiVersion: v1
kind: ServiceMonitor
metadata:
  name: cert-manager-metrics
  namespace: cert-manager
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: cert-manager
  endpoints:
  - port: tcp-prometheus-servicemonitor
    interval: 60s
    path: /metrics
kubectl apply -f cert-monitor.yaml

Verify your setup

Check that all components are running correctly and certificates are being issued properly.

# Check NGINX Ingress Controller status
kubectl get pods -n ingress-nginx
kubectl get svc -n ingress-nginx

Check cert-manager status

kubectl get pods -n cert-manager kubectl get clusterissuers

Check certificate status

kubectl get certificates -A kubectl describe certificate test-app-tls

Check certificate request details

kubectl get certificaterequests -A kubectl describe certificaterequest -n default

Test SSL certificate functionality by accessing your application:

curl -H "Host: test.example.com" https://YOUR_INGRESS_IP/ -k -v

Common issues

SymptomCauseFix
Certificate stuck in Pending stateDNS not pointing to ingress IPVerify DNS records point to LoadBalancer external IP
HTTP-01 challenge failsIngress controller not receiving trafficCheck LoadBalancer/NodePort service and firewall rules
cert-manager webhook errorsNetwork policies blocking admission webhooksAllow webhook traffic on port 10250 and DNS resolution
Rate limit errors from Let's EncryptToo many certificate requestsUse staging issuer for testing, wait for rate limit reset
ClusterIssuer not readyInvalid email or ACME server unreachableCheck email format and network connectivity
Ingress shows 404 errorsIngress class not specified correctlyAdd ingressClassName: nginx to Ingress spec

Advanced configuration options

Configure certificate renewal alerts

Set up PrometheusRule to alert when certificates are close to expiry or renewal failures occur.

apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  name: cert-manager-alerts
  namespace: cert-manager
spec:
  groups:
  - name: cert-manager
    rules:
    - alert: CertManagerCertExpirySoon
      expr: certmanager_certificate_expiration_timestamp_seconds - time() < 86400 * 7
      for: 5m
      labels:
        severity: warning
      annotations:
        summary: "Certificate expires soon"
        description: "Certificate {{ $labels.name }} in namespace {{ $labels.namespace }} expires in less than 7 days"
    - alert: CertManagerCertNotReady
      expr: certmanager_certificate_ready_status == 0
      for: 10m
      labels:
        severity: critical
      annotations:
        summary: "Certificate not ready"
        description: "Certificate {{ $labels.name }} in namespace {{ $labels.namespace }} is not ready"
kubectl apply -f cert-alerts.yaml

Configure resource limits and requests

Set appropriate resource limits for cert-manager and NGINX Ingress Controller to ensure stable operation.

# Update cert-manager with resource limits
helm upgrade cert-manager jetstack/cert-manager \
  --namespace cert-manager \
  --reuse-values \
  --set resources.requests.cpu=100m \
  --set resources.requests.memory=128Mi \
  --set resources.limits.cpu=200m \
  --set resources.limits.memory=256Mi

Update ingress-nginx with resource limits

helm upgrade ingress-nginx ingress-nginx/ingress-nginx \ --namespace ingress-nginx \ --reuse-values \ --set controller.resources.requests.cpu=200m \ --set controller.resources.requests.memory=256Mi \ --set controller.resources.limits.cpu=500m \ --set controller.resources.limits.memory=512Mi

Next steps

Automated install script

Run this to automate the entire setup

Need help?

Don't want to manage this yourself?

We handle managed devops services for businesses that depend on uptime. From initial setup to ongoing operations.