ALB Ingress vs. NGINX Ingress on EKS — When to Use Which and How to Set Up Both with Terraform

Routing external traffic into your Amazon EKS cluster can feel overwhelming given the number of options available. When building out your Kubernetes infrastructure, you’ll inevitably run into a critical decision: should you use the AWS-native ALB Ingress Controller (via the AWS Load Balancer Controller) or a third-party solution like the NGINX Ingress Controller?
In this post, we’ll break down both approaches, examine the cost and architectural trade-offs, and look at the actual Terraform and Kubernetes manifests to deploy them. By the end, you’ll understand the architectural differences between ALB and NGINX Ingress, see the full progression from a basic LoadBalancer Service all the way to NGINX with automated Let’s Encrypt TLS, and know exactly when to choose which approach.
Prerequisites
- An active EKS cluster.
- Terraform installed and configured with AWS credentials.
kubectland basic knowledge of Kubernetes manifests.- A registered domain name (if following along with the HTTPS/TLS sections).
The Core Concept: Two Different Routing Patterns
Think of your EKS cluster as a secured office building. You need a way to route visitors (traffic) to the right offices (services).
- The ALB Approach (AWS Native): You build a dedicated front desk (an Application Load Balancer) for each company in the building. AWS handles the front desk management completely.
- The NGINX Approach (Third-Party): You build one main entrance (a Network Load Balancer) that funnels everyone to a highly efficient internal mailroom (NGINX), which then routes traffic to the correct offices based on the visitor’s destination.
Let’s look at how we get there, starting from the most basic setup.
Step 1: The Basic LoadBalancer Service
Before diving into Ingress, it’s worth understanding the baseline. If you expose a Service of type LoadBalancer, the AWS Load Balancer Controller can provision a Network Load Balancer (NLB) for that specific service.
# nlb-service/2-service.yaml
apiVersion: v1
kind: Service
metadata:
name: myapp
namespace: 5-example
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
spec:
type: LoadBalancer
ports:
- port: 8080
targetPort: http
selector:
app: myapp
[!NOTE]
While this works, provisioning an NLB for every service gets expensive quickly. This is exactly the problem Ingress aims to solve by doing host/path-based routing.
Approach A: AWS Load Balancer Controller (ALB Ingress)
The AWS Load Balancer Controller creates an AWS Application Load Balancer (ALB) when you create a Kubernetes Ingress resource.
Setting up the Controller with Terraform
First, we install the controller using Helm and give it the necessary IAM permissions via IRSA (IAM Roles for Service Accounts).
# terraform/15-aws-lbc.tf
resource "helm_release" "aws_lbc" {
name = "aws-load-balancer-controller"
repository = "https://aws.github.io/eks-charts"
chart = "aws-load-balancer-controller"
namespace = "kube-system"
version = "1.7.2"
set = [
{
name = "clusterName"
value = aws_eks_cluster.eks.name
},
{
name = "serviceAccount.name"
value = "aws-load-balancer-controller"
}
# ... VPC and region config omitted for brevity
]
}
ALB HTTP Ingress
To route traffic, we define an Ingress object using the alb ingress class.
# alb-ingress-http/3-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: myapp
namespace: 6-example
annotations:
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/target-type: ip
alb.ingress.kubernetes.io/healthcheck-path: /health
spec:
ingressClassName: alb
rules:
- host: ex6.${DOMAIN_NAME}
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: myapp
port:
number: 8080
Upgrading to HTTPS (with ACM)
For production, you need TLS. The beauty of the AWS-native approach is its seamless integration with AWS Certificate Manager (ACM). You simply add a few annotations:
# alb-ingress-https/3-ingress.yaml
annotations:
alb.ingress.kubernetes.io/certificate-arn: arn:aws:acm:us-east-2:424432388155:certificate/7f32327d-ad95-4977-91c2-8fae85e9e598
alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS":443}]'
alb.ingress.kubernetes.io/ssl-redirect: "443"
AWS handles TLS termination at the ALB. It’s incredibly easy to manage, but here is the catch: by default, AWS creates one ALB per Ingress resource. If you have 20 microservices with their own Ingress objects, you pay for 20 ALBs.
Approach B: NGINX Ingress Controller
To save costs and gain more granular routing features, many teams use NGINX Ingress. Here, you provision a single Network Load Balancer (NLB) that passes all L4 traffic to NGINX pods running inside your cluster. NGINX then inspects the L7 HTTP(S) traffic and routes it.
Setting up NGINX with Terraform
We deploy NGINX using Helm, and we pass a custom values.yaml to tell the AWS Load Balancer Controller to provision an NLB for NGINX’s entrypoint Service.
# terraform/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")]
}
The magic happens in the values file, where we configure the annotations for the NLB:
# terraform/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
NGINX Ingress Manifest
Now, when you create an Ingress, you just point to the external-nginx class. No new load balancer is created by AWS; NGINX just updates its internal configuration.
# nginx-ingress/3-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: myapp
namespace: 8-example
spec:
ingressClassName: external-nginx
rules:
- host: ex8.${DOMAIN_NAME}
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: myapp
port:
number: 8080
Adding TLS: NGINX + Cert-Manager
Since we aren’t using an ALB, we can’t easily use ACM for TLS termination. Instead, we use cert-manager to automate Let’s Encrypt certificates directly inside the cluster.
First, install cert-manager via Terraform:
# 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"
}
Then, configure a ClusterIssuer to solve the ACME challenge using NGINX:
# cert-manager-nginx/0-cluster-issuer.yaml
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: http-01-production
spec:
acme:
email: ${ACME_EMAIL}
server: https://acme-v02.api.letsencrypt.org/directory
privateKeySecretRef:
name: http-01-production-cluster-issuer
solvers:
- http01:
ingress:
ingressClassName: external-nginx
Finally, annotate your Ingress to request a certificate. Cert-manager will automatically provision a Kubernetes Secret containing the TLS cert!
# cert-manager-nginx/4-ingress.yaml
metadata:
annotations:
cert-manager.io/cluster-issuer: http-01-production
spec:
ingressClassName: external-nginx
rules:
- host: ${DOMAIN_NAME}
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: myapp
port:
number: 8080
tls:
- hosts:
- ${DOMAIN_NAME}
secretName: ${DOMAIN_NAME}
Gotchas and Practical Tips
-
You can group ALB Ingresses to save costs. By default, AWS creates one ALB per Ingress resource. You can group multiple Ingress resources into a single ALB using the
alb.ingress.kubernetes.io/group.nameannotation, but it requires careful management of path collisions across teams. -
Destroy NGINX before destroying the cluster. When tearing down your infrastructure with Terraform, always run
terraform destroy --target helm_release.external_nginxbefore running a fullterraform destroy. Otherwise, the NLB created by AWS for the NGINX service may linger as an orphaned resource in your AWS account, silently costing you money.
Conclusion: When to Use Which?
| Feature | AWS ALB Ingress | NGINX Ingress |
|---|---|---|
| AWS Integration | Deep native integration (WAF, ACM, Cognito) | Minimal (Requires external tools like cert-manager) |
| Cost | High. One ALB per Ingress (unless grouped) | Low. One NLB handles all Ingresses. |
| Routing Features | Basic path/host routing | Advanced (rewrites, rate limiting, custom Lua scripts) |
| Maintenance | AWS manages the load balancer | You manage the NGINX pods (scaling, tuning) |
Key Takeaways
- Start simple, but plan ahead: The LoadBalancer service is great for quick tests, but you need Ingress for production HTTP/S traffic.
- ALB Ingress is best for Enterprise: If you rely heavily on AWS ACM for certificates, AWS WAF, and don’t mind the cost, the AWS LBC is highly reliable.
- NGINX is best for Cost & Flexibility: If you run many microservices and want to minimize cloud bills, a single NLB backing an NGINX controller paired with cert-manager is the industry standard approach.