The most widely deployed Ingress controller in Kubernetes history no longer has maintainers. On November 11, 2025, SIG Network and the Security Response Committee announced the retirement of ingress-nginx; best-effort maintenance ended in March 2026, and the repository was archived to read-only later that month. From here on, every CVE filed against it ships with the same remediation plan: none. The Kubernetes Steering Committee's January 2026 statement put the exposure plainly — the project underpins "about half of cloud native environments" — and told all of them to begin migrating immediately.
If your cluster still routes traffic through ingress-nginx, you are not on a deprecated path. You are on an unsupported one. The replacement SIG Network points you at is the Gateway API, and the April 2026 v1.5 release — the project's biggest, graduating six features to stable — removed the last credible excuse to wait for the API to mature. This post is the concrete version of that migration: what moves where, what the tooling does for you, and where the new model is genuinely better rather than just a forced rewrite.
Short version first — the whole migration is this table:
| Ingress world | Gateway API world |
|---|---|
Ingress resource per app | HTTPRoute per app, attached to a shared Gateway |
Controller Deployment + IngressClass | Implementation install + GatewayClass |
spec.tls[].secretName | Gateway listeners with tls.certificateRefs |
rewrite-target annotation | URLRewrite filter on the route rule |
canary-* annotations | Weighted backendRefs (native traffic splitting) |
enable-cors / cors-* annotations | CORS filter (stable since v1.5) |
configuration-snippet | Nothing portable — implementation-specific policy or extension |
The deadline already passed
The retirement played out on a fixed calendar, and every date on it is now in the past:
| Date | Event |
|---|---|
| November 11, 2025 | SIG Network and the Security Response Committee announce retirement; best-effort maintenance only |
| January 29, 2026 | Steering Committee and SRC joint statement reiterates the March end-of-life and the security exposure |
| March 2026 | Maintenance halts: no further releases, bugfixes, or security patches |
| March 24, 2026 | Repository archived to read-only |
Two facts make "we'll migrate when something breaks" a bad plan. First, existing deployments keep running and existing artifacts (Helm charts, container images) stay available, so nothing forces the issue visibly — the failure mode is silent: a vulnerability with no patch, or a Kubernetes upgrade the controller never gets tested against. Second, migration activity spiked 300% in February–March 2026 as teams raced the deadline (per VMware's post-deadline migration guide), which means operators who waited are now competing for migration attention with everyone else who waited.
Why this retirement is final
Retirements sometimes get reversed by a fork or a vendor rescue. Three details from the announcement explain why this one won't be.
First, the maintainer math never worked. For years the project had one or two people doing development in their own time, after work hours and on weekends, while serving roughly half the ecosystem. That is not a bus-factor problem; it is a project that was already running on fumes while everyone assumed it was infrastructure.
Second, the flexibility that made ingress-nginx popular became the reason it couldn't be secured. The configuration-snippet annotations — arbitrary NGINX directives injected from Ingress YAML — went from beloved escape hatch to documented security liability. Yesterday's flexibility is today's unfixable technical debt, in SIG Network's own framing, and no fork inherits a smaller debt.
Third, there is no in-family successor waiting. The maintainers' 2024 plan was to wind ingress-nginx down while building InGate, a replacement controller, with the Gateway API community. The announcement confirmed InGate never matured far enough to be that replacement, and it is being retired too. The path forward is Gateway API on somebody's implementation — not a renamed ingress-nginx.
What actually has to move: the before/after map
For a git-push PaaS, the routing layer is two things: TLS termination with per-app certificates, and custom-domain routing to the right backend. Here is the smallest realistic version of both, before and after.
Before — one Ingress, annotations doing the real work:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: tenant-app
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /$2
spec:
ingressClassName: nginx
tls:
- hosts: [app.example.com]
secretName: app-example-com-tls
rules:
- host: app.example.com
http:
paths:
- path: /api(/|$)(.*)
pathType: ImplementationSpecific
backend:
service:
name: tenant-api
port:
number: 8080After — the platform owns the Gateway (listeners, TLS):
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: platform-gateway
spec:
gatewayClassName: platform-gateway-class
listeners:
- name: https
protocol: HTTPS
port: 443
hostname: "*.example.com"
tls:
certificateRefs:
- name: app-example-com-tlsAnd the tenant owns the HTTPRoute (routing):
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: tenant-app
spec:
parentRefs:
- name: platform-gateway
hostnames: [app.example.com]
rules:
- matches:
- path:
type: RegularExpression
value: /api(/|$)(.*)
filters:
- type: URLRewrite
urlRewrite:
path:
type: ReplaceFullPath
replaceFullPath: /$2
backendRefs:
- name: tenant-api
port: 8080Three things to notice. The TLS block moved up from the app's manifest to the platform's Gateway — certificate lifecycle is now infrastructure's job, which is where a PaaS wants it. The rewrite stopped being a regex smuggled through an annotation and became a typed filter the API server validates. And the route attaches to the Gateway by reference, so the platform team can rewire listeners without touching tenant manifests.
The migration in practice
SIG Network shipped the mechanical part. ingress2gateway 1.0 landed March 20, 2026 — four days before the retirement deadline — and converts Ingress manifests (plus a curated set of ingress-nginx annotations) into Gateway and HTTPRoute YAML. One independent source-level count puts the recognized set at 47 distinct nginx.ingress.kubernetes.io/* annotations (often quoted as "30+"): rewrites, CORS, session affinity, and the other "clean" annotations convert syntactically without drama.
What it skips is the actual migration plan. The tool flatly declines the hard cases — no rate limiting, no auth-url/auth-signin external auth, no configuration-snippet, no TCP/UDP tcp-services passthrough. Each of those needs a destination picked by a human: auth moves to an implementation's auth policy or an external identity-aware proxy, rate limiting to a Policy CRD or a sidecar, snippets to whatever first-class feature (or accepted loss) covers the behavior the snippet was hacking in. Audit those four categories first; everything else is bulk conversion.
A cutover checklist that has worked since the deadline rush:
- Inventory annotations. List every
nginx.ingress.kubernetes.io/*key in the fleet and sort into converts-cleanly versus the four hard cases above. The hard cases are your schedule; the rest is an afternoon. - Pick an implementation before converting.
ingress2gatewayhas per-implementation emitters, and Policy CRDs differ per project — converting twice is the tax for choosing late. - Run both data planes. Install the Gateway API implementation alongside ingress-nginx, convert routes, and shift traffic per-hostname (DNS or a weighted split) rather than flag-daying the edge.
- Migrate TLS issuance first. If cert-manager feeds your Ingress secrets, re-target
Certificateissuance at the Gateway's secrets and confirm renewal works before any route moves. - Delete the controller last. Only remove ingress-nginx once every hostname has served production traffic through the new Gateway for at least one full certificate-renewal cycle.
Genuinely better, not just different
A forced migration earns its keep only if the destination is better. Four places where Gateway API clears that bar for a PaaS-shaped edge:
Header-based matching and traffic splitting are first-class. Ingress can match host and path; everything else was annotations. An HTTPRoute rule matches headers and query params natively, and backendRefs carry weights, so canary-by-header and percentage splits are typed API fields instead of canary-* annotation folklore that only one controller understood.
The platform/tenant split is structural. GatewayClass and Gateway belong to the platform team; HTTPRoute belongs to whoever owns the app, attached by reference with ReferenceGrant governing cross-namespace access. Under Ingress, that separation was convention plus RBAC prayer; here it is the object model. v1.5's stable ListenerSet extends the same idea to listeners, letting teams contribute listeners to a shared Gateway without all editing one resource — the multi-tenant pattern a PaaS edge actually needs.
CORS stopped being annotation soup. The v1.5 graduation of the HTTPRoute CORS filter — origins (including https://*.bar.com-style wildcards), methods, headers, credentials, max-age — replaces a dozen cors-* annotations with one validated filter. Small feature, large readability win on every API-serving route.
TLS in both directions is now stable API. v1.5 also graduated client-certificate validation (frontend mTLS at the Gateway) and certificate selection for Gateway-to-backend TLS origination, alongside TLSRoute for SNI-based passthrough/terminate TCP routing. A PaaS terminating tenant TLS at the edge and re-originating it to backends finally does both in portable API rather than controller-specific annotations.
Picking an implementation
At the v1.5 announcement, seven implementations were already fully conformant: Agentgateway, Airlock Microgateway, GKE Gateway, HAProxy Ingress, kgateway, NGINX Gateway Fabric, and Traefik. One naming trap to defuse immediately: NGINX Gateway Fabric is not ingress-nginx renamed — it is F5's separate Gateway API implementation, and migrating "from NGINX to NGINX" still means new CRDs, new install, and the annotation audit above.
For a self-hosted operator, the shortlist logic is simple. If you already run Envoy anywhere, an Envoy-based Gateway (Envoy Gateway, kgateway) keeps one proxy to learn. If your edge is Traefik or Cilium today, their Gateway API support is the lowest-friction move. Weight three things beyond conformance badges: which Policy CRDs cover your hard cases from the audit (this is where portability ends — policies are per-implementation by design), how cert-manager integrates with the Gateway's certificate model, and whether the project's release pace matches your upgrade discipline. Conformance tells you the standard parts work; your annotations tell you which non-standard parts you actually need.
What stays hard
Honest caveats, because the migration guides undersell them:
- The long tail stays non-portable. The annotation long tail maps to per-implementation Policy CRDs, which means the least portable part of your Ingress setup becomes the least portable part of your Gateway setup — budget real design time there, not just conversion time.
- TCP/UDP lagged HTTP by a release.
TCPRoute/UDPRouteonly reached the Standard channel in v1.6 (June 2026), so confirm your implementation's support for non-HTTP edge traffic before promising timelines. - Effort is measured in change windows, not days. Community playbooks written during the deadline rush suggested three to four months end-to-end (assess, pilot, staging, production). Starting now, with no EOL clock left to race, the constraint is your own change windows — not the calendar.
None of that changes the verdict. The old controller is unmaintained, the new API is stable where a PaaS needs it, and the conversion tooling handles the bulk. What remains is the part no release can ship for you: auditing what your annotations actually do and giving each one a new home.
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.



