Life After ingress-nginx: A Field Guide to Gateway API
On this page
The deadline has come and gone. As of March, ingress-nginx, the controller that routes traffic into roughly half of all cloud native environments, receives no releases, no bug fixes, and no security patches. We covered why that matters in our security review write-up: it is retired software sitting on your network edge, and the exposure compounds quietly because nothing visibly breaks. What that post did not cover is the harder question you face once you accept the premise. How do you actually leave?
The uncomfortable subtext of the retirement announcement was that there is no official successor. InGate, the replacement project floated by the community, never reached a stable release and was wound down as part of the same announcement. There is no helm upgrade that takes you somewhere safe. Leaving ingress-nginx means choosing a new data plane and, in most cases, a new API for describing routes. That sounds like a rewrite, and rewrites get deferred. So here is the version of the migration that does not require a heroic weekend: two separate decisions, an inventory you can build in ten minutes, and a cutover you can do one hostname at a time.
First, untangle two decisions that look like one
The retirement of ingress-nginx does not deprecate a single line of your YAML. The Ingress API itself went GA back in Kubernetes 1.19, and it is not going anywhere: it is frozen, not removed. What died is the software behind it. That distinction splits your migration into two decisions with very different clocks.
The first decision is urgent: stop running unmaintained software on your edge. You could satisfy it by swapping in another maintained ingress controller that reads your existing Ingress objects, and for some teams that is a defensible move. The second decision is strategic: adopt Gateway API, which is where every new routing feature in the ecosystem now lands. Ingress is frozen precisely because the community moved its energy there.
The trap is making the first move without thinking about the second, because then you pay for a migration twice. If you are going to re-test every route in the cluster anyway, and you are, it is worth asking whether this is the moment to land on the API that the next decade of tooling is being built against. For most teams the answer is yes, and the rest of this post assumes it.
Why the annotation era had to end
It is worth being honest about what ingress-nginx actually was, because its greatest strength was also the thing that made it unmaintainable. The Ingress spec is minimal: hosts, paths, a backend, TLS. Everything else, and in production there is a lot of everything else, lived in annotations. Canary weights, rewrites, timeouts, buffer sizes, CORS, rate limits: over a hundred vendor-specific annotations, each one a string that no schema validates and no other controller understands.
At the far end of that spectrum sat the snippet annotations, which let anyone who could create an Ingress object inject raw nginx configuration into the shared proxy. That is not a routing feature. That is remote configuration of your edge by every team in the cluster, and it is the design flaw that IngressNightmare (CVE-2025-1974, CVSS 9.8) turned into an unauthenticated path to full cluster takeover in March 2025.
Gateway API is what you get when that lesson is absorbed into the design. The things people did with annotations are typed fields now: traffic splitting, header matching and rewrites, mirroring, timeouts are part of the spec, validated at admission, portable across implementations. And the API is split along the lines that real organisations are split. A GatewayClass belongs to whoever provides the infrastructure. A Gateway, the listener with its IP and certificates, belongs to the platform team. An HTTPRoute belongs to the application team, lives in the application’s namespace, and attaches to the shared gateway without being able to reconfigure it. Cross-namespace references only work where a ReferenceGrant explicitly allows them. The blast radius that snippets handed to every namespace is gone by construction.
A route with a canary split, in its entirety:
apiVersion: gateway.networking.k8s.io/v1kind: HTTPRoutemetadata: name: checkout namespace: shopspec: parentRefs: - name: edge namespace: infra hostnames: ["shop.example.com"] rules: - matches: - path: type: PathPrefix value: /checkout backendRefs: - name: checkout port: 8080 weight: 90 - name: checkout-canary port: 8080 weight: 10No annotations. Any conformant implementation runs it.
Choosing an implementation without a bake-off
Gateway API went GA in late 2023, and by now the implementation list is long enough to cause analysis paralysis: Envoy Gateway, Cilium, Istio, Traefik, HAProxy, Kong, NGINX Gateway Fabric, and more, each publishing conformance reports against the official test suite. You do not need a six-week evaluation. You need one question: which data plane are you already operating?
If your CNI is Cilium, it ships a conformant gateway and you add zero new components. If you run Istio, its gateway support is mature and you already own the operational surface. If you want to stay on an nginx data plane, NGINX Gateway Fabric is an actively maintained implementation built for Gateway API from the start; the name is confusingly close to ingress-nginx, but it is a different codebase with a different security posture, notably with no snippet-style escape hatch enabled by default. And if none of those apply, Envoy Gateway is the strong default: a CNCF project wrapping the proxy that already powers most of the service mesh world, built for exactly this job.
The honest summary is that conformance has done its job. The routing behaviour is specified, so the differentiator is operations, and the cheapest component to operate is the one your team already knows.
The migration, one hostname at a time
The big-bang version of this migration fails for a predictable reason: nobody can enumerate what their Ingress objects actually do. So the first step is to make the unknown enumerable, which takes one command:
kubectl get ingress --all-namespaces -o json \ | jq -r '.items[].metadata.annotations // {} | keys[]' \ | sort | uniq -c | sort -rnThat histogram is your migration plan. Every route falls into one of three buckets. Boring routes use host, path, and TLS, and nothing else: these convert mechanically. Annotation routes use rewrites, canaries, timeouts: these map to typed Gateway API fields, and the mapping is well documented. Snippet routes carry raw nginx config, and each one needs an engineer to ask what it was actually for; in our experience half of them are cargo-culted from an old Stack Overflow answer and can simply be dropped, which is its own small security win.
For the mechanical share, the community maintains ingress2gateway, which reads your live Ingress objects, understands the common ingress-nginx annotations, and emits Gateway API drafts:
ingress2gateway print --providers=ingress-nginx > gateway-draft.yamlTreat the output as a draft, not a deployment. Review it the way you would review a colleague’s PR, because a converter cannot know which behaviours were intentional and which were accidents that traffic now depends on.
Then the cutover, and this is the part that saves your weekend: the two stacks run side by side. The new gateway gets its own LoadBalancer and its own IP. Traffic moves per hostname, with a DNS change, starting with the least frightening domain you own. The old controller keeps serving everything you have not moved. When a hostname misbehaves on the new stack, moving it back is another DNS change, not a rollback. Most teams find that after the first ten routes the remaining ninety are repetition.
The one rule with no exceptions: the day the old controller serves zero traffic, delete it. A retired proxy idling on your edge with a cluster-facing service account is not a backup plan, it is attack surface with nostalgia value.
Where a platform earns its keep
Everything above is doable by hand. What a platform changes is who has to remember it. In Ankra, the ingress controller is not a snowflake deployment someone applied in 2023; it is a pinned-version node in a stack, which is why the AI’s proactive insights could flag retired and end-of-life components in the first place. The migration itself is a stack change: add the new gateway addon, and it rolls out with dependency ordering and a Git record, through drafts and approvals rather than a sequence of hand-run applies that live only in someone’s shell history. And when the last hostname moves, removing ingress-nginx is a reviewed change too, so the “delete the old one” rule actually happens instead of surviving as a TODO.
The ingress-nginx retirement is the rare industry event with a clean moral. Software that everyone uses and nobody maintains does not fail loudly; it retires quietly and leaves half the internet doing a migration on a deadline. The teams that come out of this well are not the ones with the cleverest proxy configuration. They are the ones for whom “what is running on our edge, and who maintains it” is a question with a current answer. If your answer still contains ingress-nginx, the histogram command above is a good place to spend your next ten minutes. If you are on the upgrade treadmill more broadly, this migration is the same discipline applied to one very exposed component.
Get started: Create a free account on Ankra.
Join our community: Slack
Follow us on: LinkedIn | GitHub
Contact us: [email protected]
Get the next post in your inbox
Related Posts
Your Scanner Found 4,000 Findings. It Made Zero Decisions.
Three weeks ago we called CVE scanners a layer-seven answer for teams who skipped the layer-one work. Then we shipped one. Here is why that is not a contradiction, and what a vulnerability finding has to become before it is worth anything.
The Kubernetes Security Work That Actually Matters in 2026
Half of all clusters are running an ingress controller that stopped receiving security patches in March. Meanwhile, teams debate service meshes. The five findings that show up in every real cluster security review, each with the incident that proves it and the fix.