Skip to main content

Ingress2Gateway 1.0: A Scripted Migration Path Off the Now-Read-Only Ingress-NGINX

10 min readDora NodaDora Noda
Share
On this page

On March 24, 2026, the most widely used Ingress controller in the Kubernetes ecosystem became read-only. The kubernetes/ingress-nginx repository was archived after its final releases shipped on March 19, and there will be no new features, no bug fixes, and — the part that should end every "we'll migrate later" discussion — no more CVE patches (retirement announcement, migration guide).

If your fleet still terminates tenant traffic through ingress-nginx, you are now running an internet-facing proxy whose next IngressNightmare-class RCE (CVE-2025-1974) will never get a fix. The good news: four days before the archive, SIG Network shipped ingress2gateway 1.0 (March 20, 2026), a stable tool that converts existing Ingress objects — annotations included — into Gateway API resources instead of forcing you to hand-write every tenant's HTTPRoute from scratch.

This post is the migration triage for a fleet operator: what the scripted pass converts cleanly, what still needs hand-migration, and the five behavioral gotchas that cause silent outages when you convert syntax but not semantics.

The migration on one page​

Run one command and you learn which bucket every tenant Ingress falls into:

bash
ingress2gateway print --providers ingress-nginx --all-namespaces 2>&1 | tee migration-preview.yaml
ingress2gateway print --providers ingress-nginx --all-namespaces 2>&1 | grep "WARNING"

The tool prints converted Gateway and HTTPRoute manifests to stdout and logs a WARNING for every annotation it cannot convert. Those warnings are your hand-migration backlog. Everything without a warning is bucket A:

BucketContentsAction
A. Converts automaticallyHost/path routing, TLS termination, rewrite-target → URLRewrite filter, canary/canary-weight → weighted backendRefsReview the printed manifests, apply
B. Needs vendor extensionsauth-url/auth-response-headers → ext-auth policy, limit-rps/limit-connections → rate-limit policy, session affinity, CORSHand-write per your chosen implementation's CRDs
C. No equivalentconfiguration-snippet lines that depend on nginx internals, modsecurity-* WAF rulesRe-implement (Envoy patch policy, separate WAF) or drop

This three-bucket framing follows Kazu's annotation walkthrough, which pairs every category with before/after manifests in shinagawa-web/ingress-nginx-to-gateway-api. One subtlety worth knowing up front: bucket C is judged line by line, not annotation by annotation. Most configuration-snippet contents, broken down line by line, land in bucket A (a response header becomes a standard filter) or bucket B (a request-ID becomes an Envoy patch policy). Only directives that depend deeply on nginx internals are truly unmigratable.

What makes the scripted pass viable now, versus the 0.x era, is what 1.0 landed:

  • 30+ supported ingress-nginx annotations, up from 3 in the previous release — the common cases (rewrites, canary weights, TLS, CORS-adjacent settings) convert without hand-editing.
  • A stable print workflow that also emits GatewayClasses, TLSRoutes, TCPRoutes, and ReferenceGrants where the input implies them, not just Gateways and HTTPRoutes.
  • Pluggable emitters for vendor-specific output (e.g. --emitter envoy-gateway), so bucket B items can come out as your implementation's extension resources instead of stopping at standard CRDs.

The 1.0 scope is documented in the release coverage from March 31; providers include ingress-nginx and kong, so Kong-origin Ingress objects get the same scripted first pass.

Why the retirement happened, and what replaced it​

The cause was not technical. The November 2025 retirement announcement was blunt: not enough maintainers, with effectively a skeleton crew carrying the most-deployed Ingress controller for years. A Steering Committee and Security Response Committee statement followed on January 29, 2026, and then the clock ran fast:

DateEvent
Nov 11–12, 2025Retirement announced
Jan 29, 2026Steering Committee + SRC statement
Feb 27, 2026"Before You Migrate" behaviors post + Gateway API 1.5 (ListenerSet, HTTPRoute CORS filter)
Mar 19, 2026Final ingress-nginx releases
Mar 20, 2026ingress2gateway 1.0
Mar 24, 2026Repository archived read-only

No successor was named — just a recommendation to migrate to Gateway API. Even the InGate successor experiment ended up archived under kubernetes-retired. That reads as an abdication until you see the reasoning: the ecosystem already had five-plus conformant Gateway API implementations, so blessing one successor would have been picking a winner rather than filling a gap.

Gateway API changes the ownership model, which matters for how you assign the migration work. Ingress gave you one flat object the cluster admin managed end to end. Gateway API splits it into three roles: the infrastructure team owns the GatewayClass, platform ops owns the Gateway (listeners, TLS, attached policies), and developers own HTTPRoute objects that attach to it. For a self-hosted PaaS, that split is a feature: your controller can own the Gateway while each tenant's route stays a small, reviewable object.

Five behaviors that break silently if you only convert syntax​

SIG Network's February 27 post documents five ingress-nginx quirks where a structurally correct conversion still causes outages. Audit your fleet for these before you apply anything the tool prints — ingress2gateway converts declared intent, not accidental behavior your clients may depend on:

#Surprising behaviorOutage if ignoredGateway API fix
1Regex matches are prefix-based and case-insensitive (/[A-Z]{3} matches /uuid)Silent 404s — Gateway API regex is full-match, case-sensitiveWiden the pattern explicitly (/[a-zA-Z]{3}.*)
2use-regex: "true" on one Ingress makes all paths for that host regex, across all IngressesExact paths that "worked" via accidental regex now 404Audit every Ingress sharing the host; convert intended-regex paths only
3rewrite-target silently implies use-regex, with all of #2's side effectsSame as #2, with no regex annotation to grep forSame audit — search for rewrite-target, not just use-regex
4/my-path gets an automatic 301 to /my-path/ (Exact or Prefix, non-regex)Clients following the redirect now get 404Add an explicit RequestRedirect filter with ReplaceFullPath
5URL normalization before matching (./.. collapsed, // deduplicated)Mostly safe — Istio, Envoy Gateway, and Kgateway normalize ./.. by defaultVerify against your implementation's docs

Items 2 and 3 are the ones that bite fleets specifically. A tenant typo like path: /Header with pathType: Exact returns 200 today because another tenant's Ingress for the same host set use-regex — and after migration it 404s. No per-object converter can flag that; only a host-level audit across all namespaces catches it. That is the concrete reason the scripted pass is step one, not the whole migration.

Worked runbook: scripted pass to zero-downtime cutover​

The production-safe shape is a parallel run: both stacks serve, DNS shifts gradually, ingress-nginx retires last. End to end:

1. Install the tool. Either flavor pins the 1.0 release:

bash
go install github.com/kubernetes-sigs/ingress2gateway@v1.0.0
bash
curl -LO https://github.com/kubernetes-sigs/ingress2gateway/releases/download/v1.0.0/ingress2gateway-linux-amd64
chmod +x ingress2gateway-linux-amd64
sudo mv ingress2gateway-linux-amd64 /usr/local/bin/ingress2gateway

2. Print, don't apply. Generate the conversion and collect the backlog in one pass:

bash
ingress2gateway print --providers ingress-nginx --namespace production > gateway-resources.yaml
ingress2gateway print --providers ingress-nginx 2>&1 | \
  grep "WARNING.*annotation" | sort -u > unsupported-annotations.txt

Read every WARNING as a ticket: auth-url becomes an ext-auth policy, limit-rps a rate-limit policy, modsecurity-* a separate WAF decision. If you have already chosen your target, re-run with its emitter (e.g. --emitter envoy-gateway) to get extension resources drafted too.

3. Install the Gateway API foundation. Apply the standard-channel CRDs, then your chosen implementation and its GatewayClass:

bash
kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.4.0/standard-install.yaml
kubectl get gatewayclass

One caution carried over from early-2026 field reports: installing Gateway API CRD v1.5.x under Istio 1.28/1.29 crashed istiod through API field mismatches, so several guides pin v1.4.0 for Istio fleets. Check your implementation's supported-CRD matrix before reaching for the newest bundle — "latest CRDs" is not automatically the safe pick during a migration window.

4. Apply routes alongside, not instead. Create the Gateway (listeners, TLS via your existing cert-manager setup), apply the converted HTTPRoutes, and verify against the Gateway's address before touching DNS. Keep ingress-nginx serving production throughout.

5. Shift traffic gradually. Move DNS weights (or a test hostname first), watch 4xx/5xx rates per route — the section above tells you which 404s mean "behavioral gap" rather than "bad conversion" — then drain ingress-nginx and delete the old Ingress objects only after the new stack has served a full traffic cycle clean.

Picking the target implementation​

Writing an HTTPRoute does nothing until some controller reads it, so the target choice is part of the migration. Nobody can pick it for you sight unseen, but a fleet operator can narrow the field fast with three questions, drawing on the four-way comparison (Envoy Gateway, Istio, NGINX Gateway Fabric, Traefik):

  • Do you already run a mesh? If yes, Istio as a pure Gateway API controller (no sidecars required) reuses operational knowledge you have. If no, installing a whole mesh stack just to terminate edge traffic is the heaviest option on the table.
  • How much bucket B do you have? Auth, rate limiting, and session affinity live in Extended or implementation-specific CRDs — Envoy Gateway's SecurityPolicy, Istio's AuthorizationPolicy — and the moment you write one, you are locked to that implementation. Envoy Gateway (CNCF incubating) and Istio (CNCF graduated) lead on extension maturity; Traefik and NGINX Gateway Fabric route the basics equally well but their vendor extensions are thinner. Size your bucket B before choosing.
  • How much nginx muscle memory matters? NGINX Gateway Fabric shares the nginx data plane, which makes it the gentlest behavioral landing — but it is F5's separate Gateway API project, not a continuation of ingress-nginx, and ships at a lower cadence. Kong deserves a look for the same "one codebase answers both APIs" reason: its controller serves classic Ingress and HTTPRoute side by side, which simplifies the parallel-run phase.

Two escape hatches exist if Gateway API itself is more migration than you can schedule right now: Traefik's Kubernetes provider reads ingress-nginx annotations directly, letting you swap the controller without rewriting objects — and HAProxy's ingress controller ships a Gateway API mode for a staged move. Treat those as bridges, not destinations; the ecosystem's conformance energy is all behind Gateway API now.

What to do this week​

The deadline framing is over — the repository is archived and every week on ingress-nginx is unpatched-CVE exposure. The scripted pass is what makes "this week" realistic instead of aspirational:

  1. Inventory annotations across all namespaces (kubectl get ingress -A plus the WARNING grep above) and sort them into buckets A/B/C.
  2. Run the scripted pass into a file, review the printed Gateways and HTTPRoutes, and turn every WARNING into a bucket B/C ticket with an owner.
  3. Audit the five behaviors per host, not per object — especially use-regex/rewrite-target leakage across tenants sharing a hostname.
  4. Pick the target implementation using your bucket B size, then stand up CRDs + Gateway in parallel with production.
  5. Cut over one low-risk host first, watch 4xx rates with the behavior table open, then schedule the fleet-wide DNS shift.

The teams that will hurt are the ones hand-writing HTTPRoutes from scratch under pressure six months from now, after the first unpatched CVE drops. The tool is stable, the behaviors are documented, and the conversion of the common 80% is one command. Run it now while the migration is still a planned project instead of an incident response.

Bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on machines you own. Star the repo on GitHub or deploy your first app today.

Related articles

Check your move before you migrate

Free browser tools: check a render.yaml or your Render scripts against bex, or turn a Heroku app or docker-compose.yml into a draft render.yaml. Nothing you paste leaves your browser.

Open the migration tools