Containers
Migrating from NGINX Ingress to ALB: Handling oauth2-proxy
The NGINX Ingress Controller was retired in March 2026. Many teams running Amazon Elastic Kubernetes Service (Amazon EKS) have already started migrating to the AWS Load Balancer Controller, a fully AWS native solution that provisions Application Load Balancers (ALBs) directly from Kubernetes Ingress resources.
The AWS guide Navigating the NGINX Ingress retirement: a practical guide to migration on AWS covers the core migration: the controller comparison, URI rewriting, and TLS termination. This post covers the one area that guide intentionally left out: preserving your OpenID Connect (OIDC) authentication flow when oauth2-proxy is in the request path.
For teams already running oauth2-proxy with NGINX for OIDC authentication (such as Keycloak and Okta), this migration raises a key question: how do I keep my authentication flow working with ALB?
This post presents two solutions:
- Solution 1: ALB + oauth2-proxy in reverse proxy mode. The ALB forwards all traffic to oauth2-proxy, which handles authentication and proxies to the backend. Minimal change to your existing auth configuration.
- Solution 2: ALB native OIDC. The ALB handles authentication itself using its built-in
authenticate-oidcaction. oauth2-proxy is removed entirely from the request path.
One trade-off to understand upfront: Solution 2 changes the header your backend receives. The ALB forwards the token in x-amzn-oidc-accesstoken, not the standard Authorization: Bearer header that oauth2-proxy sends. If your application expects the standard header, this is a subtle breaking change you will need to handle (covered in Solution 2). Solution 1 preserves the Authorization: Bearer header your backend already expects.
Solution overview
Both solutions work. The right choice depends on your requirements around token handling, the number of components you want to run, and whether your backend can accept a different header. Use this decision framework:
| Criteria | Solution 1: oauth2-proxy reverse proxy | Solution 2: ALB native OIDC |
| Need custom claims / fine-grained token handling? | Yes: full control through oauth2-proxy | No: limited to ALB’s OIDC defaults |
| Want fewer components to run? | No: keeps oauth2-proxy | Yes: removes oauth2-proxy |
| Backend header changes acceptable? | No change needed (Authorization: Bearer) |
Backend must read x-amzn-oidc-accesstoken or add a header proxy |
| Backward-compatible migration (NGINX + ALB coexist)? | Yes | Partial (auth flow differs) |
| Amount of configuration change | Low: 3 flags + Ingress backend swap | Medium: new annotations + identity provider callback change (register /oauth2/idpresponse) |
In short: choose Solution 1 for the least disruption and no backend changes. Choose Solution 2 to remove oauth2-proxy when your backend can adapt to ALB’s headers.
Background: How NGINX + oauth2-proxy works today
Most NGINX + oauth2-proxy setups use auth subrequests, an NGINX-specific feature where NGINX validates every incoming request against oauth2-proxy before forwarding to the backend:
The Ingress resource uses these annotations:
The ALB can’t perform subrequests. When you switch from ingressClassName: nginx to ingressClassName: alb, these annotations are silently ignored and traffic flows to the backend without authentication. Both solutions that follow address this.
Solution 1: ALB + oauth2-proxy in reverse proxy mode
Keep oauth2-proxy in your architecture but switch it from auth-subrequest mode (where NGINX called it) to reverse proxy mode (where it receives all traffic directly from the ALB). oauth2-proxy handles both authentication and proxying to the backend. The ALB’s only job is TLS termination and load balancing.
Update oauth2-proxy configuration
Add these arguments to your existing oauth2-proxy deployment:
| Flag | Required? | Purpose |
--upstream |
Yes | Where to proxy authenticated requests (your backend) |
--reverse-proxy=true |
Yes | Trust X-Forwarded headers from ALB |
--pass-authorization-header=true |
Yes | Send Authorization: Bearer <token> to upstream. Replaces NGINX’s auth-response-headers: authorization |
--set-xauthrequest=true |
Optional | Send X-Auth-Request-User and X-Auth-Request-Email to upstream. Replaces NGINX’s auth-response-headers: x-auth-request-user, x-auth-request-email |
--pass-access-token=true |
Optional | Send X-Forwarded-Access-Token to upstream |
--set-authorization-header=true |
Optional | Set Authorization in the response back to the client |
To exactly replicate what auth-response-headers: x-auth-request-user, x-auth-request-email, authorization did in NGINX, add all the optional flags.
This change is backward-compatible. oauth2-proxy in reverse proxy mode still responds to /oauth2/auth subrequests. Your existing NGINX Ingress continues working while you validate the ALB path.
Ingress configuration
The backend is oauth2-proxy, not your application. This is the key architectural difference from the NGINX Ingress, which pointed to the backend and used subrequests for auth.
If you use a local certificate (for example, cert-manager with a Vault or Let’s Encrypt issuer), keep the spec.tls section and remove the certificate-arn annotation. The ALB controller reads the cert from the referenced Kubernetes Secret.
Security groups
Add an inbound rule to your cluster/node security group allowing TCP 4180 from the ALB security group.
Solution 2: ALB native OIDC authentication
The ALB has a built-in OIDC authentication action. When configured, the ALB itself performs the full OpenID Connect flow: redirecting unauthenticated users to your identity provider (IdP), exchanging the authorization code for tokens, and setting a session cookie. No oauth2-proxy needed.
After authentication, the ALB forwards the request to your backend with these headers:
Ingress configuration
The OIDC secret contains your identity provider’s client credentials:
Register the redirect URL with your identity provider
Before the flow will work, your identity provider must allow the ALB’s callback URL as a valid redirect/callback URI. The ALB always completes the OpenID Connect (OIDC) code exchange at the /oauth2/idpresponse path on your application’s hostname, so register:
https://app.example.com/oauth2/idpresponse
Use the same hostname your users hit: the load balancer DNS name or its CNAME alias. This is the most common miss with ALB native OIDC. If the redirect URI isn’t allowlisted, the identity provider rejects the callback and users get an error after signing in instead of reaching your application.
The header difference
As noted in the overview, this is the key trade-off of Solution 2. The ALB doesn’t set the standard Authorization: Bearer <token> header. It sets x-amzn-oidc-accesstoken instead. If your backend expects Authorization: Bearer, either modify your application to read x-amzn-oidc-accesstoken directly or place a lightweight proxy in front of your backend to translate the header.
Migration process (both solutions)
Regardless of which solution you choose, the migration follows the same safe pattern:
- Prepare – Provision an AWS Certificate Manager (ACM) certificate (or keep your local certificate). Verify subnet tags (
kubernetes.io/role/internal-elb: 1). Verify that thealbIngressClass exists. - Deploy in parallel – Create the ALB Ingress with a different name. Both NGINX and ALB coexist, each with its own load balancer. (See the parallel deployment options that follow.)
- Validate – Test the ALB path directly using a Host header override or DNS override. Verify the full authentication flow end-to-end.
- Cut over – Switch the DNS record to the ALB. For Amazon Route 53 Private Hosted Zones, propagation is near instant.
- Decommission – Delete the old NGINX Ingress. Uninstall the NGINX Ingress Controller.
Running NGINX and ALB in parallel
To reduce risk, validate the ALB path before sending real users to it. Because the ALB Ingress and the NGINX Ingress are independent resources, each provisioning its own load balancer, they can run side by side. There are two common approaches:
- Separate hostname (recommended for validation): Expose the ALB under a distinct hostname such as
app-alb.example.comwhile production traffic stays onapp.example.com(NGINX). Run your full authentication test suite against the ALB hostname. When confident, repointapp.example.comto the ALB. This keeps the two paths fully isolated during testing. - Weighted DNS (recommended for gradual cutover): Use Route 53 weighted records on the production hostname. Start with a small weight to the ALB (for example, 10 percent) and the remainder to the NGINX Network Load Balancer (NLB). Monitor authentication success rates and error logs, then gradually increase the ALB weight to 100 percent. NGINX remains the fallback throughout the ramp.
Rollback at any point: revert the DNS record (or set the ALB weight back to 0) to send all traffic to the NGINX NLB. Keep the NGINX Ingress and controller in place until the cutover is fully validated.
When to choose each solution
Choose solution 1 (ALB + oauth2-proxy reverse proxy) if:
- You want minimal change to your existing setup (add three flags, change the Ingress backend).
- Your backend expects
Authorization: Bearer <token>and you don’t want to modify it or add a header proxy. - You want a backward-compatible migration where NGINX and ALB coexist during validation.
- You need custom claims or fine-grained token handling that oauth2-proxy already provides.
- You already have oauth2-proxy configured and tested with your identity provider.
Choose solution 2 (ALB native OIDC) if:
- You want to remove oauth2-proxy from the architecture entirely (fewer components to run).
- Your backend can read
x-amzn-oidc-accesstokendirectly, or you accept adding a header proxy. - You want a minimal ALB configuration with no additional pods.
- ALB’s built-in OIDC handling meets your token and session requirements.
Conclusion
Migrating from NGINX Ingress to ALB doesn’t mean losing your OIDC authentication. Both solutions preserve a secure authentication flow:
- Solution 1 (oauth2-proxy reverse proxy) keeps your existing auth logic intact. Add three flags, change the Ingress backend, and you’re done. It supports a fully backward-compatible, parallel migration with the least disruption.
- Solution 2 (ALB native OIDC) removes oauth2-proxy entirely. The ALB handles the OIDC flow natively. This recommended when your backend can accept ALB’s headers or you’re willing to adapt.
For most teams migrating from an existing NGINX + oauth2-proxy setup, Solution 1 gets you to ALB with the least risk and the fewest surprises. Pair this post with the broader Navigating the NGINX Ingress retirement guide for the complete migration picture.