An Ingress with the path /[A-Z]{3} and use-regex: "true" looks like it routes exactly one thing: three uppercase letters. Point it at a backend and curl /uuid — four lowercase letters — and the request sails through with a 200. Ingress-nginx regex matches are prefix-based and case-insensitive, so /uuid matches, and a migration that ports that pattern to a Gateway API RegularExpression match (full match, case-sensitive on Envoy-based implementations) turns working traffic into a 404 on cutover day.
That is behavior number one of five in the Kubernetes project's companion guide, published February 27, 2026, one month before the retirement it was written for. Its recurring warning deserves quoting in full: a seemingly correct translation can still cause outages if it does not consider ingress-nginx's quirks. Seven months after the repository went read-only, the fleets still on ingress-nginx are not short on migration walkthroughs — this blog alone has published the annotation translation table, the four-phase cutover, and the Gateway API v1.5 payoff. What most of them skipped is the layer underneath all of that: read the January retirement statement closely, then audit what your fleet silently depends on before any conversion tool runs. This post is that layer.
What the January statement actually says
On January 29, 2026, the Kubernetes Steering Committee and Security Response Committee issued a joint statement confirming the November 2025 retirement announcement. Maintenance ends March 2026. Datadog research puts ingress-nginx in about half of cloud-native environments, and the project had been held together by one or two people working in their free time. Fleets cite this statement constantly and misread it just as often. Here is what the two documents actually say, against the five assumptions that refuse to die:
| What fleets assume | What the documents actually say |
|---|---|
| The Ingress API is going away, so we must leave Ingress-shaped routing entirely | It retires one controller, kubernetes/ingress-nginx. It explicitly blesses migrating to "Gateway API or one of the many third-party Ingress controllers" — staying on the Ingress API with a maintained controller is a conforming outcome |
| F5's NGINX Ingress is affected too | The February companion guide states plainly that ingress-nginx and F5 NGINX Ingress are separate, unrelated controllers sharing only a dataplane. Only the community project retired |
| Things will break visibly so we will notice in time | "Existing deployments will continue to work, so unless you proactively check, you may not know you are affected until you are compromised." Silence is the failure mode, not the all-clear |
| Someone — a fork, a vendor, the community — will keep shipping security fixes | "There will be no more releases for bug fixes, security patches, or any updates of any kind" — and maintaining the codebase is "no longer reasonable or even possible ... even if resources did materialize" |
| There is a drop-in replacement that makes this a swap, not a project | "None of the available alternatives are direct drop-in replacements. This will require planning and engineering time." |
Start with the statement's own detection step, which costs thirty seconds and settles whether any of the rest applies to you:
kubectl get pods --all-namespaces --selector app.kubernetes.io/name=ingress-nginxIf that returns controller pods, also check for the image directly (catches nonstandard labels) and for Helm releases carrying the frozen chart:
kubectl get pods -A -o jsonpath='{range .items[*]}{.metadata.namespace}{"/"}{.metadata.name}{" "}{.spec.containers[*].image}{"\n"}{end}' | grep 'ingress-nginx/controller'
helm list -A | grep -i ingress-nginxWhat "no updates of any kind" has looked like since March
The statement's bluntness invited a reasonable suspicion: surely critical fixes would still ship. The seven months since are the answer, and every entry is dated:
| Date | What happened |
|---|---|
| March 19, 2026 | Final patch releases ship (1.15.1 is the last community release). Whatever you run after this, you run as-is |
| March 20, 2026 | Ingress2Gateway 1.0 ships, expanding ingress-nginx annotation coverage from three annotations to 30+ with per-annotation Parsed/Unsupported/Unrecognized tracking |
| March 24, 2026 | The repository is archived read-only. No releases, bugfixes, or CVE fixes of any kind from that moment on |
| August 7, 2026 | O3 Security reports that CVE-2026-4342 — fixed in the March 19 release — still lurks in production clusters that never applied it, with no upstream left to send a second warning |
| September 2026 | CVE-2026-42533, a critical NGINX heap overflow, lands with the frozen controller's embedded NGINX in the affected range and no community release to move to |
| November 2026 | Microsoft's support bridge — critical security patches for the AKS application-routing add-on's managed NGINX — ends. After that, managed NGINX gets no Azure support either |
"No updates of any kind" turned out to be literal, exactly as written. November 2026 is now one month away, and it is the last date on this timeline with anyone else's name on it. That is the decision the January statement demands: not whether to migrate, but whether the audit below starts this week on your schedule or later on a disclosure's schedule.
The audit, in order
Do these in order, and do all of them before running a conversion tool. Each item ends with what "done" looks like — an audit without a definition of done is a vibe.
0. Confirm exposure. Item done when you can name every cluster running the controller, the exact image tag on each, and whether the admission webhook is still enabled. The commands from the previous section give you the first two; the webhook matters because it was the IngressNightmare attack surface (CVE-2025-1974, CVSS 9.8, unauthenticated RCE, March 2025), and disabling it is the single highest-leverage hardening step for any fleet that stays a month longer.
1. Inventory every annotation. Item done when you hold a sorted count of every nginx.ingress.kubernetes.io/* key across all namespaces, with each row marked portable, rewritable, or dead-end:
kubectl get ingress -A -o json | jq -r '.items[].metadata.annotations // {} | keys[]' | sort | uniq -c | sort -rnMost fleets find the same shape: a long tail of boring keys (issuer, timeouts, body size) and a short list carrying the entire migration risk. Treat these as guilty until proven portable: configuration-snippet and server-snippet (raw config injection — the bug class that killed the project), auth-url / auth-signin (external-auth chains), canary + canary-weight (traffic splitting), whitelist-source-range (IP allowlists), custom-http-errors (rewrites your error surface), and ssl-passthrough (L4 passthrough with its own controller-flag requirement). Rows without a Gateway API equivalent are your actual project plan, measured in rewrites owed, not manifests converted.
2. Probe the five surprising behaviors. This is the February guide's core, and the step migration walkthroughs assume someone already did. Item done when each behavior below has a verdict for your fleet: preserve deliberately (reproduce it in the new config) or fix deliberately (correct the manifests so the quirk stops mattering). The guide's curl demos prove each quirk; the fleet-scale question is where each one hides in your manifests:
| # | Behavior | Where it hides in your fleet |
|---|---|---|
| 1 | Regex matches are prefix-based and case-insensitive | Every use-regex path: assume each pattern matches a larger, case-folded set than its author intended, then check access logs for what actually arrives |
| 2 | One Ingress's use-regex silently regex-ifies all paths of that host across all Ingresses | List every Ingress with the annotation, then list every other Ingress sharing those hosts — their Exact paths are regexes today and will not be tomorrow |
| 3 | rewrite-target silently implies use-regex, with all of behavior 2's side effects | Same host-sharing sweep as behavior 2, keyed on rewrite-target instead — run both sweeps, since either annotation flips the whole host to regex |
| 4 | Exact or Prefix paths missing a trailing slash get a 301 to the slashed path | Every path ending in /, plus controller access logs sampled for 301s — any client following those redirects needs an explicit redirect filter on the new side |
| 5 | URLs are normalized (., .., //) before matching | Access-log sampling for unnormalized request paths; most Envoy-based Gateway implementations normalize . and .. too, but verify yours rather than assuming it |
The two sweeps worth scripting today:
kubectl get ingress -A -o json | jq -r '.items[] | select(.metadata.annotations["nginx.ingress.kubernetes.io/use-regex"]=="true") | .spec.rules[].host' | sort -u
kubectl get ingress -A -o json | jq -r '.items[] | select(.metadata.annotations | has("nginx.ingress.kubernetes.io/rewrite-target")) | .spec.rules[].host' | sort -uEvery host either command prints is a host whose routing semantics change under a naive translation. Those hosts get migrated with explicit before/after route tests, not with a bulk conversion.
3. Document default-backend behavior. Item done when you can answer: what serves a request that matches no host today? Unmatched traffic in ingress-nginx lands on the default backend (a 404 page, unless you customized it via custom-http-errors or a custom default backend service). Gateway API has no default-backend concept — unmatched requests get whatever your implementation returns, typically its own 404. That gap is invisible in every translation table because there is no source field to mistranslate; it only shows up as a changed error surface your status pages, synthetic monitors, and API clients may depend on. Record today's behavior with one unmatched-host curl per environment before you migrate, so the new side can be held to it.
4. Document TLS-termination semantics. Item done when every TLS edge case below is written down with the config that produces it:
- Which Secrets terminate which hosts (
spec.tlsblocks), and what serves clients with no SNI match — ingress-nginx falls back to its--default-ssl-certificate, a behavior you must consciously reproduce or retire. - Whether anything relies on
ssl-passthrough(requires the controller's explicit enable flag; it is L4 passthrough, not termination, and ports to a TLSRoute-shaped answer, not an HTTPRoute one). - Whether backends expect re-encrypted traffic (
backend-protocol: HTTPS), which ports to backend-TLS policy on the new implementation, not to anything in the route match.
TLS is where "the converter handled it" fails most quietly, because a translation can be syntactically perfect and still terminate, pass through, or re-encrypt differently. The audit output here is a per-host table — terminate where, with which cert, passthrough or re-encrypt — that the converted manifests get diffed against.
Where this ends and the walkthroughs begin
The audit above is the input; conversion is a separate stage that consumes it. The sequence from here:
- Run
ingress2gateway printagainst the audited manifests and triage its per-annotation tracking — Parsed, Unsupported, Unrecognized — against your item-1 inventory. Anything the tool drops or flags should already be on your rewrites-owed list; if it is not, the audit missed it. Our translation-table post shows what that triage looks like on a realistic ten-annotation tenant set. - Stand up the new controller alongside ingress-nginx (separate LoadBalancer, separate IP) and dual-serve, shifting traffic per hostname with the before/after route tests from item 2 gating each move.
- Cut over hostname by hostname, then decommission in the order that closes the attack surface first: admission webhook off, controller deleted, CRDs last. The full no-flag-day choreography is in our four-phase cutover guide.
Note what this post deliberately did not do: recommend a controller, convert a single manifest, or move a single DNS record. Those are execution decisions with good guides already written. The audit is the decision layer those guides assume — and the step whose absence turns a planned migration into a month of diffing 404s.
One month remains before the last vendor patch bridge ends. The audit above is a day's work for most fleets: thirty seconds to confirm exposure, an afternoon for the annotation inventory and host sweeps, and a morning of access-log sampling for the behaviors manifests cannot show. Run it this week, while the schedule is still yours.
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.
Sources
- Ingress NGINX: Statement from the Kubernetes Steering and Security Response Committees (January 29, 2026)
- Ingress NGINX Retirement: What You Need to Know (November 11, 2025)
- Before You Migrate: Five Surprising Ingress-NGINX Behaviors You Need to Know (February 27, 2026)
- Announcing Ingress2Gateway 1.0: Your Path to Gateway API (March 20, 2026)
- IngressNightmare: Unauthenticated RCE in ingress-nginx (Wiz Research, March 2025)
- Announcing Gateway API support for App Routing (Azure, March 18, 2026 — November 2026 support bridge)
- CVE-2026-42533: Critical NGINX Heap Overflow Hits EOL Ingress-NGINX (HeroDevs)
- Ingress-NGINX EOL: CVE-2026-4342 Still Unpatched (O3 Security finding, August 7, 2026)



