Amazon EKS Ingress Controllers: AWS Load Balancer Controller, ALB Routing, and TLS

On Amazon EKS, Ingress lets you route HTTP and HTTPS traffic to multiple services through a single AWS Application Load Balancer. This page explains what Ingress is, how it differs from a Kubernetes Service, how the AWS Load Balancer Controller makes it work on EKS, and when to use it.

Simple explanation

If you are new to Kubernetes, here is the short version.

A Service is the standard Kubernetes way to expose a set of pods to network traffic. When you create a Service of type LoadBalancer, EKS provisions an AWS load balancer and gives you an external address. One Service, one load balancer.

An Ingress is a higher-level routing layer. It is a Kubernetes resource that defines rules like: requests for api.example.com/v1 go to the API service, and requests for dashboard.example.com go to the dashboard service. One Ingress resource can route traffic to many Services through a single load balancer.

An ingress controller is the software that makes those rules work. By itself, an Ingress resource is just YAML. It does not do anything. The ingress controller watches for Ingress resources in the cluster and configures a load balancer to enforce the routing rules you defined.

Analogy

An Ingress is like a sign at the front entrance of a building: “Reception: floor 1, Engineering: floor 3, Finance: floor 5.” The sign defines the rules. The ingress controller is the security desk that actually reads the sign and directs each visitor to the right floor. Without the security desk, the sign is just a piece of paper.

Ingress vs Service: what is the difference

A Kubernetes Service of type LoadBalancer creates one AWS load balancer per Service. If you have five services to expose externally, an API, a dashboard, a webhook handler, a docs site, and a metrics endpoint, you end up with five separate load balancers. Each one costs money, needs its own DNS record, and has its own health check configuration.

An Ingress sits in front of multiple Services and routes requests to the right one based on URL path or hostname:

  • api.example.com routes to the API Service
  • dashboard.example.com routes to the Dashboard Service
  • example.com/webhooks routes to the Webhook Service

All of this goes through a single AWS Application Load Balancer. One load balancer, one set of TLS certificates, one DNS entry point.

When a LoadBalancer Service is enough:

  • You only have one service to expose externally
  • You need TCP or UDP routing, not HTTP
  • You want the simplest possible setup with no extra components installed

When Ingress is the better choice:

  • You have multiple HTTP/HTTPS services to expose
  • You want path-based or host-based routing
  • You want TLS termination in one place
  • You want to reduce the number of load balancers and the costs that come with them

What an ingress controller does on EKS

On Amazon EKS, the main AWS-native option for running an ingress controller is the AWS Load Balancer Controller.

The controller is a pod that runs inside your cluster. It watches for Ingress and Service resources using the Kubernetes API. When you create an Ingress resource with the right annotations, the controller reads it, calls the AWS API, and creates or updates an Application Load Balancer to match your routing rules.

This is different from a self-hosted ingress controller like NGINX, which runs a proxy pod inside the cluster. With AWS Load Balancer Controller, the load balancer itself lives in AWS infrastructure, not inside your cluster. That means native AWS integrations (WAF, ACM, Shield, CloudWatch), better throughput, and no in-cluster proxy to scale or maintain.

Not installed by default

AWS Load Balancer Controller is not included on new EKS clusters. If you apply an Ingress resource without installing it first, nothing happens, no error, no ALB, just silence. Install it via Helm before expecting any Ingress to work.

How it works on AWS

Here is the request flow when a user hits a service exposed via Ingress on EKS:

User browser

Route 53  →  resolves the domain to the ALB DNS name

Application Load Balancer  →  listener rules match hostname or path

Target group  →  routes directly to pod IPs

Pod (your application)

Route 53 typically points your domain to the ALB’s DNS name using an Alias record. The Application Load Balancer handles TLS termination, evaluates listener rules, and distributes traffic. The EKS networking model (VPC CNI) gives every pod a real VPC IP address, which is why the ALB can route directly to pods rather than going through node ports.

Path-based routing sends traffic to different Services based on the URL path. A request to /api/ goes to the API service and a request to /admin/ goes to the admin service, both through the same ALB and the same hostname.

Host-based routing sends traffic to different Services based on the hostname. api.example.com goes to one Service and dashboard.example.com goes to another. Each hostname maps to a separate listener rule on the ALB.

You can combine both: route by hostname first, then by path within that hostname.

What you need before this works

Before an Ingress resource on EKS will create a working ALB, you need all of the following in place:

  • A working EKS cluster. If you do not have one, start with creating your first EKS cluster.
  • AWS Load Balancer Controller installed. Not present by default. Install it via Helm.
  • IRSA configured for the controller. The controller needs an IAM role with permission to create and manage ALBs, target groups, and security groups. That role is linked to the controller’s Kubernetes service account via IAM Roles for Service Accounts.
  • Subnets tagged correctly. The controller uses subnet tags to find where to place the ALB. Public subnets need kubernetes.io/role/elb: 1 for internet-facing ALBs. Private subnets need kubernetes.io/role/internal-elb: 1 for internal ALBs.
  • Security groups that allow ALB traffic. The ALB needs to reach your pods on the target port.
  • An ACM certificate if using HTTPS. You do not store certificates in Kubernetes. The ALB uses a certificate from AWS Certificate Manager, which handles renewal automatically.
IRSA is the most common silent failure

Without IRSA configured correctly, the controller starts up, watches for Ingress resources, and then fails to call the AWS API. No ALB is created. No visible error appears on the Ingress resource itself. You only see it if you check the controller logs. Set up IRSA before you try to create any Ingress.

Basic Ingress example

Here is the simplest useful Ingress: one backend service, HTTPS enabled.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-app-ingress
  namespace: production
  annotations:
    kubernetes.io/ingress.class: alb
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/target-type: ip
    alb.ingress.kubernetes.io/certificate-arn: arn:aws:acm:eu-west-1:123456789012:certificate/abc123
    alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS": 443}]'
    alb.ingress.kubernetes.io/ssl-redirect: '443'
spec:
  rules:
  - host: app.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: my-app-service
            port:
              number: 80

When you apply this, the AWS Load Balancer Controller creates an internet-facing ALB that listens on ports 80 and 443, redirects HTTP to HTTPS automatically, terminates TLS using the ACM certificate, and routes all traffic to my-app-service.

Here is an example with multiple backends using path-based and host-based routing:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: main-ingress
  namespace: production
  annotations:
    kubernetes.io/ingress.class: alb
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/target-type: ip
    alb.ingress.kubernetes.io/certificate-arn: arn:aws:acm:eu-west-1:123456789012:certificate/abc123
    alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS": 443}]'
    alb.ingress.kubernetes.io/ssl-redirect: '443'
spec:
  rules:
  - host: api.example.com
    http:
      paths:
      - path: /v1
        pathType: Prefix
        backend:
          service:
            name: api-service
            port:
              number: 80
      - path: /webhooks
        pathType: Prefix
        backend:
          service:
            name: webhook-service
            port:
              number: 80
  - host: dashboard.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: dashboard-service
            port:
              number: 80

Important AWS Load Balancer Controller annotations

The controller uses annotations on the Ingress resource to configure the ALB. These are the ones you will use most often:

AnnotationValuesWhat it does
alb.ingress.kubernetes.io/schemeinternet-facing, internalControls whether the ALB is publicly accessible or only within your VPC. Use internal for services that should only be reached from inside the cluster or VPC.
alb.ingress.kubernetes.io/target-typeip, instanceUse ip on EKS. Routes traffic directly to pod IPs using VPC CNI, bypassing node ports and kube-proxy. The instance mode adds an unnecessary extra hop on EKS.
alb.ingress.kubernetes.io/certificate-arnACM certificate ARNThe ARN of your ACM certificate for HTTPS. Without this, the ALB will not serve HTTPS traffic.
alb.ingress.kubernetes.io/listen-portsJSON array of port configsDefines which ports the ALB listens on. Specify both HTTP 80 and HTTPS 443 to support both protocols.
alb.ingress.kubernetes.io/ssl-redirect443Automatically redirects HTTP requests to HTTPS. Requires listen-ports to include HTTP 80 and HTTPS 443.
alb.ingress.kubernetes.io/group.nameAny string, e.g. shared-albGroups multiple Ingress resources across namespaces onto a single ALB. Without this, each Ingress creates its own ALB. Shared groups are the most cost-effective pattern when you have many services.
Tip: use group.name from the start

If you have more than one Ingress resource in your cluster, set the same group.name on all of them. The controller will merge their routing rules onto a single ALB automatically. Adding this later means a brief period of downtime as the controller creates a new shared ALB and removes the old individual ones.

ingressClassName vs ingress.class

Newer versions of the AWS Load Balancer Controller support spec.ingressClassName: alb as an alternative to the kubernetes.io/ingress.class: alb annotation. Both work. ingressClassName is the Kubernetes-standard approach going forward, so prefer it on newer clusters.

TLS termination with ACM

With AWS Load Balancer Controller, TLS is terminated at the ALB, not inside your cluster. Here is how the setup works:

  1. Request or import a certificate in AWS Certificate Manager for your domain.
  2. Add the certificate ARN to your Ingress as the certificate-arn annotation.
  3. The controller configures the ALB with an HTTPS listener using that certificate.
  4. The ALB handles the TLS handshake with the client.
  5. The ALB forwards the request to your pod as plain HTTP over the VPC network.

Your pods do not need to handle certificates at all. ACM manages certificate renewal automatically, with no secrets to rotate and no Kubernetes resources to update when a certificate renews.

Traffic between ALB and pods is plain HTTP

After TLS is terminated at the ALB, the connection to your pod is unencrypted HTTP. This traffic stays inside your VPC, and AWS encrypts VPC traffic at the infrastructure level, so it does not cross the public internet unencrypted. For most applications this is fine. If your compliance requirements demand end-to-end TLS down to the pod, you need to configure TLS on your containers separately and update the ALB target group to use HTTPS.

AWS Load Balancer Controller vs NGINX Ingress on EKS

Both work on EKS. The choice depends on what your team needs.

AWS Load Balancer Controller provisions native AWS ALBs and NLBs directly from Kubernetes resources. The load balancer lives in AWS infrastructure, not inside your cluster. It integrates with ACM for TLS, WAF for request filtering, Shield for DDoS protection, and CloudWatch for metrics. All configuration is done through annotations on the Ingress resource.

NGINX Ingress Controller runs an NGINX proxy pod inside your cluster and exposes it via a LoadBalancer Service (which creates an NLB). It is more flexible for routing: it supports matching by headers, query strings, and request body in ways that ALB listener rules cannot. TLS is managed via Kubernetes Secrets, often using cert-manager for automated issuance and renewal.

AWS Load Balancer ControllerNGINX Ingress
Load balancer locationAWS-managed ALB outside the clusterNGINX proxy pod inside the cluster
TLS certificatesACM, auto-renewedKubernetes Secrets, often via cert-manager
AWS integrationsWAF, Shield, S3 access logs, CloudWatchLimited, only what the NLB in front provides
Routing flexibilityHost and path-basedHost, path, headers, query strings, and more
Cloud portabilityAWS onlyWorks on any Kubernetes cluster
Operational overheadNo proxy pod to maintainYou manage the NGINX pod, its resources, and scaling

For most new EKS projects, AWS Load Balancer Controller is the right default. It avoids running and scaling a proxy pod inside the cluster, and it integrates cleanly with the rest of the AWS stack. Choose NGINX Ingress when you need routing rules that ALB annotations cannot express, or when you need consistent ingress behaviour across clouds.

When to use Ingress on EKS

Use Ingress when:

  • You have more than one HTTP/HTTPS service to expose and want to avoid one load balancer per service
  • You need to route requests based on hostname or URL path
  • You want TLS termination in one place, managed through ACM
  • You want to group services from different teams or namespaces behind a single ALB using group.name
  • You are building a microservices architecture where a single external entry point fans out to multiple backends

Do not use Ingress when:

  • You need to expose a single TCP or UDP service; use a LoadBalancer Service or an NLB directly instead
  • You only have one HTTP service and want the simplest possible setup
  • You need WebSocket or gRPC connections; these work with ALB but require additional annotations and care

Common mistakes

  1. Forgetting the ingress class. Without kubernetes.io/ingress.class: alb (or spec.ingressClassName: alb), the AWS Load Balancer Controller ignores your Ingress resource entirely. No ALB is created, and no error appears in kubectl get ingress. You have to check the controller logs to see what happened.
  2. Using target-type: instance on EKS. The instance mode routes traffic to node ports via kube-proxy, adding an unnecessary extra hop. On EKS with VPC CNI, every pod has a real VPC IP. Use target-type: ip to route directly to pods.
  3. Missing or misconfigured IRSA. The controller needs an IAM role with permissions to create and manage ALBs, target groups, and security groups. Without IRSA configured correctly, the controller fails silently. Check the controller logs in kube-system for permission denied errors.
  4. Untagged subnets. The controller uses subnet tags to find where to place the ALB. Missing tags mean no ALB. Public subnets need kubernetes.io/role/elb: 1; private subnets need kubernetes.io/role/internal-elb: 1.
  5. Expecting Ingress to work before the controller is installed. Confirm the controller is running before applying any Ingress resources: kubectl get pods -n kube-system | grep aws-load-balancer.
  6. Wrong backend service port. The port in the Ingress backend spec must match the port the Service exposes, not the container port. If the Service exposes port 80 but the Ingress references port 8080, traffic will not reach your pods.

Troubleshooting checks

If your Ingress is not working, run through these in order.

Check whether the ALB was created:

kubectl get ingress -n <namespace>

The ADDRESS column should show the ALB DNS name. If it is empty, the controller did not provision the ALB.

Describe the Ingress for controller events:

kubectl describe ingress <name> -n <namespace>

The Events section at the bottom shows what the controller attempted and where it stopped.

Check the controller is running:

kubectl get pods -n kube-system | grep aws-load-balancer

Check controller logs for errors:

kubectl logs -n kube-system -l app.kubernetes.io/name=aws-load-balancer-controller
Permission errors in controller logs

If the logs show AccessDenied or similar AWS API errors, IRSA is not set up correctly. The controller pod is running but cannot call the AWS API. Check that the IAM role ARN on the service account annotation matches the role that has the Load Balancer Controller IAM policy attached.

Common specific issues:

  • No ADDRESS on the Ingress: Controller is not running, IRSA is missing, or subnets are not tagged correctly.
  • ALB created but targets are unhealthy: Confirm target-type: ip is set, the Service selector matches running pods, and the Service port matches what the container listens on.
  • HTTPS not working: Verify the ACM certificate ARN is correct and the certificate status is Issued. The domain on the certificate must match the host rule in the Ingress.
  • HTTP not redirecting to HTTPS: Both listen-ports (including HTTP 80 and HTTPS 443) and ssl-redirect: '443' must be set together.
  • 404 from the ALB: The ALB listener rules were created but no backend rule matched the request. Check that the path and host in the Ingress spec match what the request is sending.
  • Internal service not reachable from outside: The scheme annotation may be set to internal when you need internet-facing, or the ALB security group does not allow inbound traffic on port 443.

For cluster-level logging context, see logging in Kubernetes. For security controls around ingress, see securing EKS clusters.

Cost and architecture considerations

The main cost benefit of Ingress is consolidation: one ALB in front of many services instead of one load balancer per service.

A LoadBalancer Service on EKS typically provisions one Network Load Balancer. If you have ten services exposed via separate LoadBalancer Services, you pay for ten load balancers. Put those same ten services behind a single ALB via Ingress with a shared group.name, and you pay for one.

ALB pricing has two parts: a fixed hourly rate per load balancer, plus a usage-based charge (Load Balancer Capacity Units) that scales with connections, data processed, and rules evaluated. For most applications with moderate traffic, the usage cost is low and consolidating behind fewer ALBs saves money clearly. Check the current AWS pricing page for your region, since rates vary. At very high traffic volumes, model the usage cost directly against the fixed cost of running more load balancers.

Practical patterns worth knowing:

  • One ALB per environment (dev, staging, prod) using group.name is a common and cost-effective pattern. Teams define their own Ingress resources and the controller merges them into the shared ALB automatically.
  • Separate internet-facing and internal ALBs is a clean split: one group for public traffic, one for internal service-to-service communication.
  • WAF on public ALBs adds cost but provides meaningful protection if your services handle user-generated content or are exposed to untrusted traffic.

Frequently asked questions

What is the difference between an Ingress and an ingress controller?

An Ingress is a Kubernetes resource, a YAML file that defines HTTP routing rules (which hostname or path routes to which Service). An ingress controller is the software that reads those rules and configures a load balancer to enforce them. Without a running ingress controller, an Ingress resource does nothing.

Do I need AWS Load Balancer Controller to use Ingress on EKS?

To get ALB-backed Ingress on EKS, yes. AWS Load Balancer Controller is the AWS-native option that provisions real ALBs from your Ingress resources. Alternatively, you can install NGINX Ingress Controller, which runs a proxy pod inside the cluster. Both work on EKS; AWS Load Balancer Controller is the default choice for most teams building on AWS.

When should I use Ingress instead of a LoadBalancer Service?

Use Ingress when you have multiple HTTP/HTTPS services to expose and want to route them through a single load balancer using path-based or host-based rules. A LoadBalancer Service is simpler when you only have one service to expose. The more services you have, the more Ingress saves on cost and operational overhead.

Can I use NGINX Ingress Controller on EKS instead of AWS Load Balancer Controller?

Yes. NGINX Ingress Controller runs inside the cluster as a pod and exposes itself via a LoadBalancer Service, which creates an NLB. It is a good choice if you need NGINX-specific features, consistent behaviour across cloud providers, or fine-grained proxy configuration. AWS Load Balancer Controller is the better default if you want native ALB provisioning and AWS-native features like WAF integration and ACM certificates.

Does Ingress terminate TLS, or do my pods handle certificates?

With AWS Load Balancer Controller, TLS is terminated at the ALB using an ACM certificate. Traffic from the ALB to your pods travels over the VPC network as plain HTTP, so your pods do not handle certificates. If you use NGINX Ingress, TLS termination happens at the NGINX pod using a certificate stored in a Kubernetes Secret.

Last verified: 4 April 2026 Cloud services change frequently. Verify details against official documentation before making infrastructure decisions.