Setup Kubernetes ingress controller with cert-manager and Helm for automated SSL certificate management

Intermediate 25 min Apr 21, 2026 110 views
Ubuntu 24.04 Debian 12 AlmaLinux 9 Rocky Linux 9

Deploy NGINX Ingress Controller and cert-manager using Helm to automatically provision and manage SSL certificates for your Kubernetes applications with Let's Encrypt integration.

Prerequisites

  • Running Kubernetes cluster with kubectl access
  • Administrative privileges on cluster
  • Domain names with DNS pointing to cluster IP
  • 2+ CPU cores and 4GB RAM available

What this solves

Running web applications on Kubernetes requires external access and SSL certificates. This tutorial sets up NGINX Ingress Controller to route traffic to your services and cert-manager to automatically provision, renew, and manage SSL certificates from Let's Encrypt. You'll have a production-ready ingress setup that handles HTTPS termination and certificate lifecycle management without manual intervention.

Prerequisites

You need a working Kubernetes cluster with kubectl configured and administrative access. Your cluster should have at least 2 CPU cores and 4GB RAM available for the ingress components. DNS records for your domains must point to your cluster's external IP address.

Step-by-step installation

Install Helm package manager

Helm simplifies Kubernetes application deployment and management. Install the latest version and verify it can communicate with your cluster.

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 -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3
chmod 700 get_helm.sh
./get_helm.sh

Verify Helm installation and cluster connectivity:

helm version
kubectl cluster-info

Add required Helm repositories

Add the official repositories for NGINX Ingress Controller and cert-manager. These repos contain the pre-configured charts for easy deployment.

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

Create dedicated namespaces

Separate namespaces provide isolation and make resource management easier. Create namespaces for the ingress controller and certificate management.

kubectl create namespace ingress-nginx
kubectl create namespace cert-manager

Install NGINX Ingress Controller

Deploy the ingress controller with optimized settings for production use. This creates a LoadBalancer service that provides external access to your cluster.

helm install ingress-nginx ingress-nginx/ingress-nginx \
  --namespace ingress-nginx \
  --set controller.metrics.enabled=true \
  --set controller.podSecurityContext.runAsUser=101 \
  --set controller.podSecurityContext.runAsNonRoot=true \
  --set controller.service.type=LoadBalancer \
  --set controller.config.use-forwarded-headers="true" \
  --set controller.config.compute-full-forwarded-for="true"

Wait for ingress controller deployment

The ingress controller needs time to start and obtain an external IP address. Monitor the deployment status and note the external IP when available.

kubectl get pods -n ingress-nginx
kubectl get services -n ingress-nginx --watch

Wait until you see an external IP assigned to the ingress-nginx-controller service. Press Ctrl+C to stop watching once the IP appears.

Install cert-manager

Install cert-manager to handle automatic SSL certificate provisioning and renewal. This includes custom resource definitions for certificate management.

helm install cert-manager jetstack/cert-manager \
  --namespace cert-manager \
  --set crds.enabled=true \
  --set prometheus.enabled=true \
  --set webhook.timeoutSeconds=4

Verify cert-manager installation

Check that all cert-manager components are running properly before proceeding to certificate configuration.

kubectl get pods -n cert-manager
kubectl get crd | grep cert-manager

Create Let's Encrypt cluster issuer

Configure cert-manager to use Let's Encrypt for certificate issuance. Replace the email address with your own for certificate notifications.

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
    privateKeySecretRef:
      name: letsencrypt-prod
    solvers:
    - http01:
        ingress:
          class: nginx
---
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

Apply the issuer configuration:

kubectl apply -f /tmp/letsencrypt-issuer.yaml

Configure ingress with SSL certificates

Create a sample application

Deploy a test application to verify the ingress and certificate setup works correctly.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello-world
  namespace: default
spec:
  replicas: 2
  selector:
    matchLabels:
      app: hello-world
  template:
    metadata:
      labels:
        app: hello-world
    spec:
      containers:
      - name: hello-world
        image: nginxdemos/hello
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: hello-world
  namespace: default
spec:
  selector:
    app: hello-world
  ports:
  - port: 80
    targetPort: 80
  type: ClusterIP
kubectl apply -f /tmp/sample-app.yaml

Create ingress resource with TLS

Configure an ingress resource that automatically provisions SSL certificates. Replace example.com with your actual domain name.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: hello-world-ingress
  namespace: default
  annotations:
    kubernetes.io/ingress.class: nginx
    cert-manager.io/cluster-issuer: letsencrypt-staging
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
spec:
  tls:
  - hosts:
    - hello.example.com
    secretName: hello-world-tls
  rules:
  - host: hello.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: hello-world
            port:
              number: 80
kubectl apply -f /tmp/sample-ingress.yaml

Monitor certificate provisioning

Watch cert-manager automatically request and provision the SSL certificate. This process typically takes 1-2 minutes.

kubectl get certificates -n default
kubectl describe certificate hello-world-tls -n default
kubectl get certificaterequests -n default

Set up monitoring and alerting

Enable ingress controller metrics

Configure monitoring endpoints for the ingress controller to track request metrics and performance.

kubectl get service -n ingress-nginx ingress-nginx-controller-metrics
kubectl port-forward -n ingress-nginx service/ingress-nginx-controller-metrics 10254:10254 &

Test metrics endpoint:

curl http://localhost:10254/metrics | head -20
pkill -f "kubectl port-forward"

Create certificate monitoring

Set up monitoring for certificate expiration and renewal status to ensure continuous HTTPS availability.

apiVersion: v1
kind: ConfigMap
metadata:
  name: cert-monitor-script
  namespace: cert-manager
data:
  monitor.sh: |
    #!/bin/bash
    echo "Certificate Status Summary:"
    kubectl get certificates --all-namespaces -o custom-columns="NAMESPACE:.metadata.namespace,NAME:.metadata.name,READY:.status.conditions[0].status,AGE:.metadata.creationTimestamp"
    echo "\nCertificate Details:"
    kubectl get certificates --all-namespaces -o yaml | grep -E "(name:|notAfter:|issuerRef:)"
---
apiVersion: batch/v1
kind: CronJob
metadata:
  name: cert-monitor
  namespace: cert-manager
spec:
  schedule: "0 8   *"
  jobTemplate:
    spec:
      template:
        spec:
          serviceAccountName: cert-manager
          containers:
          - name: monitor
            image: bitnami/kubectl:latest
            command: ["/bin/sh"]
            args: ["/scripts/monitor.sh"]
            volumeMounts:
            - name: script-volume
              mountPath: /scripts
          volumes:
          - name: script-volume
            configMap:
              name: cert-monitor-script
              defaultMode: 0755
          restartPolicy: OnFailure
kubectl apply -f /tmp/cert-monitor.yaml

Verify your setup

Test that your ingress controller and certificate management are working correctly:

# Check ingress controller status
kubectl get pods -n ingress-nginx
kubectl get services -n ingress-nginx

Verify cert-manager components

kubectl get pods -n cert-manager kubectl get clusterissuers

Check certificate status

kubectl get certificates --all-namespaces kubectl describe certificate hello-world-tls -n default

Test SSL endpoint (replace with your domain)

curl -I https://hello.example.com openssl s_client -connect hello.example.com:443 -servername hello.example.com

Configure multiple domains and services

Create multi-service ingress

Configure ingress to handle multiple applications and domains with individual SSL certificates.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: multi-service-ingress
  namespace: default
  annotations:
    kubernetes.io/ingress.class: nginx
    cert-manager.io/cluster-issuer: letsencrypt-prod
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    nginx.ingress.kubernetes.io/rewrite-target: /$1
spec:
  tls:
  - hosts:
    - api.example.com
    secretName: api-example-tls
  - hosts:
    - app.example.com
    secretName: app-example-tls
  rules:
  - host: api.example.com
    http:
      paths:
      - path: /(.*)
        pathType: Prefix
        backend:
          service:
            name: api-service
            port:
              number: 8080
  - host: app.example.com
    http:
      paths:
      - path: /(.*)
        pathType: Prefix
        backend:
          service:
            name: web-service
            port:
              number: 80

Troubleshooting and maintenance

Configure log monitoring

Set up log collection for ingress controller debugging and traffic analysis.

# View ingress controller logs
kubectl logs -n ingress-nginx deployment/ingress-nginx-controller -f

Check cert-manager logs

kubectl logs -n cert-manager deployment/cert-manager -f

Monitor certificate challenges

kubectl get challenges --all-namespaces kubectl describe challenge -n default

Set up automatic certificate renewal testing

Create a job to periodically test certificate renewal processes and verify ACME challenge handling.

apiVersion: batch/v1
kind: Job
metadata:
  name: cert-renewal-test
  namespace: cert-manager
spec:
  template:
    spec:
      serviceAccountName: cert-manager
      containers:
      - name: cert-test
        image: bitnami/kubectl:latest
        command: ["/bin/sh", "-c"]
        args:
        - |
          echo "Testing certificate renewal..."
          kubectl get certificates --all-namespaces
          kubectl get certificaterequests --all-namespaces
          kubectl get challenges --all-namespaces
          echo "Certificate test completed"
      restartPolicy: Never
  backoffLimit: 3
kubectl apply -f /tmp/cert-test.yaml

Common issues

SymptomCauseFix
Certificate stuck in pendingDNS not pointing to ingress IPVerify DNS records with nslookup domain.com
502 Bad Gateway errorsBackend service not reachableCheck service endpoints with kubectl get endpoints
Let's Encrypt rate limitingToo many certificate requestsUse staging issuer for testing, wait for rate limit reset
ACME challenge failuresIngress controller not routing challengesCheck ingress controller logs and verify .well-known/acme-challenge path
Certificate not auto-renewingcert-manager webhook issuesRestart cert-manager pods: kubectl rollout restart -n cert-manager deployment/cert-manager
External IP not assignedLoadBalancer service pendingCheck cloud provider load balancer quota and configuration

Next steps

Running this in production?

Want this handled for you? Setting this up once is straightforward. Keeping it patched, monitored, backed up and performant across environments is the harder part. See how we run infrastructure like this for European teams.

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.