AWS
NLB
Nginx
Ingress Controller
SSL

How to use aws nlb with nginx ingress controller for ssl

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

Introduction

Using an AWS Network Load Balancer in front of ingress-nginx is a common way to expose Kubernetes services with stable Layer 4 behavior and AWS-managed networking. The first design choice is where TLS should terminate, because the annotations and NGINX settings differ depending on whether decryption happens at the NLB or inside NGINX.

Pick One TLS Model First

There are two common models.

In the first model, the NLB terminates TLS with an ACM certificate and forwards plain HTTP to the ingress controller. This is simpler when you want certificate management in AWS.

In the second model, the NLB passes encrypted TCP traffic through to NGINX and NGINX terminates TLS using a Kubernetes secret. This keeps certificate handling inside the cluster.

Do not mix these models accidentally. Most broken setups come from configuring annotations for NLB termination while also expecting NGINX to receive raw TLS.

NLB TLS Termination With ingress-nginx

If you want the NLB to terminate TLS, the Service exposing ingress-nginx needs the AWS load-balancer annotations and an ACM certificate ARN.

A typical Service manifest looks like this.

yaml
1apiVersion: v1
2kind: Service
3metadata:
4  name: ingress-nginx-controller
5  namespace: ingress-nginx
6  annotations:
7    service.beta.kubernetes.io/aws-load-balancer-type: external
8    service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip
9    service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing
10    service.beta.kubernetes.io/aws-load-balancer-ssl-cert: arn:aws:acm:us-east-1:111122223333:certificate/abcd1234
11    service.beta.kubernetes.io/aws-load-balancer-ssl-ports: https
12spec:
13  type: LoadBalancer
14  selector:
15    app.kubernetes.io/name: ingress-nginx
16    app.kubernetes.io/component: controller
17  ports:
18    - name: http
19      port: 80
20      targetPort: 80
21    - name: https
22      port: 443
23      targetPort: 80

In that configuration, the NLB accepts HTTPS on port 443, decrypts it, and forwards HTTP to the NGINX controller on port 80.

That means your application-level Ingress can stay simple.

yaml
1apiVersion: networking.k8s.io/v1
2kind: Ingress
3metadata:
4  name: demo-app
5  namespace: demo
6spec:
7  ingressClassName: nginx
8  rules:
9    - host: demo.example.com
10      http:
11        paths:
12          - path: /
13            pathType: Prefix
14            backend:
15              service:
16                name: demo-service
17                port:
18                  number: 80

TLS Passthrough or TLS at NGINX

If you want NGINX to terminate TLS instead, forward TCP and keep the NLB out of certificate management. Then create the TLS secret in Kubernetes and reference it from the Ingress.

yaml
1apiVersion: networking.k8s.io/v1
2kind: Ingress
3metadata:
4  name: demo-app
5  namespace: demo
6spec:
7  ingressClassName: nginx
8  tls:
9    - hosts:
10        - demo.example.com
11      secretName: demo-tls
12  rules:
13    - host: demo.example.com
14      http:
15        paths:
16          - path: /
17            pathType: Prefix
18            backend:
19              service:
20                name: demo-service
21                port:
22                  number: 80

This is the better model when you need NGINX-specific TLS features, custom certificate rotation inside the cluster, or end-to-end encryption all the way to the controller.

Client IP and Proxy Protocol Considerations

A common follow-up issue is losing the original client IP address. Depending on how traffic is forwarded, you may need the PROXY protocol enabled and the ingress-nginx controller configured to trust it.

If you enable proxy protocol on the AWS side, make sure the controller config matches. Otherwise, NGINX interprets the connection incorrectly and requests can fail in ways that look unrelated to TLS.

Also ensure your health checks are pointed at the controller's expected path and port. For many deployments, /healthz on the controller is the safest target.

Helm-Based Installation Pattern

Many teams deploy ingress-nginx with Helm and pass the annotations through values.

bash
1helm upgrade --install ingress-nginx ingress-nginx/ingress-nginx \
2  --namespace ingress-nginx \
3  --create-namespace \
4  --set controller.service.type=LoadBalancer \
5  --set controller.service.annotations."service\.beta\.kubernetes\.io/aws-load-balancer-type"=external \
6  --set controller.service.annotations."service\.beta\.kubernetes\.io/aws-load-balancer-nlb-target-type"=ip \
7  --set controller.service.annotations."service\.beta\.kubernetes\.io/aws-load-balancer-ssl-cert"=arn:aws:acm:us-east-1:111122223333:certificate/abcd1234 \
8  --set controller.service.annotations."service\.beta\.kubernetes\.io/aws-load-balancer-ssl-ports"=https

That keeps the AWS-facing configuration on the Service where it belongs, instead of scattering TLS concerns across unrelated Ingress resources.

Common Pitfalls

The biggest mistake is not deciding where TLS terminates. If the NLB terminates TLS, NGINX usually receives HTTP. If NGINX terminates TLS, the NLB should not also decrypt the connection.

Another mistake is setting the backend port wrong. In NLB termination mode, port 443 on the load balancer often forwards to port 80 on the controller.

A third issue is forgetting health checks, proxy protocol alignment, or ACM certificate region mismatches. Any of those can make the service look healthy from one layer and broken from another.

Finally, do not debug only the Ingress resource. With NLB-backed ingress, the Service annotations are often where the real problem lives.

Summary

  • Decide first whether TLS terminates at the NLB or at NGINX.
  • For AWS-managed certificates, use ACM and Service annotations on the ingress-nginx controller Service.
  • In NLB termination mode, the controller often receives plain HTTP on port 80.
  • If NGINX terminates TLS, use Kubernetes TLS secrets and an Ingress tls section.
  • Keep health checks and proxy protocol settings aligned with the chosen model.
  • Most SSL failures in this setup come from mixing two termination strategies unintentionally.

Related reading
Course
Beginner
27 lessons
10 hours
System Design Fundamentals

Build a strong foundation in designing scalable, reliable distributed systems.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

All Rights Reserved.