AI-narrated article. Prose is preserved with pronunciation adjustments; code and image cues refer to the written article.
Ingress-NGINX to Gateway API: Preserve the Traffic Contract Before Cutover
This is the AI-narrated article. The prose follows the written post; pronunciation is adjusted, and code and images are announced separately.
An accepted HTTPRoute can still break clients. Migrate redirects, authentication, TLS and limit semantics before moving a hostname to a new controller.
TL;DR: Ingress-NGINX retirement requires a controller migration, not an assumption that another implementation preserves its behavior. Inventory host-level routing, annotations, security policies and application expectations. Split listener and route ownership, distinguish portable Gateway API resources from implementation extensions, and validate the traffic contract before cutover. This article provides a pinned Envoy Gateway example with an explicit trailing-slash redirect, expected request cases and cleanup. It explains why accepted routes can still break clients, why local rate limits and TLS need separate reviews, and how a parallel endpoint supports a bounded rollback plan.
Migrate the traffic contract before the controller
A route can be accepted, its proxy can be healthy, and a migration can still break clients. The dangerous differences often sit outside the obvious hostname and backend: a trailing-slash redirect, regex matching across sibling paths, an authentication failure mode, or a quota that changes when the proxy replica count changes.
The community Ingress-NGINX controller has retired; its repository is archived and it no longer receives releases, bug fixes or security updates. Existing deployments do not automatically stop forwarding traffic. This concerns the community controller, not the separate F5 NGINX Ingress Controller, and does not remove Kubernetes' Ingress API. Project status.
The useful migration unit is a reviewed traffic contract for one hostname and its paths. Translate that contract into Gateway API resources, prove critical behavior on a parallel endpoint, then move traffic with a reversible plan. A YAML converter is an inventory aid, not evidence of equivalent behavior.
Inventory behavior and assign an owner
Export the Ingress objects, controller ConfigMap, deployment arguments and referenced Secrets. Keep Secret values out of migration reports. Group routes by hostname, because separate Ingress objects can affect the same NGINX server configuration. Inventory annotations and snippets alongside application expectations: authentication, rewrites, timeouts, upload limits, connection handling, redirects, TLS and source-address trust.
Ingress-NGINX can apply case-insensitive prefix regex behavior with host-wide consequences. An Envoy regex can instead be a full, case-sensitive match; exact and prefix routes do not silently inherit regex semantics. Record representative requests with case changes, trailing slashes, suffixes and sibling paths before translating them. Behavior differences.
GatewayClass selects the implementation, Gateway defines listeners, and HTTPRoute defines application routing. Decide who owns each layer. A platform team can own listener exposure, certificates and namespace attachment policy; service teams can own reviewed application routes. Neither splitting the YAML nor installing CRDs establishes that ownership automatically. Migration guide.
Use a small contract table that names the decision, not just the annotation:
Table. Columns: Contract, Evidence to retain, Migration owner.
Row 1. Contract: Matching and redirects; Evidence to retain: Status, Location, method, path and query handling; Migration owner: Service team.
Row 2. Contract: Authentication; Evidence to retain: Allow/deny decisions, forwarded headers, auth outage behavior; Migration owner: Security and service teams.
Row 3. Contract: TLS; Evidence to retain: SNI, certificate chain, renewal and upstream verification; Migration owner: Platform team.
Row 4. Contract: Limits and retries; Evidence to retain: Scope, replica effects, non-idempotent request handling; Migration owner: Service and platform teams.
End of table.
Separate portable routes from implementation policies
Do not paste nginx.ingress.kubernetes.io/auth-url onto an HTTPRoute and expect protection. Envoy Gateway uses its own SecurityPolicy for external authorization. Validate denial behavior, header propagation, request-body handling where relevant, and what happens when the authorization service is unavailable. External authorization.
Rate limits need an equally explicit scope. Envoy Gateway's local rate limit uses BackendTrafficPolicy and applies per proxy instance; increasing replicas can increase the aggregate allowance. Each route has its own bucket even when the policy targets a Gateway. That is not equivalent to a globally shared customer quota. Local rate limiting.
An HTTPS listener terminates frontend TLS; it does not automatically secure the backend connection. Cross-namespace certificate references need permission from the target namespace through ReferenceGrant. Keep route attachment permissions, backend-reference permissions and backend TLS configuration as separate reviews. TLS guide.
A bounded route migration you can follow
This example preserves an explicit contract: requests to /get redirect to /get/, and /get/ reaches the backend. It isolates a compatibility failure without pretending to implement your authentication or production TLS policy.
Prerequisites: Bash, curl, Helm and kubectl configured for an empty disposable local cluster, with no cloud load-balancer integration. Use a Kubernetes version in Envoy Gateway 1.9's supported 1.33–1.36 range. Pin Envoy Gateway v1.9.2 and its compatible Gateway API v1.6.1. The upstream backend image is pinned to registry.k8s.io/gateway-api/echo-basic:v1.5.1. These are different component versions; Gateway API 1.6 does not mean Envoy Gateway 1.6. Compatibility matrix.
The chart installs CRDs. Do not install it over provider-managed CRDs or into a shared cluster without a separate ownership plan. The upstream quickstart creates GatewayClass eg, Gateway eg, and the backend Deployment, Service and HTTPRoute. Versioned quickstart.
Code example. Refer to the written article for the complete code.
Save this complete replacement HTTPRoute as route.yaml. Its backend Service port is 3000, not an assumed container port or the listener's port. Parent references bind it to the named HTTP listener.
Code example. Refer to the written article for the complete code.
Apply the route and forward the generated proxy Service. An external address may remain pending without a local load-balancer implementation; the documented port-forward path lets you inspect HTTP behavior without provisioning one.
Code example. Refer to the written article for the complete code.
In another terminal, inspect the first response without following redirects. Then request the canonical path. Do not use curl -L through this port-forward unless you account for the redirect hostname/port: the redirect represents the listener's public authority, not necessarily localhost port 8888.
Code example. Refer to the written article for the complete code.
Expected behavior from this configuration: /get redirects with 301 and a Location path ending in /get/; /get/ reaches the echo backend. The extra suffix and wrong host have no matching route. These are expectations to verify, not reported execution results. Also inspect Route Accepted and ResolvedRefs conditions and their observedGeneration; stale conditions are not evidence that the current manifest is effective. Pinned upstream manifest.
Failure case: the redirect disappears
Suppose a translation keeps only the second exact-match rule. A client requesting /get no longer matches /get/, even though the route can be accepted and its backend references valid. An old controller's implicit trailing-slash behavior is not a portable promise.
Image. Three routing tracks compare the intended contract, a missing redirect, and an explicit Gateway API redirect
Diagnose by comparing the original request, status and Location rather than just following redirects until a page loads. Inspect route attachment, matching rules and proxy access logs. Restore the explicit redirect rule, then repeat both canonical and noncanonical requests. Check method semantics too: a permanent 301 is the example's chosen contract, not a universal choice for APIs carrying non-idempotent requests.
For cleanup, stop port-forwarding with Ctrl+C, delete the downloaded quickstart resources from this isolated namespace, and uninstall the release. Remove remaining cluster-scoped CRDs through the disposable cluster's lifecycle rather than deleting shared definitions indiscriminately.
Code example. Refer to the written article for the complete code.
Cut over by hostname, with a tested way back
Build the replacement endpoint alongside the old endpoint. Compare request/response behavior using safe probes and representative read-only requests. Replaying writes or mirroring sensitive traffic needs a separate data and side-effect review. A backend-weight split within the new Gateway does not migrate clients still reaching the old Ingress endpoint.
Choose the traffic switch explicitly: DNS, an existing upstream load balancer, or a controller-specific mechanism. Review certificate coverage, forwarded-header trust, client IP handling, connection draining and propagation delays. Keep the old configuration and endpoint available for a bounded rollback window; rollback only helps if the old path still serves the current application safely.
Retire one hostname after its contract passes, security behavior is covered, and service owners agree on the acceptance evidence. Replacing an unsupported controller is urgent work. Rushing unreviewed authentication and routing semantics into a new controller simply moves the risk.
FAQ
Do Gateway API CRDs replace Ingress-NGINX? No. You also need a compatible implementation and data plane, plus translated application behavior.
Can conversion tools preserve every annotation? Do not assume that. Review converted resources against the selected implementation and the observed traffic contract.
Why can an accepted route still fail a migration? Conditions describe configuration handling; they do not prove equivalent redirects, authentication, backend responses or client behavior.
Should I delete the old controller immediately after changing DNS? Keep a bounded, operational rollback path while verifying propagation and client behavior, then remove it through a reviewed retirement plan.