On March 24, 2026, kubernetes/ingress-nginx — the controller sitting in front of more Kubernetes clusters than any other — went read-only. No more releases. No more bugfixes. No more CVE patches, ever, for the component that terminates TLS and routes every byte of external traffic into your cluster. If you run a self-hosted PaaS that auto-provisions an Ingress and a cert-manager certificate every time a tenant pushes code, that component is almost certainly ingress-nginx, and it is now a security liability with an expiration date already in the past.
This isn't a slow-motion deprecation you can plan around next quarter. It already happened. The question left is which of the two sanctioned exits you take, and what your routing layer should look like on the other side. Here's the decision, made concretely: a head-to-head of the two paths, the actual YAML each one produces, and where the object ownership boundary should sit once a git-push platform is generating these resources instead of a human hand-writing annotations.
Why One Maintainer's Burnout Became Your Incident
Ingress-nginx's retirement wasn't a strategic sunset — it was a maintenance model that ran out of road. For years the project was kept alive by one or two people doing security triage after their day jobs, on a controller whose defining feature — arbitrary NGINX configuration injection via annotations — kept surfacing as a fresh CVE class. The Kubernetes Steering and Security Response Committees said as much directly in their January 2026 statement: the flexibility that made ingress-nginx the default choice for a decade is the same flexibility that made it unmaintainable.
The official retirement announcement set the terms in November 2025 — best-effort support until March 2026, then nothing. That date came and went. The GitHub repo is now archived and read-only, kept around for reference only. Any CVE discovered in ingress-nginx from this point forward — and given its history, one will be — has no upstream fix. Ever. You'd be patching it yourself, forking a project you don't maintain, in the one place in your stack that terminates TLS and decides which tenant's traffic goes where.
For a self-hosted PaaS this isn't abstract. Auto-provisioning routing per tenant deploy is table stakes — it's the whole "push a git repo, get an HTTPS URL" promise. Most of the self-hosted platforms that make that promise (and no small number of internal platform teams) built it on Ingress objects like this:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: tenant-acme-web
annotations:
kubernetes.io/ingress.class: "nginx"
nginx.ingress.kubernetes.io/rewrite-target: /
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/proxy-body-size: "50m"
cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
tls:
- hosts: ["acme.tenant-apps.example.com"]
secretName: acme-web-tls
rules:
- host: acme.tenant-apps.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: acme-web
port: { number: 8080 }Every one of those nginx.ingress.kubernetes.io/* annotations is a bet on a controller that will never receive another security fix. That's the whole problem in one YAML block: the config format a platform generates on every deploy is now frozen against a project with no future.
Two Viable Exits — Compared Head to Head
SIG Network didn't just announce the retirement and walk away. The same window that closed out ingress-nginx opened its replacement tooling: Ingress2Gateway 1.0 shipped March 20, 2026, four days before the EOL date, specifically so nobody would be migrating without a tested path. But "migrate to Gateway API" and "migrate this week" are different asks, and the ecosystem converged on two distinct answers rather than one.
| Traefik drop-in (stopgap) | Gateway API migration (destination) | |
|---|---|---|
| What changes | Swap the controller binary; existing Ingress objects and nginx.ingress.kubernetes.io/* annotations are read as-is | Rewrite Ingress → Gateway + HTTPRoute; annotations become structured fields |
| Migration effort | Near-zero — Traefik's NGINX Ingress provider parses ingress-nginx annotations natively, no YAML rewrite | Per-resource conversion, one annotation set at a time (30+ common ones auto-translated) |
| Tooling | Traefik's built-in NGINX-annotation provider | ingress2gateway 1.0, with controller-level integration tests proving behavioral equivalence |
| Downtime risk | Low — same object model, new controller reading it | Low if done incrementally (Traefik and other Gateway-API-native controllers can run both models side by side during transition) |
| Where it leaves you | Off the dead project, but still on the Ingress API and its annotation-as-config pattern | On the API SIG Network is actively investing in, with a structured resource model instead of magic strings |
| When to pick it | You need to be off ingress-nginx now and can't schedule a resource-model rewrite this quarter | You're generating these resources programmatically (i.e., you're a platform, not a one-off cluster) and want to stop re-encoding vendor-specific annotations forever |
The Traefik team's own pitch is explicit that this is a two-phase plan, not a rival destination: decommission ingress-nginx onto Traefik first without touching your resource model, then modernize to Gateway API on your own timeline instead of under a CVE deadline. That's a reasonable move for a single cluster with a backlog of hand-written Ingress YAML nobody wants to touch under pressure.
It's the wrong move for a platform. If your control plane is the thing generating these resources on every git push, you don't have a backlog of legacy YAML to protect — you have a template. Rewriting a template once is cheaper than running an annotation-translating shim indefinitely, and Gateway API is where cert-manager, the ecosystem, and SIG Network's own roadmap are pointed. For a PaaS control plane specifically, the second column is the one worth building against.
What the Migration Actually Produces
This is the part the two-phase advice above skips past: what do you actually generate once you've moved? Not a kind: Ingress translated 1:1 — Gateway API splits the single Ingress object into three roles, and understanding that split is the actual value of the migration, not a cosmetic rename.
GatewayClass— cluster-scoped, one per controller (analogous toIngressClass). A platform installs this once, not per tenant.Gateway— the listener: which hostnames/ports it accepts traffic on, and where TLS terminates. A platform typically owns a small, fixed number of these — often one per entry point or per node pool, shared across many tenants.HTTPRoute— the routing rule: which hostname/path goes to which backendService. This is the object a platform generates per tenant app, replacing the per-tenantIngressfrom the example above.
Converting the earlier Ingress with ingress2gateway produces roughly this — a Gateway (created once, shared) plus an HTTPRoute (created per tenant deploy):
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: tenant-apps-gateway
spec:
gatewayClassName: traefik
listeners:
- name: acme-web-https
protocol: HTTPS
port: 443
hostname: acme.tenant-apps.example.com
tls:
certificateRefs:
- name: acme-web-tls
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: tenant-acme-web
spec:
parentRefs:
- name: tenant-apps-gateway
hostnames: ["acme.tenant-apps.example.com"]
rules:
- matches:
- path: { type: PathPrefix, value: / }
backendRefs:
- name: acme-web
port: 8080
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: acme-web-tls
spec:
secretName: acme-web-tls
dnsNames: ["acme.tenant-apps.example.com"]
issuerRef:
name: letsencrypt-prod
kind: ClusterIssuerTwo things disappear from this that were load-bearing in the ingress-nginx version, and both are improvements. First, nginx.ingress.kubernetes.io/rewrite-target and /ssl-redirect become structured HTTPRoute match/filter fields instead of opaque strings the controller has to parse and (historically) sometimes mis-parse into a config-injection bug. Second, TLS termination now belongs to the shared Gateway, not to each tenant's route — cert-manager's Gateway API integration issues the certificate against the Gateway listener, and it can even solve the ACME HTTP-01 challenge by creating a temporary HTTPRoute on the fly and deleting it once validation completes. A platform's control plane never has to hand-manage that dance per tenant.
This is also where ingress2gateway earns its "sanctioned" label rather than being just a convenience script: each of its 30+ supported ingress-nginx annotation translations is backed by controller-level integration tests that exercise real Gateway API controllers in live clusters and assert the converted resource behaves identically to the original annotation — not just that the YAML parses.
What a Git-Push PaaS's Routing Layer Should Own
Once the object model changes, the ownership boundary a platform's control plane should enforce changes with it — and this is the design decision that outlasts the migration itself:
- One
Gateway(or a small, fixed set) owned by the platform, not the tenant. Tenants shouldn't get their ownGateway; they get anHTTPRouteattached to a shared one. This is what makes routing a platform primitive instead of per-tenant snowflakes. - One
HTTPRoutegenerated per deploy, replacing the per-tenantIngressyour build pipeline used to template out. Custom domains, path rules, and header-based routing all become structuredHTTPRoutefields — inputs a platform's API can validate and diff, not opaque annotation strings it has to string-match. - TLS lives on the
Gatewaylistener, managed by cert-manager, not re-issued per tenant object. A platform's control plane requests a hostname; cert-manager and the sharedGatewayhandle issuance and renewal underneath it. - No controller-specific annotations in the platform's own schema. The entire reason ingress-nginx's annotation surface became a CVE generator is that "arbitrary controller config via string" has no validation boundary. A platform that owns structured
Gateway/HTTPRoutefields instead of re-exporting nginx annotations to its users has closed off that whole class of bug by construction.
This is precisely the shape bex's own routing layer targets: push a git repo, and the platform reconciles a per-tenant HTTPRoute against a shared, cert-manager-backed Gateway — never a hand-authored Ingress with vendor annotations baked into the platform's own config surface.
The Deadline Already Passed — the Decision Hasn't
Ingress-nginx's EOL isn't a future risk to plan for; it's a fact you're already operating under. The Traefik stopgap is the right call if you're carrying years of hand-written Ingress YAML and need the CVE exposure gone this week. But if you're a platform generating these resources from a template on every deploy, that template only needs to be right once — and Gateway API's Gateway/HTTPRoute split, with ingress2gateway doing the mechanical translation and cert-manager handling issuance, is where SIG Network, the annotation ecosystem, and the next decade of Kubernetes networking are all pointed at once.
Bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on machines you own, routed through Gateway API instead of a retired Ingress controller. Star the repo on GitHub or deploy your first app today.