🟢 Welcome to my World!
WORKSPACE
TIMELINE
root/welcome.hi

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

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.
  • kubectl and 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

  1. 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.name annotation, but it requires careful management of path collisions across teams.

  2. Destroy NGINX before destroying the cluster. When tearing down your infrastructure with Terraform, always run terraform destroy --target helm_release.external_nginx before running a full terraform 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?

FeatureAWS ALB IngressNGINX Ingress
AWS IntegrationDeep native integration (WAF, ACM, Cognito)Minimal (Requires external tools like cert-manager)
CostHigh. One ALB per Ingress (unless grouped)Low. One NLB handles all Ingresses.
Routing FeaturesBasic path/host routingAdvanced (rewrites, rate limiting, custom Lua scripts)
MaintenanceAWS manages the load balancerYou manage the NGINX pods (scaling, tuning)

Key Takeaways

  1. Start simple, but plan ahead: The LoadBalancer service is great for quick tests, but you need Ingress for production HTTP/S traffic.
  2. 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.
  3. 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.
TERMINAL
1: zsh
sirishgurung@portfolio:-$cat ./contact.txt
▄▄   ▄▄▄▄ ▄▄▄▄ ▄▄  ▄▄▄      ▄▄ ▄ ▄ ▄▄▄▄ ▄▄▄▄ ▄▄ ▄      ▄▄▄▄ ▄▄▄▄ ▄▄▄▄▄ ▄▄▄▄ ▄▄▄▄ ▄▄ ▄ ▄▄▄▄ ▄▄▄▄ ▄▄
██   ██ ▀  ██  ▀  ██▀       ██ █ █ ██ █ ██ █ ██ █       ██  ██ █ ██    ██ ▀  ██  ██ █ ██ ▀ ██ █ ██
██   ██▀   ██     ▀██▄      ██ █ █ ██ █ ██▄▀ ██▄▀       ██  ██ █ ██ ▄▄ ██▀   ██  ██▄█ ██▀  ██▄▀ ██
 █▄▄  █ █  ▐█      ▄▀▀       █ █ ▀  █ █ ▀█ █  █ █       ▐█   █ █ ▐▀ ▀▌  █ █  ▐█   █ █  █ █ ▀█ █ ▀▀
▀▀▀▀ ▀▀▀▀  ▀▀     ▀▀▀       ▀▀▀▀▀▀ ▀▀▀▀ ▀▀ ▀ ▀▀ ▀       ▀▀  ▀▀▀▀ ▀▀▀▀▀ ▀▀▀▀  ▀▀  ▀▀ ▀ ▀▀▀▀ ▀▀ ▀ ▀▀
      
I'd love to hear from you!>Get in touch