Your Ingress controller is the front door to applications in Kubernetes: it accepts web requests and routes them to the right service. If that front door is the retired community ingress-nginx controller, you need a maintained replacement that preserves the behavior your applications rely on.

Gateway API is a set of Kubernetes resources for describing gateways and routes. It is not a replacement proxy by itself. You choose an implementation, such as Envoy Gateway, Cilium's gateway support, Traefik or a supported cloud service, to read those resources and carry the traffic. This guide explains that choice, the checks before moving traffic and a separately recorded local routing test.

Community ingress-nginx retired on 24 March 2026. Existing installations can continue serving traffic, but upstream no longer supplies releases, bug fixes or security fixes. The retirement affects that controller, not the Kubernetes Ingress API or every product using NGINX. Confirm the controller image, repository and IngressClass before planning a replacement. The Kubernetes 1.36 release announcement records the retirement date.

A successful move preserves the requests your applications depend on. Gateway API defines resources; a chosen implementation supplies their behavior. First select a supported implementation, then map and verify the old routing contract, and only then move traffic. The bounded Envoy Gateway example later in this guide demonstrates routing and a reversible backend change; it is not an ingress-nginx conversion test.

Choose a target you can operate

Shortlist two plausible implementations using the controller comparison. For a team already operating Cilium, evaluate its supported Gateway capability. For a separately managed Envoy-based gateway, evaluate Envoy Gateway. Traefik may fit teams using its Ingress and middleware ecosystem. A provider-managed gateway can reduce controller work if its features and support boundaries fit.

Check the conformance report for the implementation and release, then its specific feature documentation. Conformance is useful evidence about standardized behavior; it does not promise support for your NGINX snippets, authentication service or every extension. Decide who upgrades the controller, who owns its load balancer, and who responds when the data plane rejects a route.

AKS: the November deadline has feature constraints

Microsoft says the managed NGINX application-routing add-on stops receiving Azure support after November 2026 and directs users to migrate by then. Its replacement application-routing Gateway implementation uses Istio internally but is distinct from the Istio service mesh add-on. At this review, it cannot configure request header/body size limits, Lua, or local/global rate limiting, and it does not support EnvoyFilter. A workload relying on those features needs another supported design, not just translated HTTPRoutes. Check the current AKS feature and support limits.

The application-routing Gateway implementation and Istio service mesh add-on cannot be enabled together. Do not plan their overlap as if they were independent self-managed controllers. Separately, AKS-managed Gateway CRDs require the Standard channel and a bundle compatible with the cluster version. CRDs alone do not deploy a gateway. The experimental-channel CRDs in this guide's disposable Envoy fixture are not an instruction to install them into that managed AKS setup.

Inventory the behavior before translating it

Build one worksheet row per hostname and application owner. Record the IngressClass, paths, backends, ports, certificates, protocol and external or internal exposure. Add controller-wide configuration, ConfigMaps and snippets: not every effective setting lives in the application's Ingress object. Include DNS and certificate automation, external authentication dependencies and the network path to each backend.

For each requirement, write a replacement and an acceptance request. “Supported” is incomplete without an observable result. The following mappings identify what to investigate; they do not assert that every implementation supports each feature.

  • Hosts and paths: map to HTTPRoute hostnames, matches and backend references. Test exact paths, prefixes, trailing slashes, overlapping rules and unknown hosts. Regex matching requires an explicit implementation check.
  • Rewrites and redirects: distinguish changing the upstream request from redirecting the client. Check path replacement, scheme, host, status code and query-string preservation against the target's documented filters.
  • TLS: separate the client-facing certificate, TLS passthrough and gateway-to-backend TLS. Record certificate ownership, renewal and backend identity validation; one successful HTTPS request does not establish all three.
  • Authentication and rate limits: identify the target policy or external integration and its failure behavior. Test missing, expired and malformed credentials, as well as authentication-service failure. A route must not become public while its old annotation is ignored.
  • Uploads, streaming and long requests: record body/header limits, buffering and timeouts. Check gRPC or WebSockets where used, including disconnects and backend restarts.
  • Client identity and logs: verify source address, forwarded scheme and trusted proxy handling. Confirm the logs still identify the route, backend and failure reason needed during an incident.
  • Snippets: explain the behavior each snippet supplies. Replace it with a supported policy or redesign it; do not mechanically paste proxy configuration into another implementation.

The HTTPRoute reference is the starting point for match and filter semantics. Keep unsupported requirements as explicit blockers with an owner. An API object that is accepted while silently losing a required application behavior is not a completed migration.

Use translation as input to review

Ingress2Gateway 1.0 added support for more than 30 ingress-nginx annotations and controller-level tests for supported translations. That is upstream evidence for the tool's covered cases, not a certification of your configuration. Pin the translator and selected target versions, retain warnings, and compare the generated resources against the inventory. Resolve every skipped annotation and conflicting assumption before deploying a trial route.

Record four separate versions: Kubernetes, Gateway API CRDs, the controller, and the data-plane proxy. Check who owns cluster-scoped CRDs before installing a controller chart. Multiple Helm releases or a cloud service should not unknowingly compete to manage the same API definitions. Use the target's supported installation method; the local fixture below deliberately uses a new disposable cluster.

Set attachment and reference permissions separately

A shared gateway needs an ownership model. The infrastructure team controls listeners and their allowedRoutes; application teams reference the intended Gateway using parentRefs. Set a deliberate namespace/route policy instead of granting every team access to an external listener. The cross-namespace routing guide explains this two-sided attachment.

That attachment is distinct from referring to a backend Service or certificate Secret in another namespace. Such references need the appropriate ReferenceGrant in the target namespace. Creating a grant is not a substitute for listener attachment permission, and allowing a route to attach does not authorize every backend reference. Test both allowed and denied cases so the migration preserves separation between teams.

Check status, then send the real requests

After applying a trial configuration, inspect the Gateway, its listener status and the HTTPRoute's status.parents entry for the intended parent. Compare each condition's observedGeneration with that object's metadata.generation; an older value describes an earlier configuration. The upstream status guide explains why a one-line resource listing is insufficient.

  • No parent status, or stale status: verify the route's parent name and namespace, the GatewayClass and its controller. An invalid parent reference can leave a route outside any controller's scope, with no error condition to report.
  • Accepted=False: read the reason and message. Check listener attachment permissions, hostname matching and unsupported or incompatible filters against the named failure.
  • ResolvedRefs=False: inspect the referenced object and access permission. For a route, check backend Service names and required cross-namespace grants; for a TLS listener, check its certificate reference.
  • Gateway or listener Programmed=False: inspect its reason, controller events and data-plane configuration before expecting traffic to work.

Accepted=True can describe partially valid configuration. Programmed=True indicates configuration has been delivered to the data plane, not that your application request has succeeded. These distinctions are explicit in the Gateway API condition contract; check all relevant conditions and then test the endpoint.

Run the same synthetic request matrix against both endpoints, preserving the expected hostname and TLS validation. Record status, response headers, backend identity and relevant application behavior. For an HTTPS endpoint, merely setting a Host header is not enough to test TLS server-name selection. Use a client mechanism that selects the new endpoint while retaining the intended hostname, without disabling certificate verification.

Agree on acceptance criteria before comparison. Include unauthorized requests, unknown hosts, unavailable backends and certificate renewal alongside successful traffic. Compare errors and latency under representative load, but report only what you actually measured. The local fixture below exercises a deliberately narrower HTTP subset.

Cut over with both endpoints observable

  1. Save the old manifests, controller configuration and rollback destination. Pause unrelated routing changes so a difference has an identifiable cause.
  2. Deploy the new gateway at a separate endpoint and resolve the request-matrix failures. Verify monitoring, certificate renewal, health checks and backend connectivity on that path.
  3. Move one low-risk hostname or a bounded traffic fraction where the DNS/load-balancer design supports it. Confirm application errors, authentication outcomes and backend traffic, not only gateway readiness.
  4. Observe both endpoints through the DNS-cache and long-lived-connection overlap. Do not treat a configured DNS TTL as proof that every client has switched.
  5. Remove the old controller and load balancer only after checking residual traffic and dependencies. Assign cleanup for addresses, permissions, certificate automation and obsolete manifests.

The upstream migration guidance recommends parallel evaluation. Provider add-ons can impose additional coexistence restrictions, as AKS does above. Establish that the proposed overlap is supported before relying on it for recovery.

Define what rollback restores

Name the person who can stop the cutover and define error, authentication and latency thresholds. Returning traffic to the old endpoint only helps while that endpoint, certificate and backend contract still work. Cached DNS and open connections can leave both gateways serving requests after reversal.

Keep gateway migration separate from changes to sessions, authentication and persistent data where possible. Restoring an address or HTTPRoute cannot reverse application writes or restore a removed authentication integration. Treat the retired ingress-nginx installation as a temporary migration fallback with an agreed end date, then establish recovery on the maintained target.

Tested locally: routes and a reversible backend change

Kubedex ran a bounded Gateway API fixture on 9 October 2026 using Envoy Gateway 1.9.2, Envoy Proxy 1.39.2, Kubernetes 1.36.4, Helm 4.3.0 and kind 0.33.0 on Linux arm64. Kubernetes 1.36 is in the published Envoy Gateway 1.9 compatibility matrix. The chart installed Gateway API 1.6.1 experimental-channel CRDs; the Gateway and HTTPRoute used the stable v1 API.

The downloadable fixture contains pinned images, two harmless HTTP backends, Gateway resources, two route declarations and the observed results. It used a fresh disposable cluster because the controller installation includes cluster-scoped CRDs and policy resources. Its proxy Service was ClusterIP-only; requests reached it through a localhost port-forward, with no cloud load balancer or DNS change.

The request matrix

The declared host was fixture.example.test. The first route sent the exact root path and the /app prefix to a blue backend, with a prefix rewrite to /. A more-specific exact /app/status rule sent requests to the green backend with a full-path rewrite. The table below records the observed responses, including two intentionally unmatched requests.

Run and inspect

The complete README includes fresh-cluster creation, archive checksums, image identities, manifests and cleanup. After configuring the explicit disposable kubeconfig/context and installing the pinned controller as documented there, the main resource steps were:

k=(kubectl --kubeconfig "$fixture_config" --context "$fixture_context")
"${k[@]}" apply -f backends.yaml
"${k[@]}" rollout status deployment/blue -n kubedex-gateway-fixture --timeout=120s
"${k[@]}" rollout status deployment/green -n kubedex-gateway-fixture --timeout=120s
"${k[@]}" apply -f gateway.yaml
"${k[@]}" apply -f route-blue.yaml
"${k[@]}" wait gateway/local-gateway -n kubedex-gateway-fixture --for=condition=Programmed --timeout=180s
"${k[@]}" get httproute fixture-route -n kubedex-gateway-fixture -o yaml

The test checked Accepted=True and ResolvedRefs=True against the HTTPRoute's current generation. It also waited for the managed Envoy Deployment and verified that its Service was ClusterIP. This separates a syntactically accepted resource from a programmed data plane and an application response.

With the proxy Service forwarded to localhost port 18080, a representative request was:

curl --include --header 'Host: fixture.example.test' http://127.0.0.1:18080/app

Observed response: HTTP 200, body blue. The same prefix did not match /application, which returned 404. The exact /app/status rule returned green, demonstrating the more-specific match in this fixture.

Switch and roll back the route

"${k[@]}" apply -f route-green.yaml
# Check current-generation route conditions and repeat requests.
"${k[@]}" apply -f route-blue.yaml
# Check conditions and repeat requests again to verify rollback.

The green declaration sent / and /app to green. Restoring the blue declaration returned both paths to blue, while /app/status remained green. All 12 request checks passed. The test recorded actual route conditions and image identities in the result file, then removed the entire named disposable cluster.

A configuration detail caught by verification

The first experiment showed why inspecting the resulting data plane matters: setting the chart's default proxy image did not establish that image for the explicitly configured EnvoyProxy. The final fixture pins the data-plane digest directly in that EnvoyProxy and asserts the declared pod image as well as recording the runtime identity. A Helm value alone was not accepted as proof of the image running.

What this does and does not prove

This test demonstrates hostname/path matching, two rewrite forms, a prefix boundary, exact-path precedence, and a reversible backend change for these versions. It does not install or translate ingress-nginx, reproduce its annotations, change DNS, or validate TLS, authentication, client-IP trust, cross-namespace policy, gRPC, WebSockets, rate limits, load or production cutover. Extend the request matrix for those behaviors before migrating a real endpoint. Restoring a route declaration also cannot reverse writes made by an application.

Observed Gateway API requests in the local fixture

Choose columns
Visible columns
Observed Gateway API requests in the local fixture
StageHost and pathHTTPBody
Initialfixture.example.test /200blue
Initialfixture.example.test /app200blue
Initialfixture.example.test /app/200blue
Initialfixture.example.test /app/status200green
Initialfixture.example.test /application404empty
Initialother.example.test /404empty
Switch to green/ and /app and /app/status200 for allgreen
Rollback to blue/ and /app200 for bothblue
Rollback to blue/app/status200green

9 rows

Sources & further reading

  1. Official migration guidance
  2. ingress2gateway 1.0 release
  3. Envoy Gateway installation and CRD ownership
  4. Envoy Gateway compatibility matrix
  5. Versioned Helm installation
  6. Custom EnvoyProxy configuration
  7. Envoy Gateway 1.9.2 and proxy version notes
  8. Kubernetes confirms March 24 ingress-nginx retirement
  9. Gateway API implementation conformance reports
  10. AKS application routing Gateway API and November 2026 migration deadline
  11. AKS-managed Gateway API CRD ownership and Standard channel
  12. HTTPRoute matching and filters
  13. Gateway cross-namespace attachment
  14. ReferenceGrant for cross-namespace backend and Secret references
  15. Gateway API troubleshooting, missing status and observed generation
  16. Gateway API condition semantics and partial validity

Spotted something that needs another look?

Help improve this page →