Six months ago, the most widely deployed Ingress controller in Kubernetes history stopped receiving security patches. Not a deprecation warning, not a slowed release cadence — a full retirement. If your edge still runs ingress-nginx today, every request your tenants serve passes through code that will never be fixed again.
The deadline the ecosystem spent a year warning about — March 2026 — has come and gone. The repositories are read-only. The maintainers have moved on. And the vulnerability reports that used to end with "upgrade to the fixed version" now end with nothing at all. This post is the playbook for the rest of 2026: what the retirement actually means, the five translation traps that break naive migrations, which annotations survive the move to Gateway API and which need a rewrite, how to pick an implementation for machines you own, and the TLS cutover checklist to get there safely.
The short version of the timeline, so the urgency is concrete:
| Date | What happened |
|---|---|
| Nov 11, 2025 | SIG Network and the Security Response Committee announce the retirement: best-effort maintenance until March 2026, then nothing |
| Jan 29, 2026 | A rare joint statement from the Steering Committee and the SRC: check your clusters now, start planning migration |
| Early Feb 2026 | A batch of new CVEs lands — including config-injection flaws leading to remote code execution — patched in what are effectively the final releases |
| Feb 27, 2026 | Kubernetes publishes "Five Surprising Ingress-NGINX Behaviors You Need to Know" alongside Gateway API 1.5 |
| Mar 20, 2026 | ingress2gateway 1.0 ships, translating 30+ annotations days before the deadline |
| March 2026 | Maintenance halts. No further releases, bugfixes, or security updates — ever |
| Sept 2026 (now) | Your unmigrated edge has been running unpatched for half a year |
One clarification that still confuses teams: the Ingress API is not going away. What retired is the community-maintained controller — the kubernetes/ingress-nginx project that grew from an example implementation into the default edge for countless clusters. You can keep the Ingress API with a different controller. But upstream's recommendation, and the direction of all new investment, is Gateway API.
Why find-and-replace breaks: five behaviors that don't survive translation
The single most useful migration document published this year is the February Kubernetes blog post on surprising ingress-nginx behaviors. Its core warning: a seemingly correct translation can still cause outages if it ignores the controller's quirks. Every one of these has a concrete outage mode.
| # | The surprising behavior | What breaks after naive migration | The Gateway API fix |
|---|---|---|---|
| 1 | Regex matches are prefix-based and case-insensitive — /[A-Z]{3} matches /uuid | Routes that used to match return 404, because Envoy-based implementations do full case-sensitive matches | Use RegularExpression with explicit case handling, e.g. (?i)/[a-z]{3}.* |
| 2 | use-regex on one Ingress silently regex-ifies all paths of that host across all Ingresses | Exact: /Header (a typo for /headers) used to match; after migration it 404s | Audit every path on regex-enabled hosts; fix typos or convert to explicit RegularExpression |
| 3 | rewrite-target implies regex, with all of behavior 2's side effects | Same silent 404s, on hosts where nobody ever set use-regex | URLRewrite filter does not infect neighboring matches — but you must still fix the latent typos it was masking |
| 4 | A request missing a trailing slash gets an automatic 301 redirect (/my-path → /my-path/) | Clients depending on the redirect now get 404 | Add an explicit RequestRedirect filter rule for the slashless path |
| 5 | URLs are normalized before matching (dot segments resolved, slashes deduplicated per RFC 3986) | Depends on implementation — most Envoy-based ones normalize . and .. by default, but verify yours | Check your implementation's normalization docs; don't assume parity |
Behaviors 2 and 3 are the ones that bite experienced teams. Nobody remembers that three years ago someone added rewrite-target to one Ingress on a shared host, and that every Exact path on that host has secretly been a case-insensitive prefix match ever since. Before you convert anything, list every host with use-regex or rewrite-target anywhere on it and treat all of its paths as suspect.
The annotation inventory: what converts, what needs a rewrite
ingress2gateway 1.0 is the official translation tool, and the 1.0 release was a genuine leap — from 3 supported ingress-nginx annotations to more than 30, including CORS, backend TLS, regex matching, and path rewrites. Run it early; its warnings are your rewrite list. But go in knowing the two columns:
Converts cleanly (ingress2gateway handles these, verify the output):
rewrite-target→URLRewritefilter (ReplaceFullPathorReplacePrefixMatch)use-regex→RegularExpressionpath matches (with the case-sensitivity caveats above)enable-corsand friends → the HTTPRoute CORS filter, which graduated in Gateway API 1.5canary/canary-weight→ weightedbackendRefson a single rule — arguably cleaner than the annotation ever was
Has no Gateway API equivalent (each one is a real config rewrite, not a translation):
configuration-snippet,server-snippet,auth-snippet— arbitrary NGINX config injection. There is deliberately no counterpart; Gateway API does not expose proxy internals. Every snippet must be re-expressed as a native route feature or a vendor-specific extension (PolicyAttachment, BackendTrafficPolicy, or your implementation's CRDs), and some — custom Lua, exotic rewrite logic — need redesigning.auth-url/ external auth — no standard equivalent; each implementation wires external authorization differently.- Session affinity (
affinity: cookie), customload-balancealgorithms,proxy-*-timeouttuning, custom log formats — all implementation-specific now. Check your chosen implementation's policy APIs before you promise parity. defaultBackendfallbacks — there is no shared "default backend" concept. You must write an explicit lowest-precedence catch-all HTTPRoute (or accept your implementation's default error response). Drop this silently and unmatched hosts get a bare 404/500 with no custom error page — the outage mode nobody tests until a customer reports it.
A useful discipline: treat every ingress2gateway warning as a ticket, not a footnote. The tool tells you exactly which annotations it skipped. The teams that get hurt are the ones that convert the 90% that translates, ship it, and discover the skipped 10% was load-bearing.
Picking a Gateway API implementation for machines you own
Gateway API is a standard, not software — you still have to pick the controller and data plane that terminates your tenants' TLS. For a self-hosted fleet on owned hardware, the shortlist in late 2026 is stable: Envoy Gateway, kgateway, Cilium, Traefik, and NGINX Gateway Fabric. They all speak the same core APIs (Gateway, HTTPRoute, and — since v1.6 graduated them to Standard in August — TCPRoute and UDPRoute), so evaluate on operational axes instead of feature checklists:
| Axis | What to check before committing |
|---|---|
| Conformance | Read the implementation's published Gateway API conformance report. "Supports HTTPRoute" is not binary — timeout handling, regex semantics, and header manipulation vary. One implementation rejects unsupported fields loudly; another silently ignores them. Prefer loud. |
| Install footprint | Does it reuse something you already run (Cilium if it's your CNI, Istio if it's your mesh) or add a new control plane? On a small fleet, every new control plane is on-call surface. |
| TLS model | How are listeners, SNI routing, and certificate rotation handled? Can tenants bring custom domains without touching the shared Gateway? (See ListenerSet below.) |
| Policy APIs | Where do your ex-snippet behaviors live now — rate limiting, auth, timeouts? Compare the maturity of each project's policy CRDs, not just its route support. |
| L4 needs | If you terminate non-HTTP traffic (databases, game servers, custom protocols), confirm TCPRoute/TLSRoute support and maturity — Standard-channel status in v1.5/v1.6 doesn't mean every implementation is equally far along. |
There is no universal winner, and any post that names one is selling something. The right question is narrower: which implementation makes your current snippet inventory expressible with the least custom machinery? Answer that by mapping your top five snippets to each candidate's policy docs before you install anything.
The TLS cutover checklist
For a PaaS, the edge migration is really a TLS migration with routing attached. Ingress-nginx habituated everyone to per-Ingress TLS via annotations (cert-manager.io/cluster-issuer and friends). Gateway API splits the world: cluster-operator-owned Gateway resources hold listeners and certificates, while team-owned HTTPRoute resources attach to them. That split is cleaner — and it means your certificate automation needs re-plumbing, not just re-pointing. Validate each of these before you shift traffic:
- Listeners carry the certs now. Move every hostname's certificate from Ingress TLS blocks to Gateway listeners backed by cert-manager
Certificateresources. Audit that every served hostname has a listener match — Gateway API will not serve a hostname no listener claims. - Wildcard issuance still goes through DNS-01. If you issue
*.tenant-region.yourplatform.com, that flow is unchanged conceptually (cert-managerCertificate+ DNS-01 solver), but the resulting Secret is now referenced from the Gateway, not the Ingress. Verify renewal updates propagate to the data plane without a restart. - Per-tenant custom domains become HTTPRoutes on the shared Gateway. Each
customer.comCNAME-ing to you gets an HTTPRoute with ahostnamesmatch — but its certificate needs a home. This is what Gateway API 1.5'sListenerSetis for: letting teams manage their own TLS on a shared Gateway instead of forcing every cert change through the cluster operator. cert-manager has been tracking ListenerSet support through 2026; confirm your cert-manager version handles your issuance pattern before cutover. - HTTP-01 challenge routing must exist on the new Gateway. cert-manager's HTTP-01 solver creates challenge routes assuming Ingress semantics by default. Either keep an Ingress-class solver path alive during migration or configure the solver for your Gateway implementation — otherwise renewals fail silently mid-migration and you discover it via expiry alerts.
- Verify SNI and hostname coverage with a scan, not a spot check. Enumerate every hostname in every Ingress, then assert each one resolves to a listener plus an attached route on the new Gateway. The trailing-slash and normalization behaviors from section one mean "same hostnames" is necessary but not sufficient — replay production access logs against the new edge in staging and diff status codes.
Item 5 deserves emphasis because it catches everything above. Access-log replay is the closest thing this migration has to a proof: same requests in, same status codes out. Any 404 that wasn't a 404 before is a behaviors-2/3/4 regression hiding in your config.
Cutover runbook: dual-run, shift, delete
Don't flag-day the edge. The controllers coexist fine, which makes this a traffic-shifting problem rather than a forklift upgrade:
- Deploy the new Gateway alongside ingress-nginx. Same backends, different entry point. Nothing serves production through it yet.
- Mirror one low-risk hostname. A status page or internal tool — something with real traffic and forgiving users. Compare status-code distributions between old and new edges for at least a full business day.
- Shift DNS per hostname, slowest-first. CNAME or A-record one production hostname at a time to the new edge. After each shift, watch 4xx/5xx rates for that host specifically — global dashboards average away per-host regressions.
- Keep rollback boring. Leave ingress-nginx installed but scaled down (or DNS-pointed-away) until every hostname has baked on the new edge for at least one full certificate-renewal cycle. Rollback should be a DNS change, not a reinstall.
- Decommission with a scan. When the last hostname has moved, confirm with the same detection command the retirement post gave everyone —
kubectl get pods --all-namespaces --selector app.kubernetes.io/name=ingress-nginx— then remove the controller, its CRDs, and the legacy annotations. Dead edge config left in the cluster is how the next CVE finds you.
One thing not to do: reach for a community fork as a permanent plan. Forks buy calendar time at the cost of consolidating your edge on a shrinking maintenance base — the exact situation that forced the retirement. If a fork is your bridge, name the far side of the bridge and the quarter you cross it.
The edge you own is the edge you must maintain
The ingress-nginx retirement is the clearest demonstration in years of a self-hosting truth: owning the machine means owning the lifecycle of everything on it. A managed platform migrates your edge for you and tells you afterward. On your own fleet, the November 2025 announcement, the January joint statement, the February CVEs, and the March cutoff were all messages addressed directly to you — and the teams that treated them as such are done already.
If you're reading this in the second half of 2026 with ingress-nginx still terminating production TLS, the playbook above is the fastest safe route out: inventory the behaviors, translate what's translatable, rewrite what isn't, re-plumb TLS deliberately, and shift traffic hostname by hostname. The unmaintained edge doesn't get safer with time. It just gets older.
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.



