AWS Builder Center

Kubernetes Ingress on AWS Explained: From Setup to Production

Learn how to use Kubernetes Ingress on AWS EKS with the AWS Load Balancer Controller: setup, HTTPS, security, cost tips, and troubleshooting.

Using Ingress on AWS: A Practical Guide to the AWS Load Balancer Controller on EKS

A hands-on guide for DevOps and cloud engineers deploying microservices on Amazon EKS.

1. Introduction

When you run microservices on Kubernetes, every service needs a way to be reached from outside the cluster. You could expose each one with a Service of type LoadBalancer, but that creates one cloud load balancer per service. It is expensive, hard to manage, and gives you no central place for routing rules, TLS, or authentication.
Ingress solves this. It is a Kubernetes API object that defines HTTP/HTTPS routing rules (host-based and path-based) and lets a single entry point serve many services. On AWS, the most common way to implement Ingress is the AWS Load Balancer Controller, which turns Ingress resources into Application Load Balancers (ALB).
This article covers what Ingress is, how it works on AWS, how to set it up on EKS step by step, and the best practices and pitfalls that matter in production.

2. Ingress Basics

Two pieces work together:
  • Ingress resource: a YAML object that declares the rules, for example "requests to shop.example.com/api go to the api service".
  • Ingress controller: a component running in the cluster that watches Ingress resources and configures an actual load balancer or proxy to enforce them.
Kubernetes does not ship a default controller. Without one, an Ingress resource does nothing. You choose the controller, and on AWS you have two main options.

3. Your Options on AWS

Option A: AWS Load Balancer Controller (ALB)

The controller creates and manages an AWS Application Load Balancer for your Ingress resources.
  • Fully managed load balancer outside the cluster, scaled by AWS
  • Native integration with ACM (certificates), WAF, Shield, Cognito, and security groups
  • Can route directly to pod IPs (ip target type), skipping extra hops
  • Cost: you pay for the ALB (hourly plus LCU usage)

Option B: NGINX Ingress Controller (behind an NLB)

You run NGINX pods inside the cluster, exposed through a single Network Load Balancer.
  • Rich in-cluster features: rewrites, rate limiting, custom snippets
  • Portable across clouds
  • You manage scaling, upgrades, and tuning of the NGINX pods

Which one should you pick?

  • Tight AWS integration (ACM, WAF, Cognito): ALB controller
  • Less operational burden: ALB controller
  • Advanced rewrite/rate-limit logic, multi-cloud portability: NGINX
  • Lowest per-request latency control, TCP/UDP: NLB / NGINX
For most EKS workloads, the AWS Load Balancer Controller is the default recommendation, and the rest of this guide focuses on it.

4. How the AWS Load Balancer Controller Works

  1. You apply an Ingress manifest with ingressClassName: alb.
  2. The controller sees it and calls AWS APIs to create an ALB, listeners, target groups, and rules.
  3. It registers targets and keeps them in sync as pods come and go.
  4. Traffic flows: Client > ALB > Pod IP (in ip mode) or Client > ALB > NodePort > Pod (in instance mode).
Target types:
  • ip: the ALB sends traffic straight to pod IPs. It works with the AWS VPC CNI, is required for Fargate, and is the recommended mode.
  • instance: the ALB sends traffic to worker nodes on a NodePort, then kube-proxy forwards to pods. It adds an extra hop.

5. Prerequisites

  • A running EKS cluster with the AWS VPC CNI
  • kubectl, helm, and eksctl (or Terraform) installed
  • An IAM OIDC provider associated with the cluster, so pods can assume IAM roles (IRSA)
  • Subnets tagged correctly, so the controller knows where to place load balancers:
    • Public subnets: kubernetes.io/role/elb = 1
    • Private subnets: kubernetes.io/role/internal-elb = 1
    • All subnets: kubernetes.io/cluster/<cluster-name> = shared (or owned)

6. Installing the Controller

Step 1: Associate the OIDC provider

eksctl utils associate-iam-oidc-provider
--cluster my-cluster --approve

Step 2: Create the IAM policy

Download the official policy JSON from the AWS Load Balancer Controller GitHub repository (use the version matching your controller release), then create it:
aws iam create-policy
--policy-name AWSLoadBalancerControllerIAMPolicy
--policy-document file://iam_policy.json

Step 3: Create the service account with IRSA

eksctl create iamserviceaccount
--cluster my-cluster
--namespace kube-system
--name aws-load-balancer-controller
--attach-policy-arn arn:aws:iam::<ACCOUNT_ID>:policy/AWSLoadBalancerControllerIAMPolicy
--approve

Step 4: Install with Helm

helm repo add eks https://aws.github.io/eks-charts
helm repo update
helm install aws-load-balancer-controller eks/aws-load-balancer-controller
-n kube-system
--set clusterName=my-cluster
--set serviceAccount.create=false
--set serviceAccount.name=aws-load-balancer-controller

Step 5: Verify

kubectl get deployment -n kube-system aws-load-balancer-controller
You should see the deployment ready with its pods running.

7. Your First Ingress

Assume two services, frontend and api, already running in the shop namespace.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: shop-ingress
namespace: shop
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: shop.example.com
http:
paths:
- path: /api
pathType: Prefix
backend:
service:
name: api
port:
number: 80
- path: /
pathType: Prefix
backend:
service:
name: frontend
port:
number: 80
Apply it and watch for the ALB address:
kubectl apply -f shop-ingress.yaml
kubectl get ingress -n shop
After a minute or two, the ADDRESS column shows the ALB's DNS name. Point your domain at it with a CNAME or Route 53 alias record.

8. Adding HTTPS with ACM

Request or import a certificate in AWS Certificate Manager, then reference its ARN:
metadata:
annotations:
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/target-type: ip
alb.ingress.kubernetes.io/listen-ports: '[{"HTTP":80},{"HTTPS":443}]'
alb.ingress.kubernetes.io/certificate-arn: arn:aws:acm:<region>:<account>:certificate/<id>
alb.ingress.kubernetes.io/ssl-redirect: '443'
TLS terminates at the ALB, so there is no certificate management inside the cluster. The ssl-redirect annotation sends all HTTP traffic to HTTPS.

9. Useful Annotations

  • scheme: internet-facing or internal
  • target-type: ip or instance
  • healthcheck-path: path the ALB probes for pod health
  • group.name: share one ALB across multiple Ingresses
  • wafv2-acl-arn: attach an AWS WAF web ACL
  • subnets: explicitly choose subnets
  • security-groups: use specific security groups
  • load-balancer-attributes: set access logs, idle timeout, and similar options
  • actions.<name>: custom actions such as fixed responses or redirects

10. Sharing One ALB Across Many Ingresses

By default, each Ingress creates its own ALB. With many teams or namespaces, that gets costly fast. Use IngressGroup:
metadata:
annotations:
alb.ingress.kubernetes.io/group.name: shared-public
alb.ingress.kubernetes.io/group.order: '10'
All Ingresses with the same group.name are merged onto a single ALB. Use group.order to control rule priority when paths could overlap.

11. Automating DNS with ExternalDNS

Instead of manually creating Route 53 records, deploy ExternalDNS. It watches your Ingress hosts and creates matching records automatically, so a new Ingress with host: app.example.com gets its DNS entry without manual steps. Give it an IAM role (via IRSA) with permission to modify the relevant hosted zone.

12. Security Best Practices

  • Use HTTPS everywhere and redirect HTTP to HTTPS.
  • Attach AWS WAF to protect against common web exploits and bots.
  • Use the internal scheme for private services, reachable only inside the VPC or over VPN.
  • Restrict inbound CIDRs with inbound-cidrs when only certain networks need access.
  • Least-privilege IAM: the controller's role should use only the official policy.
  • Enable ALB access logs to S3 for auditing and debugging.
  • Use NetworkPolicies or security groups for pods so only the ALB can reach them.

13. Reliability and Cost Tips

  • Define readiness probes on pods so the ALB only sends traffic to healthy ones.
  • Use pod readiness gates with the controller to avoid dropped requests during rolling updates.
  • Set a sensible deregistration_delay so in-flight requests finish before pods terminate.
  • Use IngressGroup to cut the number of ALBs.
  • Run multiple controller replicas for availability.
  • Delete unused Ingresses promptly, since an orphaned ALB keeps billing.

14. Troubleshooting

Ingress has no ADDRESS: Check the controller logs with "kubectl logs -n kube-system deployment/aws-load-balancer-controller". Common causes are missing IAM permissions, untagged subnets, or the wrong ingressClassName.
Targets show unhealthy in the target group: Verify the health check path returns a 200, the container port matches the Service, and the pod security group allows traffic from the ALB.
502 / 503 errors: Usually no healthy targets, a wrong service port, or pods not ready. Check "kubectl get endpoints" for the service.
ALB not created in the expected subnets: Recheck the kubernetes.io/role/elb and internal-elb tags, or pin subnets with the subnets annotation.
Certificate errors: The ACM certificate must be in the same region as the ALB and must cover the hostname in your Ingress.

15. Putting It Together in a CI/CD Pipeline

In a typical DevOps workflow, Ingress manifests live in Git next to your application manifests or Helm charts:
  1. A commit triggers Jenkins (or another CI tool) to build and push a Docker image.
  2. The pipeline updates the image tag in the manifests.
  3. ArgoCD (GitOps) syncs the changes to the EKS cluster.
  4. The Ingress and Service resources roll out, and the AWS Load Balancer Controller updates the ALB.
  5. Prometheus and Grafana monitor the application, and ALB metrics in CloudWatch complete the picture.
Treating Ingress as code means routing changes are reviewed, versioned, and reproducible.

16. Conclusion

Ingress on AWS gives you one clean, centrally managed entry point for all your microservices. The AWS Load Balancer Controller makes this especially smooth on EKS: it turns simple Kubernetes manifests into fully managed ALBs with native TLS, WAF, and DNS integration. Get the foundations right (OIDC and IRSA, correct subnet tags, ip target mode), use IngressGroup to control cost, and layer on HTTPS, WAF, and health checks for a production-ready setup.

Quick Checklist

  • OIDC provider associated with the cluster
  • Subnets tagged for load balancers
  • IAM policy and IRSA service account created
  • Controller installed via Helm and verified
  • Ingress uses ingressClassName: alb and target-type: ip
  • ACM certificate attached, HTTP redirected to HTTPS
  • Health checks and readiness probes configured
  • IngressGroup used to share ALBs where sensible
  • WAF and access logs enabled
Any opinions in this article are those of the individual author and may not reflect the opinions of AWS.
Enjoyed reading this content? Let the author know!

Your likes, comments, shares, and saves help creators reach more builders.

Loading recommendations

Loading article