Skip to main content

AWS Load Balancer Controller Goes GA on Gateway API: Why Your Self-Hosted Ingress Bet Just Got Its Strongest Signal Yet

11 min readDora NodaDora Noda
Share
On this page

In March 2026, AWS did something it almost never does: it told you to stop configuring its load balancers its way. The AWS Load Balancer Controller v3.0.0 release made Gateway API support generally available, and the announcement post by Alexandra Huides and Zac Nixon frames the standard Kubernetes Gateway API — not the controller's own Ingress annotations — as the way to define load balancing on EKS going forward. When the company that invented the annotation-driven load balancer integration decides the typed, portable API is the primary interface, the "should our fleet bet on Gateway API?" debate is effectively over. The only question left is which implementation you bet with.

Here is the news in 60 seconds, with the one fact-box that matters:

  • What shipped: AWS Load Balancer Controller v3.0.0 (March 2026) graduated Gateway API routing to GA. Layer-7 routes (HTTPRoute, GRPCRoute, supported since v2.14) provision an ALB via the GatewayClass controller gateway.k8s.aws/alb; Layer-4 routes (TCPRoute, UDPRoute, TLSRoute, supported since v2.13.3) provision an NLB via gateway.k8s.aws/nlb. Both are behind the ALBGatewayAPI / NLBGatewayAPI feature gates.
  • The one hard limit: you cannot mix protocol layers on a single Gateway — a TCPRoute and an HTTPRoute on the same Gateway is not supported. L4 and L7 live on separate Gateways, hence separate load balancers.
  • Why now: Gateway API v1.5 (February 2026) gave vendors a stable target — GRPCRoute in the standard channel, Gateway merging, and CORS filters graduated. Vendors GA against stable specs; March follows February for a reason.
  • Context, in two sentences: Ingress NGINX — the default ingress for a generation of clusters — was retired in November 2025 with maintenance halting in March 2026 and no further CVE patches, and SIG Network tells every user to migrate to Gateway API or another controller. AWS even published its own NGINX-to-AWS-LBC migration guide whose stated end state is Gateway API resources — the hyperscaler is migrating you twice, and the second hop is the standard.

And here is the portability proof, up front where it belongs. One realistic route — hostname, path, and header match, TLS terminated at the Gateway listener:

yaml
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: api-route
spec:
  parentRefs:
    - name: tenant-gateway
  hostnames: ["api.example.com"]
  rules:
    - matches:
        - path: { type: PathPrefix, value: /v2 }
          headers:
            - name: X-Tenant-Tier
              value: paid
      backendRefs:
        - name: api-v2
          port: 8080
Same YAML, three data planesWhat serves itWhat you changed
AWS Load Balancer ControllerManaged ALB, GatewayClass controller gateway.k8s.aws/albgatewayClassName on the Gateway
Envoy GatewayEnvoy proxy Deployment in-clustergatewayClassName on the Gateway
Cilium Gateway APIeBPF dataplane, no sidecar proxy podsgatewayClassName on the Gateway

The route — the part your developers write — is identical. The one line that changes names the infrastructure that serves it. That is the whole argument for the standard in a single table: your routing intent is now an asset that survives a change of load balancer, cloud, or decade.

What v1.5 standardized, and why AWS could GA against it

Vendors do not graduate integrations against moving targets. Gateway API v1.5, released February 2026, is the stable floor under AWS's March GA, and three graduations in it are the ones that matter to a platform team.

First, GRPCRoute in the standard channel. gRPC is no longer the second-class citizen that needed an annotation or a vendor extension — it is a core route type, which is why the LBC can promise "L7 means HTTPRoute and GRPCRoute" without hedging. For a fleet running agent sandboxes and internal gRPC services next to web tenants, that single graduation removes the "but what about our gRPC traffic?" objection to standardizing.

Second, Gateway merging. Multiple Gateways can now merge onto shared listeners without conflict, which is the primitive that makes the role split real: the platform team owns the listener (port 443, the TLS cert), tenant teams attach routes to it, and nobody steps on anyone else's config. Without merging, "shared ingress, separate teams" devolves into either one giant shared Gateway file or one load balancer per tenant — the two failure modes every multi-tenant ingress story eventually hits.

Third, CORS filters as standard. Cross-origin policy is the kind of thing that previously lived in an annotation (cors-allow-origin and friends, spelled differently per controller, silently ignored when misspelled) and is now a typed filter the apiserver validates. Small feature, large signal: the boring, everyday knobs are finishing their migration from strings to schema.

The pattern to internalize: AWS GA'd in March because the spec went stable in February. Track the spec's graduation calendar and you can predict vendor behavior a quarter ahead — a cheaper planning input than waiting for each vendor's changelog.

What annotations cost you

The old way of getting "route /v2 for paid tenants to the new backend" out of the AWS controller looked like this on an Ingress object:

yaml
metadata:
  annotations:
    alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS": 443}]'
    alb.ingress.kubernetes.io/ssl-redirect: '443'
    alb.ingress.kubernetes.io/conditions.api-v2: >
      [{"field":"http-header","httpHeaderConfig":{"httpHeaderName":"X-Tenant-Tier","values":["paid"]}}]

Five annotations, JSON embedded in YAML embedded in a string map, one typo away from silence. That last property is the actual cost: a misspelled annotation is not an error, it is a no-op. alb.ingress.kubernetes.io/condtions.api-v2 applies cleanly, reconciles successfully, and routes nothing — the controller never knew you meant something. The equivalent mistake in the HTTPRoute above (header instead of headers, a bad path type) is rejected by the apiserver before anything is persisted, with a validation error pointing at the field.

To be precise about the title claim: AWS has not removed the annotation path — existing Ingress and Service integrations still work, and the migration guide's first hop is NGINX-to-LBC-on-Ingress. "Doesn't default to annotations anymore" means the new capability, the documented end state, and the worked examples are all Gateway API. The annotation surface is maintenance mode in everything but name: no new routing expressive-ness will ever land there, because every new feature (merging, CORS filters, GRPCRoute) is being built on the typed side. Betting new config on annotations in 2026 is betting on the branch that receives no investment.

The honest limits: what GA does not mean

A GA stamp on one controller is not portability magic. Four pieces of residue stay non-portable, and a fleet plan has to name them:

1. The L4/L7 split forces two Gateways on AWS. Because mixing TCPRoute and HTTPRoute on one Gateway is unsupported, a tenant needing both Postgres-over-TCP and HTTPS gets two Gateways and two load balancers (NLB + ALB), each billed. Envoy Gateway and Cilium have no such restriction — one Gateway can serve both. This is the sharpest edge in a GatewayClass swap: moving that tenant's YAML from AWS to self-hosted deletes a load balancer, and moving back creates one. Cost-model it per tenant before you promise "seamless portability."

2. AWS-only knobs live in AWS-only CRDs. Anything the standard cannot express — target-group health-check tuning, ALB attributes, listener-rule ordering — lives in the gateway.k8s.aws/v1beta1 extension CRDs (LoadBalancerConfiguration, TargetGroupConfiguration, ListenerRuleConfiguration). These are typed and validated, which beats annotations, but they are not portable: that block of YAML is dead weight on Envoy or Cilium. Discipline: keep cloud-only tuning in extension CRDs attached by reference, never smeared across the portable route — so the day you move, you drop one file, not fifty edits.

3. Channel discipline bites. Gateway API ships a standard channel and an experimental channel, and controllers disagree on which they install. The failure mode is real: a controller that watches a standard-channel CRD (say, ListenerSet) crash-loops when the cluster only has the experimental XListenerSet installed — a version skew between your CRD bundle and your controller version, and nothing routes. Pin the CRD channel in the same commit as the controller version, and upgrade them as one unit. The conformance reports each implementation publishes are versioned for exactly this reason — read the report for your version, not the project's latest.

4. Route support varies by implementation and version. AWS documents its matrix precisely (L7 from v2.14, L4 from v2.13.3). Envoy Gateway, Cilium, Traefik, and the rest each have their own matrix and their own conformance badges, and "supports Gateway API" without a version number is marketing, not engineering. Before standardizing, open the implementation's conformance report and check the four route types you actually need at the version you will actually run.

Envoy Gateway vs Cilium: the fleet pick as a table

For a self-hosted, Cluster-API-managed fleet — owned Hetzner machines, no cloud load-balancer API — the choice narrows to two serious defaults. Same HTTPRoute, different trade-offs:

DimensionEnvoy GatewayCilium Gateway API
Data-plane modelDedicated Envoy proxy Deployment per Gateway; familiar L7 feature surface (rate limit, auth, transcoding)eBPF in the kernel via the CNI you may already run; no proxy pods per Gateway
Getting an address on bare metalNeeds an address allocator: MetalLB/equivalent or host networking; you operate the LB layerSame bare-metal address problem, but if Cilium is already your CNI the dataplane half is sunk cost
gRPC + conformance postureEnvoy is the reference-grade gRPC proxy; consistently top-tier conformance scoresNeeds gatewayAPI.enableAlpn=true for gRPC (easy to miss); strong but check the versioned report
Day-2 cost for a small teamOne more stateful-ish Deployment to size, upgrade, and scale per GatewayIf Cilium is already the CNI: near-zero marginal ops; if not, adopting a CNI and an ingress at once is the bigger project

The verdict rule: if Cilium is already your CNI, take Cilium Gateway — the marginal ops cost is ~zero. Otherwise, take Envoy Gateway — the standalone ingress with the best gRPC story and the simplest mental model. Either way, the portable asset is the HTTPRoute YAML your tenants write, not the proxy that serves it. Revisit only when a tenant needs something outside the standard (then see limit #2), not on every release.

What to do Monday

  1. Pin standard-channel CRDs with the controller version, in one commit, upgraded as one unit. Channel skew is the cheapest outage in this space and the most embarrassing.
  2. One GatewayClass per environment (prod-alb / prod-envoy), referenced by name from tenant Gateways — never let tenants name controllers directly. The class is your seam for moving data planes later.
  3. Keep cloud-only knobs in extension CRDs, referenced, not inlined — so a future move drops files instead of editing routes.
  4. Read the conformance report for your exact version and check the four route types you need. "Supports Gateway API" is not a support matrix.

The signal AWS sent in March is not "use our controller." It is that the typed, portable API won — the hyperscaler with the most to gain from lock-in chose the standard as its primary interface. For a self-hosted fleet, that converts Gateway API from a CNCF spec waiting on endorsement into the durable layer to build tenant routing on: write routes once, swap the GatewayClass when the infrastructure changes, and spend the saved migration budget on something tenants actually notice.


Sources

  • AWS Load Balancer Controller v3.0.0 release: Gateway API support GA for ALB and NLB — github.com/kubernetes-sigs/aws-load-balancer-controller/releases/tag/v3.0.0
  • Huides & Nixon (2026-03-06): "AWS Load Balancer Controller adds general availability support for Kubernetes Gateway API," incl. gateway.k8s.aws/alb GatewayClass example and role-oriented design rationale — aws.amazon.com
  • LBC v3.0 Gateway API overview: L4 routes (TCPRoute, UDPRoute, TLSRoute ≥ v2.13.3) vs L7 routes (HTTPRoute, GRPCRoute ≥ v2.14), no mixed-protocol Gateways, NLBGatewayAPI/ALBGatewayAPI feature gates — kubernetes-sigs.github.io
  • LBC v3.0 L4 routing: controllerName: gateway.k8s.aws/nlb, reconciliation loop, gateway.k8s.aws/v1beta1 customization CRDs — kubernetes-sigs.github.io
  • Gateway API v1.5 standard channel (GatewayClass, Gateway, HTTPRoute, GRPCRoute, ReferenceGrant, BackendTLSPolicy) and v1.6 TCPRoute/UDPRoute promotion — gateway-api.sigs.k8s.io via implementation docs
  • Ingress NGINX retirement (announced Nov 2025, maintenance ends March 2026, migrate to Gateway API) — kubernetes.io, TechTarget
  • AWS NGINX-retirement migration guide (NGINX → LBC on Ingress → Gateway API) — aws.amazon.com
  • Self-hosted implementation landscape (Envoy Gateway, Cilium, Istio, Traefik, Contour conformance; Cilium enableAlpn gRPC note; version snapshots mid-2026) — Gateway API implementations list and community migration write-ups

Bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on machines you own. Tenant routing on a self-hosted PaaS is exactly the Gateway API bet this post describes: portable routes up front, your machines underneath. Star the repo on GitHub or deploy your first app today.

Related articles

Run this on infrastructure you own

bex is the open-source, AI-native Render alternative — push a git repo and get a running HTTPS service on your own machines.

Get started with bex