Ingress-NGINX to Gateway API: A Cutover Acceptance Checklist
Published: August 18, 2026
Updated: November 2, 2026
An Ingress-to-Gateway API migration is complete only when the new controller reproduces the behavior your applications depend on. Converted YAML is a starting artifact, not acceptance evidence. The safe path is to inventory the current contract, classify every annotation, deploy the new controller beside the old one, test request behavior, shift a bounded slice of traffic, and keep a proven rollback path until the observation window closes.
This guide targets platform teams moving from Ingress-NGINX to Gateway API. The runnable path below is scoped to HTTP and HTTPS migration with Gateway API v1.6, Ingress2Gateway v1.2, and APISIX Ingress Controller 2.1 as pinned reference points. It does not establish L4 compatibility for TCPRoute or UDPRoute. The workflow also applies to other conformant controllers, but policy resources, supported fields, defaults, and status conditions remain implementation-specific.
The Acceptance Standard
A successful migration must prove all four conditions:
- The Kubernetes API accepts the target resources and the selected controller reports them as programmed.
- Requests produce the expected routing, TLS, authentication, transformation, timeout, and failure behavior.
- Operators can observe and troubleshoot the new path before the old path is removed.
- The team can return traffic to the previous controller within its recovery objective.
That standard is deliberately stricter than “the manifests applied.” The official Gateway API migration guide explains that it does not prepare a live migration or convert every implementation-specific feature. Treat that limit as a design input.
Before starting, prepare exported Ingress manifests, a non-production cluster or isolated entry point, the target controller's support matrix, request fixtures for every protocol the workload uses, and authority to move and roll back traffic. Replace the example namespaces, hostnames, addresses, and certificates below with test values for your environment.
Start with an Annotation Inventory
Export the source Ingress objects and group annotations by behavior. Do not translate an annotation solely because the target controller exposes a similarly named field.
| Behavior to preserve | Typical source | Target decision | Acceptance evidence |
|---|---|---|---|
| Host and path routing | Ingress rules | HTTPRoute matches | Expected backend for positive and negative paths |
| TLS termination and redirect | TLS block and annotations | Gateway listener plus route or policy | Certificate chain, SNI, HTTP-to-HTTPS result |
| Rewrites and header changes | NGINX annotations | Core filters or controller extension | Backend-observed path and headers |
| External authentication | auth annotations or snippets | Controller policy or external service | Allowed, missing, invalid, and unavailable cases |
| Rate limiting | annotations or snippets | Gateway policy or plugin | Counter scope, rejection status, reset behavior |
| Timeouts and retries | proxy annotations | Standard field or controller policy | Failure timing and duplicate-side-effect check |
| Request buffering and body size | NGINX settings | Controller-specific policy | File upload and large-body tests |
| Client IP and forwarded headers | controller configuration | Listener and proxy trust settings | Backend-observed address from trusted and untrusted hops |
The last three rows are common sources of false confidence. A new controller can accept a route while changing retry timing, upload streaming, or the meaning of a forwarded address. Those are application contracts, not cosmetic configuration.
Save three artifacts for each workload: the source manifest, the conversion output and warnings, and an owner-approved mapping table. An annotation with no owner is still a risk; it may encode a dependency that nobody remembers.
Generate Candidate Resources, Not Production Truth
Ingress2Gateway can convert core Ingress behavior and selected provider annotations. Pin the tool version and work from exported manifests or a non-production context:
ingress2gateway print \ --providers=ingress-nginx \ --input-file=source-ingress.yaml \ > candidate-gateway-api.yaml \ 2> conversion-warnings.log
This form converts the exact exported file instead of whatever the active Kubernetes context happens to expose. If you intentionally use cluster-context mode, pin and record the context and namespace first. Keep conversion-warnings.log: warnings about unsupported or ambiguous behavior are part of the migration record.
Review the output for:
- the intended
GatewayClassand parent references; - listener hostnames, ports, protocols, and certificate references;
- namespace boundaries and any required
ReferenceGrant; - route match precedence and default-backend behavior;
- filters that replaced annotations;
- features that moved to implementation-specific policies;
- API versions supported by the installed controller.
Gateway API v1.6 makes TCPRoute and UDPRoute Standard v1 resources, but APISIX Ingress Controller 2.1 still reconciles those kinds through v1alpha2. The Gateway API v1.6 Standard CRDs do not serve that older version. Keep the pinned 2.1 path in this article to Gateway and HTTPRoute; for TCP or UDP workloads, select a controller release that explicitly supports the installed v1 resources and validate that controller/CRD pair as a separate cutover batch.
Choose the Runtime Before the Conversion
Gateway API defines a Kubernetes contract; a controller and data plane implement it. Choose that implementation before finalizing manifests.
For Apache APISIX, APISIX Ingress Controller translates Gateway API resources into Apache APISIX configuration. Its compatibility table lists controller 2.1 with APISIX 3.0 or later and recommends APISIX 3.13. The same page states that the Standalone API-driven mode in controller 2.0 and later requires APISIX 3.13 or later. Treat 3.13 as a requirement in that mode, not merely a recommendation, and verify the exact pair rather than mixing examples from different releases.
APISIX Ingress Controller also exposes extension policies for behavior outside core Gateway API. Those policies are not portable promises. If authentication, rate limiting, transformations, or upstream behavior depends on an APISIX policy, name that dependency in the workload contract and test it through the gateway.
Before proceeding, record:
Gateway API CRDs: v1.6.x Controller: name, image digest, and version Data plane: name, image digest, and version GatewayClass controllerName: exact value Supported route kinds: HTTPRoute for this pinned path; separately validated list for TCPRoute or UDPRoute Extension policies: name, version, and attachment target Rollback owner and maximum recovery time
Run a Three-Layer Validation
1. Validate the Objects
Use server-side dry run against the cluster that has the target CRDs and admission policies:
kubectl apply --server-side --dry-run=server -f candidate-gateway-api.yaml
This checks schema and admission. It does not prove that the controller will accept or program the objects.
2. Validate Controller Status
Apply the resources in staging, then inspect Gateway and route status:
kubectl get gateway,httproute -A kubectl describe gateway -n edge public-gateway kubectl describe httproute -n shop checkout
Require positive parent conditions for the intended controller. Investigate Accepted=False, Programmed=False, unresolved references, invalid listener attachment, and unsupported fields. A resource that exists but has no accepted parent is not serving traffic.
3. Validate Request Behavior
Send traffic directly to the new entry point before changing public DNS. Preserve the production hostname so host and TLS routing are exercised:
curl --resolve shop.example.com:443:NEW_GATEWAY_IP \ --cacert ./test-ca.pem \ -i https://shop.example.com/healthz
Repeat with a request matrix that covers:
- allowed host and path;
- unknown host and unmatched path;
- HTTP redirect and HTTPS response;
- valid, missing, expired, and wrong-audience credentials;
- route rewrite and header behavior as observed by the backend;
- request bodies at, below, and above the expected limit;
- backend connection refusal, timeout, and partial failure;
- gRPC, WebSocket, SSE, or large uploads when the workload uses them;
- rate-limit identity, rejection response, and recovery window;
- retry behavior for non-idempotent requests.
Record status, relevant headers, backend identity, duration, and a request correlation value. Do not treat a single 200 as proof that the old and new paths are equivalent.
Cut Over Without Hiding Rollback Risk
Run the old and new controllers side by side with distinct entry points. Avoid letting both controllers claim the same resource or load balancer unintentionally.
A controlled sequence is:
- Apply target resources and pass object, status, and request tests.
- Send internal or synthetic traffic to the new entry point.
- Move one low-risk hostname, route, or weighted traffic slice.
- Compare errors, latency, authentication denials, upstream failures, and application-level outcomes.
- Expand only after the agreed observation interval.
- Stop new changes on the old path, then retire it after rollback is no longer required.
DNS can support a cutover, but DNS rollback is bounded by resolver and CDN caching. A load balancer, CDN, or upstream routing layer may provide faster weighted switching. Document which control actually moves traffic and who can operate it.
Rollback criteria should be observable, for example:
- authentication denial rate exceeds the approved baseline;
- uploads or streaming requests fail their acceptance suite;
- backend 5xx or gateway timeouts exceed the rollback threshold;
- the new controller cannot reconcile a required route;
- operators cannot correlate a failed request to a route and upstream.
Rollback means moving traffic, not merely reapplying old YAML. Test the move before production cutover.
Troubleshoot by Failure Layer
| Symptom | First check | Likely boundary |
|---|---|---|
| Resource applies but route is unreachable | Parent and listener conditions | Kubernetes/controller reconciliation |
| TLS certificate is wrong | Listener hostname and certificate reference | Gateway listener and secret access |
404 only on new path | Host/path match and rewrite | Route selection or filter semantics |
| Authentication differs | Policy attachment, identity source, unavailable behavior | Controller extension or auth service |
| Upload becomes empty or rejected | Buffering, chunking, body limits | Data-plane HTTP behavior and application server |
| Duplicate write after upstream error | Retry policy and method safety | Gateway retry semantics |
| Client IP changes | Trusted proxy chain and forwarded-header policy | Entry point and proxy trust boundary |
Fix the owning layer and rerun the complete affected test set. A route change can alter authentication attachment; a listener change can alter TLS and forwarded headers.
Completion Checklist
Do not decommission the old controller until every checked workload has:
- pinned source and target versions;
- an annotation-to-policy mapping with owners;
- accepted and programmed Gateway API status;
- positive and negative request evidence;
- protocol-specific tests for the traffic it actually serves;
- dashboards, logs, and request correlation on the new path;
- a tested traffic rollback;
- an agreed observation period and retirement decision.
Gateway API improves the configuration model, but it cannot make implementation-specific behavior portable by itself. Use Ingress2Gateway to reduce mechanical work, then prove the runtime contract on the controller you will operate.
For an APISIX target, begin with the APISIX Ingress Controller overview and verify the exact compatibility and Gateway API support for your release before converting a production workload.



