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

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.
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
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!




