The annotations are the real migration. A real-world NGINX Ingress isn't just a hostname and a backend. It's auth-url pointing at an external OAuth service, limit-rps: 10 enforced per-client, affinity: cookie with a custom session name, canary-weight: 20 for a staged rollout, and six cors-* fields that took someone a week to get right. Dropping those annotations means your auth breaks, your rate limiting disappears, your sticky sessions stop working, and your canary routes everything to prod. The Kubernetes SIG tool ingress2gateway handles basic routing — host, path, backend. It explicitly documents that annotation coverage is out of scope. That's a reasonable decision for an upstream tool. But it means the hard part of migration is still entirely manual.
So did some mapping work: 50+ nginx.ingress.kubernetes.io/* annotations, each one researched against both Traefik v3 and Gateway API (Envoy Gateway), with a status of supported / partial / unsupported and a note on what "partial" actually means in practice. Then I wrapped it in a CLI and a local web UI.
What it does:
- ing-switch scan - ing-switch analyze --target traefik — produces a per-ingress, per-annotation compatibility table. Each annotation gets a status and a note. - ing-switch migrate --target gateway-api — generates the complete output directory: GatewayClass, Gateway, HTTPRoutes and others - ing-switch ui — a four-page local web dashboard if you prefer a visual walkthrough.
Four things I had to figure out that aren't obvious from the spec:
1. You cannot put a RequestRedirect filter and backendRefs in the same HTTPRoute rule. This catches everyone who tries to handle HTTP→HTTPS redirect in a single route — the spec forbids it, and some implementations silently accept it while others reject it. The correct approach is two separate HTTPRoute resources: one with sectionName: http for the redirect, one with sectionName: https-N for the backend. Getting this wrong causes redirect loops on HTTPS (the HTTPS request gets redirected, too).
2. backendRequest timeout must be ≤ request timeout. A typical nginx config has proxy-read-timeout: 300 and proxy-connect-timeout: 5. Mapping these naively — read→backendRequest, connect→request — violates the constraint because 300 > 5. The tool maps only proxy-read-timeout → backendRequest and explains why the other one is dropped.
3. HTTPS listeners need allowedRoutes.namespaces.from: All or your HTTPRoutes show attachedRoutes: 0 in kubectl get gateway, even when the namespace matches. Nothing in the error output tells you why. This took an embarrassing amount of time to debug on a live cluster.
4. Paths like /app(/|$)(.*) are valid NGINX regex paths but fail Gateway API's PathPrefix validation (parentheses, pipes, and brackets are invalid characters). Auto-detection of regex characters switches the path type to RegularExpression automatically, even when use-regex annotation is absent.
The limitations:- Traefik migration is more complete. Almost every annotation has a direct Traefik equivalent. The generated Traefik YAML is ready to apply in most cases. - Gateway API has 14–15 annotations that are genuinely unsupported in the core spec: cookie-based canary routing, session-cookie-samesite, basic auth, proxy-buffering, and a few others. The tool tells you this upfront with the specific workaround for each. - The tool generates files. It does not apply them. Zero kubectl apply without explicit human sign-off.