Kubernetes networking
Kubernetes Ingress NGINX End of Life: Now What?
Kubernetes retired the ingress-nginx controller in March 2026. Clusters that still run it keep routing traffic, but nobody will patch the next vulnerability. This guide covers what changed, how to check your exposure, how to convert Ingress resources, and how the migration works on SeaGit clusters.
By Muhammad Soliman, Founder at SeaGit
·
Key takeaways
- Ingress NGINX is retired. Best-effort maintenance ended in March 2026, and the repository is archived and read-only.
- Existing clusters keep working after retirement. The risk is the next vulnerability, which no release will fix.
- Check with one command: kubectl get pods --all-namespaces --selector app.kubernetes.io/name=ingress-nginx.
- Kubernetes recommends Gateway API. None of the alternatives is a drop-in replacement, so plan engineering time.
- ingress2gateway 1.0 converts most common ingress-nginx annotations. Review its warnings, because regex matching and timeouts are translated with defaults you may need to change.
Is ingress-nginx really end of life?
Yes. The retirement was announced on 11 November 2025, and the Kubernetes Steering and Security Response Committees restated it on 29 January 2026. The project stopped releasing bugfixes and security patches after March 2026. Existing installations keep running, so nothing breaks on the retirement date. The change is that no one is committed to fixing the next vulnerability in the controller.
What was announced
The first notice came from SIG Network and the Security Response Committee in November 2025. It set the end of best-effort maintenance at March 2026 and said that afterwards there would be no releases, no bugfixes and no updates for newly found vulnerabilities. The notice also said existing deployments would keep working and that the Helm charts and container images would remain available.
The January 2026 statement from the Steering and Security Response Committees is blunter. It says ingress-nginx is critical infrastructure for about half of cloud native environments, citing internal Datadog research. It also says the project had only one or two maintainers, working in their free time, for several years. That is the reason the committees gave for winding it down rather than finding a new maintainer team. The statement puts the risk in plain terms: remaining on the controller after retirement "leaves you and your users vulnerable to attack."
The retirement applies to the ingress-nginx controller. It does not apply to the Ingress resource itself, and it does not apply to the NGINX Ingress Controller that F5 maintains under a different repository. If you are unsure which one you run, check the image and the Helm chart name on the running workload.
What end of life means in practice
The project README is the most precise statement, so here it is in summary. Before the cutoff, there was best-effort maintenance. After the cutoff there are no further releases of any kind, no bugfixes, and no security updates. The README also notes that the repository was archived by its owner on 24 March 2026 and is now read-only.
Keeps working
- Clusters that already run the controller keep routing traffic.
- Helm charts and container images stay available to pull.
- Ingress resources you already have keep being served by the controller.
Stops
- Any release, including a security release.
- Fixes for vulnerabilities found after the cutoff.
- Updates of any kind, which the statement says cannot be left to the last minute.
The practical effect is timing. A cluster does not change on the retirement date, so teams often do nothing until a scanner or an advisory names the controller. At that point the choice is between an emergency migration and an unpatched component. The Kubernetes statement says that existing deployments may not be noticed until they are compromised, which is why the first step is to find out what you run.
Check whether your cluster runs it
The Kubernetes statement gives this check: find ingress-nginx pods with a cluster administrator account. It is the fastest way to answer the question, and it needs no changes to the cluster.
kubectl get pods --all-namespaces --selector app.kubernetes.io/name=ingress-nginxAn empty result does not prove you are clear. Controllers installed with a different label, a Helm release renamed by your team, or a second controller in another namespace can all escape that selector. The next two commands show which IngressClass and which Ingress resources exist, so you can match each route to its controller.
# Which controller implements each IngressClass?
kubectl get ingressclass -o yaml
# Which Ingress resources exist, and in which namespaces?
kubectl get ingress --all-namespacesCount the Ingress resources that point at a class served by ingress-nginx. That number is the real scope of the migration, because each one needs a new route on the replacement controller.
Pick a migration path
There are two paths the Kubernetes project endorses. They differ in how much you change at once.
Gateway API, the recommended successor
Gateway API is the successor to the Ingress API. It separates what Ingress used to combine into one object. The Gateway defines where and how traffic enters the cluster, which is a cluster operator’s task. The HTTPRoute defines how traffic reaches Services, which is an application developer’s task. The Gateway API documentation describes that split as the reason it is safer for shared clusters.
The trade-off is that Gateway API is a different API. Annotations do not carry over. Some ingress-nginx behaviour has a native field in Gateway API, some needs an implementation-specific extension, and some has no equivalent. The Gateway API guide for ingress-nginx users points to that mapping, and the rest of this article shows how to work through it.
Another maintained Ingress controller
Kubernetes also lists other Ingress controllers in its documentation. Staying on the Ingress API means fewer resources change, so this is the smaller short-term move if the goal is to close the security gap quickly. The cost is that annotations are controller-specific. The Gateway API migration guide makes this point directly: every implementation has its own extensions, so moving controllers can mean rewriting one annotation dialect into another.
Convert with ingress2gateway
Ingress2Gateway is the Kubernetes SIG Network conversion tool. Its 1.0 release, published on 20 March 2026, supports more than 30 common ingress-nginx annotations, including CORS, backend TLS, regex matching and path rewrite. Before 1.0 it supported three. Each supported annotation is backed by integration tests that compare runtime behaviour on real controllers, not just the YAML.
Install it with Homebrew or Go, then print the converted manifests from files or from the cluster:
# With Homebrew
brew install ingress2gateway
# With a Go toolchain (pins the 1.0.0 release)
go install github.com/kubernetes-sigs/ingress2gateway@v1.0.0# From manifest files
ingress2gateway print --input-file my-manifest.yaml,my-other-manifest.yaml --providers=ingress-nginx > gwapi.yaml
# From one namespace in the live cluster
ingress2gateway print --namespace my-api --providers=ingress-nginx > gwapi.yaml
# From the whole cluster
ingress2gateway print --providers=ingress-nginx --all-namespaces > gwapi.yamlThe printed output also includes warnings. Treat each one as a to-do item. Four come up often enough to check for every migration.
- Untranslatable settings. The tool cannot translate every annotation. For example, the configuration-snippet annotation has no direct equivalent. The warning tells you to check the documentation of your Gateway API implementation for an equivalent behaviour.
- Regex matching. ingress-nginx regex paths are case-insensitive prefix matches. The translated output reflects that with a leading (?i) and a trailing .*. Most teams want exact, case-sensitive matching, so remove both unless you need the old behaviour.
- Timeouts. The proxy-send and proxy-read timeout annotations are translated to a request timeout as a best-effort default. In the 1.0 announcement, the example becomes 10 seconds. Set the value your service actually needs.
- URL normalisation. Gateway API does not configure RFC 3986 section 6 URL normalisation. Check whether your application depends on it.
The 1.0 release also has emitter flags for implementation-specific output. The release names envoy-gateway among them, so a conversion aimed at Envoy Gateway can include the extensions that the default output leaves out:
# Add implementation-specific output for Envoy Gateway
ingress2gateway print --providers=ingress-nginx --emitter envoy-gateway --input-file my-manifest.yaml > gwapi.yamlGateway API vs Ingress: the same app, both ways
The main difference between Ingress and Gateway API is the split of roles. An Ingress puts the entry point and the routing rules in one object, with controller-specific annotations for anything extra. Gateway API separates them: a Gateway describes the entry point and its listeners, and route objects such as HTTPRoute describe where requests go.
The Gateway API migration guide converts one Ingress with two hosts into resources in two steps. The first step is the Gateway. It names the GatewayClass that the installed implementation provides, creates an HTTP and an HTTPS listener, and points the HTTPS listener at the TLS secret the Ingress already used. The example is verbatim from the guide, with the hostnames unchanged:
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: example-gateway
spec:
gatewayClassName: prod
listeners:
- name: http
port: 80
protocol: HTTP
hostname: "*.example.com"
- name: https
port: 443
protocol: HTTPS
hostname: "*.example.com"
tls:
mode: Terminate
certificateRefs:
- kind: Secret
name: example-comThe second step is one HTTPRoute per hostname. Each route attaches to the HTTPS listener through parentRefs, and it carries the same path rules the Ingress had. The guide splits one Ingress into a route for foo.example.com and a route for bar.example.com. This is the shorter of the two routes:
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: foo
spec:
parentRefs:
- name: example-gateway
sectionName: https
hostnames:
- foo.example.com
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: foo-app
port: 80
- matches:
- path:
type: PathPrefix
value: /orders
backendRefs:
- name: foo-orders-app
port: 80Check the output against the behaviour you tested before you apply it. The two most common surprises are the case sensitivity of path matches and the default timeouts, both covered above.
Run both controllers and shift traffic
A safe cutover keeps both controllers running at the same time, so the old path stays available while you move traffic. The pattern is the same whether you use a managed platform or do it yourself: install the new controller, copy the routes, send a small share of traffic to the new path, compare error rates and latency, increase the share, switch DNS when the new path carries all traffic, and keep the old controller for a soak period before you remove it.
The most common failure at the traffic step is not a routing error. It is a behaviour that changed without anyone noticing, such as an auth check, a body size limit or a rate limit. Compare those behaviours on the new path before you move the next share of traffic, and keep the old path ready until the soak period ends.
The cost of waiting
Nothing in the cluster forces the change on the retirement date. That is the risk. The timing of the next vulnerability is not known in advance, so it could come soon or much later. Planning the migration before that happens turns it into a scheduled cutover rather than an incident response, and it lets the team test the new routes against real traffic first.
The migration also touches every Service behind the controller at once, which makes it a poor fit for a single weekend. Start with the cluster that has the fewest Ingress resources, and use that run to learn which annotations your teams depend on.
How SeaGit handles this
SeaGit clusters can run either controller. Here is what the product does today, with the limits stated.
- New clusters. The Ingress field in the create form offers NGINX Ingress Controller by default, and Gateway API (Envoy Gateway) on Kubernetes 1.33 and later. The Gateway option is available on AWS only.
- Existing clusters. An AWS cluster that runs NGINX can migrate in place. The migration runs as steps: prepare installs Envoy Gateway and copies your Ingress routes, set weight sends 10, 25, 50 or 100 percent of traffic to Envoy, cutover switches DNS, soak holds the old path for up to seven days, and finish stops the route sync.
- Deploys during the migration. While the migration is running, each application deploy or removal syncs that application’s Ingress to Envoy automatically, without changing its traffic weight. After finish, each application applies its own HTTPRoutes.
- Blockers. Some ingress-nginx annotations do not have an Envoy Gateway equivalent. The status response lists them as blockers, and cutover is refused until they are removed or replaced. The documented examples are auth-url, proxy-body-size, rate-limit and auth-type.
- Limits. The migration API works on AWS EKS clusters on SeaGit only. Azure and GCP are not supported. The cluster must be started for a step to run, and each step is a separate call. Your team still owns the blocker fixes and the cutover decision.
The step-by-step API is documented in the ingress migration guide. The example below shows the first two calls, with the API key header left out, which the guide describes:
# Start the migration (prepare installs Envoy Gateway and copies routes)
curl -X POST "https://seagit.com/api/org/$ORG_ID/clusters/$CLUSTER_ID/ingress-migration?accid=$ACCOUNT_ID" \
-H "Authorization: $API_KEY" \
-H "Content-Type: application/json" \
-d '{"action":"prepare"}'
# Send 10 percent of traffic to Envoy (weights: 0, 10, 25, 50, 100)
curl -X POST "https://seagit.com/api/org/$ORG_ID/clusters/$CLUSTER_ID/ingress-migration?accid=$ACCOUNT_ID" \
-H "Authorization: $API_KEY" \
-H "Content-Type: application/json" \
-d '{"action":"set_weight","weight":10}'If you run clusters somewhere else, or you use another provider, the sections above are the path. SeaGit is one option among several, and the checks on this page apply to any controller you choose. For the cluster model behind this, see bring your own cloud.