Skip to main content

Command Palette

Search for a command to run...

Part 5: Mastering NGINX Ingress on EKS – Secure Traffic with Cert-Manager & Let’s Encrypt TLS

Updated
6 min read
Part 5: Mastering NGINX Ingress on EKS – Secure Traffic with Cert-Manager & Let’s Encrypt TLS
N

Hi! I'm a highly motivated Security and DevOps professional with 7+ years of combined experience. My expertise bridges penetration testing and DevOps engineering, allowing me to deliver a comprehensive security approach.

In this article, I’m tackling one of the most critical aspects of Kubernetes networking—secure traffic routing—using the NGINX Ingress Controller and Cert-Manager on Amazon EKS.

I’ll walk you through how I secured my applications with Let’s Encrypt TLS certificates (auto-renewed 🎉), and how I managed ingress routing cleanly using Terraform + Helm.

👉 After experimenting with AWS ALB Ingress, I needed more flexibility for complex routes, dev environments, and TLS management—this is where NGINX shined.

💡 If you haven’t followed Part 1–4, start here:

🔗 Part 1: Build a Scalable EKS Cluster from Scratch
🔗 GitHub Repository

⚠️ The Problem I Faced

Once my EKS cluster was running autoscaling smoothly, I hit a bottleneck:

  • I needed custom routing logic for multiple environments

  • TLS termination was too rigid with ALB

  • Let’s Encrypt integration was missing

  • Manual DNS setup became repetitive

I needed a developer-friendly, fully open-source, and automated ingress setup that would just work—across dev, staging, and production.

✅ The Solution: NGINX + Cert-Manager on EKS

To solve this, I used:

  • 🧭 NGINX Ingress Controller – For fine-grained path/host-based routing.

  • 🔐 Cert-Manager – For automatic TLS certificates via Let’s Encrypt.

  • 🧩 Terraform + Helm – For full automation and reproducibility.

Now, every service I deploy can automatically:

  • Get a DNS record

  • Route traffic using an ingress rule

  • Obtain a valid HTTPS certificate (auto-renewed)

Step 1: Deploy NGINX Ingress Controller via Helm

Let’s create a Helm release to install NGINX.

📄 16-nginx-ingress.tf

resource "helm_release" "external_nginx" {
  name = "external"

  repository       = "https://kubernetes.github.io/ingress-nginx"
  chart            = "ingress-nginx"
  namespace        = "ingress"
  create_namespace = true
  version          = "4.10.1"

  values = [file("${path.module}/values/nginx-ingress.yaml")]
  depends_on = [helm_release.aws_lbc]
}

Since we have defined the nginx-ingress.yaml file in the config, we need to create it in the values/ folder.

📄 values/nginx-ingress.yaml

---
controller:
  ingressClassResource:
    name: external-nginx
  service:
    annotations:
      service.beta.kubernetes.io/aws-load-balancer-type: external
      service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip
      service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing

This sets up NGINX with an external AWS Network Load Balancer (NLB) for high performance and low latency.

Step 2: Install Cert-Manager via Terraform

📄 17-cert-manager.tf

resource "helm_release" "cert_manager" {
  name = "cert-manager"

  repository       = "https://charts.jetstack.io"
  chart            = "cert-manager"
  namespace        = "cert-manager"
  create_namespace = true
  version          = "v1.14.5"

  set {
    name  = "installCRDs"
    value = "true"
  }

  depends_on = [helm_release.external_nginx]
}

Now apply it:

terraform apply -auto-approve

Once installed, check the services:

kubectl get services -n ingress

Step 3: Test Basic NGINX Ingress

We’ll create a new namespace and deploy a test app:

📁 08-nginx-ingress-basic/

0-namespace.yaml

---
apiVersion: v1
kind: Namespace
metadata:
  name: 8-example

1-deployment.yaml

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
  namespace: 8-example
spec:
  replicas: 1
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
        - name: myapp
          image: aputra/myapp-195:v2
          ports:
            - name: http
              containerPort: 8080
          resources:
            requests:
              memory: 128Mi
              cpu: 100m
            limits:
              memory: 128Mi
              cpu: 100m

2-service.yaml

---
apiVersion: v1
kind: Service
metadata:
  name: myapp
  namespace: 8-example
spec:
  type: ClusterIP
  ports:
    - port: 8080
      targetPort: http
  selector:
    app: myapp

3-ingress.yaml

---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: myapp
  namespace: 8-example
spec:
  ingressClassName: external-nginx
  rules:
    - host: ex8.neamulkabiremon.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: myapp
                port:
                  number: 8080

Apply the resources:

cd ..
kubectl apply -f 08-nginx-ingress-basic/
kubectl get ingress -n 8-example

Test without a DNS record:

curl -i --header "Host: ex8.neamulkabiremon.com" http://k8s-ingress-external-0872039d57-878cc0426c6e7a6a.elb.us-east-1.amazonaws.com/about

Result: ✅ HTTP 200 means your ingress routing is working!

🔐 Step 4: Enable HTTPS with Cert-Manager

Let’s create a cluster-wide issuer for Let’s Encrypt.

📁 09-https-cert-manager-nginx-ingress/

0-cluster-issuer.yaml

---
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: http-01-production
spec:
  acme:
    email: your-email@devopsbyexample.com
    server: https://acme-v02.api.letsencrypt.org/directory
    privateKeySecretRef:
      name: http-01-production-cluster-issuer
    solvers:
      - http01:
          ingress:
            ingressClassName: external-nginx

1-namespace.yaml

---
apiVersion: v1
kind: Namespace
metadata:
  name: 9-example

2-deployment.yaml

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
  namespace: 9-example
spec:
  replicas: 1
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
        - name: myapp
          image: aputra/myapp-195:v2
          ports:
            - name: http
              containerPort: 8080
          resources:
            requests:
              memory: 128Mi
              cpu: 100m
            limits:
              memory: 128Mi
              cpu: 100m

3-service.yaml

---
apiVersion: v1
kind: Service
metadata:
  name: myapp
  namespace: 9-example
spec:
  type: ClusterIP
  ports:
    - port: 8080
      targetPort: http
  selector:
    app: myapp

Add Ingress with TLS:

4-ingress.yaml

---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: myapp
  namespace: 9-example
  annotations:
    cert-manager.io/cluster-issuer: http-01-production
spec:
  ingressClassName: external-nginx
  rules:
    - host: ex9.neamulkabiremon.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: myapp
                port:
                  number: 8080
  tls:
    - hosts:
        - ex9.neamulkabiremon.com
      secretName: ex9-neamulkabiremon-com

Before applying the deployment, let's check if the certificate manager is running.

kubectl get pods -n cert-manager

Let's apply the configuration.

cd ..
kubectl apply -f 09-https-cert-manager-nginx-ingress
kubectl get ingress -n 9-example

Nginx successfully completed its task and displayed the DNS address. Copy the application's DNS for my app.

k8s-ingress-external-0872039d57-878cc0426c6e7a6a.elb.us-east-1.amazonaws.com

Now we need to create a CNAME record in our domain hosting zone to point to this DNS. Go to Route 53 or wherever your domain hosting zone is, and create a record.

Enter your record name, set the record type to CNAME, and provide the Nginx ingress DNS name in the value section. Make sure there are no typing mistakes because they are case-sensitive.

After creating the CNAME record, it may take a few minutes to update the certificate.

Now, let's wait and see.

Verify TLS:

watch -t kubectl get certificate -n 9-example

Once the certificate status is true, check it using a browser in incognito mode.

https://ex9.neamulkabiremon.com/about

As we can see, the application successfully configured HTTPS, the certificates are valid, and the connection is secure. We used a Let's Encrypt certificate.

✅ You’ll see a valid Let’s Encrypt HTTPS certificate with secure padlock 🔒

P.S. This certificate is valid for 90 days, and the cert-manager will automatically renew it. If it fails, you will receive a warning if you provide an email in the 0-cluster-issuer.yaml config.

✅ Summary: What You’ve Achieved

By the end of this guide, you’ve:

✔️ Installed NGINX Ingress Controller on EKS
✔️ Integrated Cert-Manager with Let’s Encrypt
✔️ Issued and auto-renewed TLS certificates
✔️ Deployed a fully secure, DNS-routed HTTPS app
✔️ Used only Terraform + Helm for full automation

This setup is now your foundation for production-grade traffic management and SSL across all your microservices in EKS.


⏭️ What’s Next?

In Part 6, we’ll cover:

  • 📦 EFS CSI Driver setup

  • 🔐 AWS Secrets Manager integration with EKS workloads

  • 📁 CSI Secrets Store driver for secure secret mounts

📌 Follow me here and on LinkedIn to never miss the next part of the Amazon EKS Production Series!

More from this blog

Untitled Publication

21 posts

Hi, I'm a highly motivated Security and DevOps professional with a combined 7+ years of experience in penetration testing and DevOps engineering. My dual expertise allows me to approach security from