Configure Kubernetes ingress controller with NGINX and SSL certificates using cert-manager

Intermediate 25 min May 22, 2026 105 views
Ubuntu 24.04 Debian 12 AlmaLinux 9 Rocky Linux 9

Set up NGINX Ingress Controller with automated SSL certificate management using cert-manager for secure routing of external traffic to your Kubernetes services. Configure custom domains, SSL automation, and advanced routing rules for production workloads.

Prerequisites

  • Running Kubernetes cluster with kubectl access
  • Cluster admin permissions
  • Helm 3 installed
  • Domain name with DNS management access

What this solves

Kubernetes services run inside your cluster but need a way to receive external traffic. An ingress controller acts as the entry point, routing HTTP and HTTPS requests to the correct services based on domain names and paths. This tutorial sets up NGINX Ingress Controller with cert-manager to automatically provision and renew SSL certificates from Let's Encrypt, giving you secure HTTPS endpoints without manual certificate management.

Prerequisites and cluster setup

You need a running Kubernetes cluster with kubectl access and cluster-admin permissions. Your cluster should have at least 2 CPU cores and 4GB RAM available. External traffic must be able to reach your cluster nodes on ports 80 and 443.

Verify cluster access

Confirm your kubectl configuration and cluster connectivity.

kubectl cluster-info
kubectl get nodes
kubectl get pods --all-namespaces

Check cluster resources

Verify you have sufficient resources for the ingress controller components.

kubectl top nodes
kubectl describe nodes | grep -A 5 "Allocated resources"

Install NGINX Ingress Controller

Add the NGINX Ingress Helm repository

We'll use Helm to install the ingress controller for easier configuration management.

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

Create ingress namespace

Organize ingress components in a dedicated namespace.

kubectl create namespace ingress-nginx

Install NGINX Ingress Controller

Deploy the controller with cloud provider integration for external load balancer provisioning.

helm install ingress-nginx ingress-nginx/ingress-nginx \
  --namespace ingress-nginx \
  --set controller.replicaCount=2 \
  --set controller.nodeSelector."kubernetes\.io/os"=linux \
  --set controller.admissionWebhooks.patch.nodeSelector."kubernetes\.io/os"=linux \
  --set controller.service.type=LoadBalancer \
  --set controller.service.externalTrafficPolicy=Local

Verify ingress controller deployment

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

kubectl get pods -n ingress-nginx
kubectl get services -n ingress-nginx
kubectl logs -n ingress-nginx deployment/ingress-nginx-controller
Note: If using a bare-metal cluster without cloud LoadBalancer support, change controller.service.type to NodePort and expose ports 80 and 443 through your firewall or external load balancer.

Install and configure cert-manager

Add cert-manager Helm repository

Add the Jetstack repository that maintains cert-manager.

helm repo add jetstack https://charts.jetstack.io
helm repo update

Install cert-manager CRDs

Install the Custom Resource Definitions required by cert-manager.

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

Create cert-manager namespace

Create a dedicated namespace for certificate management components.

kubectl create namespace cert-manager

Deploy cert-manager

Install cert-manager with webhook validation and resource monitoring enabled.

helm install cert-manager jetstack/cert-manager \
  --namespace cert-manager \
  --version v1.13.3 \
  --set installCRDs=false \
  --set webhook.timeoutSeconds=4

Verify cert-manager installation

Confirm all cert-manager components are running correctly.

kubectl get pods -n cert-manager
kubectl get apiservices | grep cert-manager
kubectl logs -n cert-manager deployment/cert-manager

Configure Let's Encrypt certificate issuer

Create staging certificate issuer

Start with Let's Encrypt staging environment to test certificate issuance without rate limits.

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 /tmp/letsencrypt-staging.yaml

Create production certificate issuer

Configure the production Let's Encrypt issuer for valid SSL certificates.

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
kubectl apply -f /tmp/letsencrypt-prod.yaml

Verify certificate issuers

Check that both issuers are registered and ready.

kubectl get clusterissuers
kubectl describe clusterissuer letsencrypt-staging
kubectl describe clusterissuer letsencrypt-prod
Warning: Replace admin@example.com with your actual email address. Let's Encrypt uses this for certificate expiration notices and account recovery.

Configure ingress resources with SSL certificates

Deploy a sample application

Create a simple web application to test ingress routing and SSL termination.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: sample-app
  namespace: default
spec:
  replicas: 2
  selector:
    matchLabels:
      app: sample-app
  template:
    metadata:
      labels:
        app: sample-app
    spec:
      containers:
      - name: app
        image: nginx:alpine
        ports:
        - containerPort: 80
        volumeMounts:
        - name: html
          mountPath: /usr/share/nginx/html
      volumes:
      - name: html
        configMap:
          name: sample-app-html
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: sample-app-html
  namespace: default
data:
  index.html: |
    
    
    Sample App
    
      

Hello from Kubernetes!

This application is served through NGINX Ingress with SSL.

--- apiVersion: v1 kind: Service metadata: name: sample-app-service namespace: default spec: selector: app: sample-app ports: - port: 80 targetPort: 80 type: ClusterIP
kubectl apply -f /tmp/sample-app.yaml

Create ingress with SSL certificate

Configure an ingress resource that automatically requests and uses SSL certificates.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: sample-app-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:
    - app.example.com
    secretName: sample-app-tls
  rules:
  - host: app.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: sample-app-service
            port:
              number: 80
kubectl apply -f /tmp/sample-ingress.yaml

Monitor certificate provisioning

Watch cert-manager automatically request and install the SSL certificate.

kubectl get certificates
kubectl get certificaterequests
kubectl describe certificate sample-app-tls
kubectl logs -n cert-manager deployment/cert-manager -f

Test SSL certificate

Verify the certificate was issued and is accessible through HTTPS.

kubectl get secrets sample-app-tls -o yaml
kubectl get ingress sample-app-ingress
curl -k https://app.example.com

Advanced ingress configuration and routing

Configure path-based routing

Route different URL paths to different services within the same domain.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: multi-path-ingress
  namespace: default
  annotations:
    kubernetes.io/ingress.class: nginx
    cert-manager.io/cluster-issuer: letsencrypt-prod
    nginx.ingress.kubernetes.io/rewrite-target: /$2
    nginx.ingress.kubernetes.io/use-regex: "true"
spec:
  tls:
  - hosts:
    - api.example.com
    secretName: api-example-tls
  rules:
  - host: api.example.com
    http:
      paths:
      - path: /v1(/|$)(.*)
        pathType: Prefix
        backend:
          service:
            name: api-v1-service
            port:
              number: 8080
      - path: /v2(/|$)(.*)
        pathType: Prefix
        backend:
          service:
            name: api-v2-service
            port:
              number: 8080
      - path: /health
        pathType: Exact
        backend:
          service:
            name: health-service
            port:
              number: 80
kubectl apply -f /tmp/multi-path-ingress.yaml

Configure rate limiting

Implement rate limiting to protect your services from traffic spikes.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: rate-limited-ingress
  namespace: default
  annotations:
    kubernetes.io/ingress.class: nginx
    cert-manager.io/cluster-issuer: letsencrypt-prod
    nginx.ingress.kubernetes.io/rate-limit-requests: "10"
    nginx.ingress.kubernetes.io/rate-limit-window: "1m"
    nginx.ingress.kubernetes.io/rate-limit-connections: "5"
    nginx.ingress.kubernetes.io/limit-whitelist: "203.0.113.0/24"
spec:
  tls:
  - hosts:
    - secure.example.com
    secretName: secure-example-tls
  rules:
  - host: secure.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: secure-service
            port:
              number: 80
kubectl apply -f /tmp/rate-limited-ingress.yaml

Configure custom headers and CORS

Add security headers and CORS configuration for web applications.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: headers-ingress
  namespace: default
  annotations:
    kubernetes.io/ingress.class: nginx
    cert-manager.io/cluster-issuer: letsencrypt-prod
    nginx.ingress.kubernetes.io/configuration-snippet: |
      add_header X-Content-Type-Options nosniff;
      add_header X-Frame-Options DENY;
      add_header X-XSS-Protection "1; mode=block";
      add_header Strict-Transport-Security "max-age=31536000; includeSubDomains";
    nginx.ingress.kubernetes.io/cors-allow-origin: "https://app.example.com"
    nginx.ingress.kubernetes.io/cors-allow-methods: "GET, POST, PUT, DELETE, OPTIONS"
    nginx.ingress.kubernetes.io/cors-allow-headers: "DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization"
spec:
  tls:
  - hosts:
    - webapp.example.com
    secretName: webapp-example-tls
  rules:
  - host: webapp.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: webapp-service
            port:
              number: 3000
kubectl apply -f /tmp/headers-ingress.yaml

Monitoring and troubleshooting ingress traffic

Enable ingress controller metrics

Configure Prometheus metrics collection for monitoring ingress performance.

helm upgrade ingress-nginx ingress-nginx/ingress-nginx \
  --namespace ingress-nginx \
  --set controller.metrics.enabled=true \
  --set controller.metrics.serviceMonitor.enabled=true \
  --set controller.podAnnotations."prometheus\.io/scrape"="true" \
  --set controller.podAnnotations."prometheus\.io/port"="10254"

View ingress controller logs

Monitor real-time traffic and troubleshoot routing issues through controller logs.

kubectl logs -n ingress-nginx deployment/ingress-nginx-controller -f
kubectl logs -n ingress-nginx deployment/ingress-nginx-controller --previous

Check certificate status

Monitor SSL certificate health and renewal status.

kubectl get certificates --all-namespaces
kubectl describe certificate sample-app-tls
kubectl get events --field-selector reason=Issuing
kubectl get events --field-selector reason=Ready

Test ingress connectivity

Verify ingress routing and SSL termination from inside the cluster.

kubectl run test-pod --image=curlimages/curl --rm -it --restart=Never -- /bin/sh

Inside the pod:

curl -I http://sample-app-service.default.svc.cluster.local curl -k -I https://app.example.com exit

For comprehensive monitoring setup, refer to our guide on setting up Kubernetes monitoring with Prometheus Operator.

Verify your setup

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

Verify cert-manager

kubectl get pods -n cert-manager kubectl get clusterissuers

Check certificates

kubectl get certificates --all-namespaces kubectl get secrets | grep tls

Test HTTPS connectivity

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

Common issues

SymptomCauseFix
Certificate stuck in "Pending"DNS not pointing to ingressVerify A record points to LoadBalancer external IP
502 Bad GatewayService not found or wrong portCheck service exists: kubectl get svc
Rate limit errors in logsLet's Encrypt API limits hitUse staging issuer for testing, then switch to production
SSL certificate not trustedUsing staging certificatesSwitch annotation to letsencrypt-prod issuer
Ingress not getting external IPCloud provider LoadBalancer not provisionedCheck cloud provider quotas and LoadBalancer service logs
Path routing not workingIncorrect regex or path matchingTest with nginx.ingress.kubernetes.io/rewrite-target annotation

Next steps

Running this in production?

Want this handled for you? Setting up ingress and SSL once is straightforward. Keeping certificates renewed, monitoring ingress performance, handling traffic spikes, and managing security policies across environments is the harder part. See how we run infrastructure like this for European SaaS and e-commerce 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.