Ingress NGINX retirement: revisit your Kubernetes ingress before you migrate
A platform team plans its Kubernetes 1.37 upgrade for the next sprint. The cluster checklist is green, except one line nobody owns: the ingress-nginx Helm chart, pinned at 4.15.1, in front of forty services. A security reviewer asks a simple question: “Who patches this proxy now?” Since March 2026 the honest answer is “nobody”. The controller still routes traffic, so nothing feels urgent. That is exactly why it needs a plan.
This post covers what the retirement means, where Gateway API stands, how ingress-nginx annotations map (and where they do not), how to choose a replacement, and a migration plan. Facts come from the kubernetes.io blog and docs, the archived kubernetes/ingress-nginx repository, Gateway API v1.6.2 release files and conformance reports, ingress2gateway v1.2.0 docs and source, and the projects’ own docs, checked on 6 October 2026. Drill results are from my own run.
The short version
- Community Ingress NGINX (
kubernetes/ingress-nginx) is retired. Its last releases came out in March 2026 and the repository was archived on 24 March 2026. Existing installs keep working; there are no more fixes, including security fixes. - The Ingress API is not going away. It is frozen, and Kubernetes recommends Gateway API instead.
- F5’s NGINX Ingress Controller and NGINX Gateway Fabric are different, maintained projects. Do not confuse them with the retired one.
- Gateway API v1.6.2 is the current release. Gateway, HTTPRoute, GRPCRoute, TLSRoute, TCPRoute, UDPRoute, ListenerSet, BackendTLSPolicy and ReferenceGrant are in the Standard channel. Retries, session persistence and external auth are still experimental.
- Many annotations have no Standard equivalent: rate limits, auth, snippets, cookie affinity, body size, IP allow lists. You will need your chosen implementation’s own policies for these.
- ingress2gateway 1.x is a good first draft, not a final answer. In my drill it reported clearly what it skipped, but the output still needed human review.
Where things stand on 6 October 2026
Date
Event
24 March 2025
Critical ingress-nginx CVEs, including CVE-2025-1974, patched in v1.12.1 and v1.11.5
11 November 2025
SIG Network and the Security Response Committee announce the retirement for March 2026
29 January 2026
Joint statement from the Steering and Security Response Committees
27 February 2026
Gateway API v1.5.0 released
19 March 2026
Last tags: controller-v1.15.1, v1.14.5 and v1.13.9
20 March 2026
ingress2gateway 1.0 announced
24 March 2026
kubernetes/ingress-nginx archived, now read-only
30 June 2026
Gateway API v1.6.0 released (v1.6.2 on 3 September 2026)
7 July 2026
ingress2gateway v1.2.0 tagged
26 August 2026
Kubernetes v1.37 released
The final README lists v1.15.1 as tested on Kubernetes 1.31 to 1.35. Kubernetes is now at 1.37, so every upgrade from here moves you further from what was tested.
What retirement means, and what it does not
The November 2025 announcement is clear: after March 2026, “no further releases, no bugfixes, and no updates to resolve any security vulnerabilities”. Existing deployments are not broken, and images and Helm charts stay available. The January 2026 statement adds a line worth printing for your planning meeting: “None of the available alternatives are direct drop-in replacements.” It also cites internal Datadog research that about half of cloud native environments rely on this controller.
The final mailing list note from the maintainers says the Kubernetes Security Response Committee will still issue CVEs for ingress-nginx code, but will not ship patch releases for issues reported after end of life. So you may get a CVE number with no fix.
The announcement gives two reasons: too few maintainers for years, and flexibility that became risk, such as snippet annotations that inject raw NGINX configuration. The replacement project, InGate, never matured and is also being retired.
Three things did not change:
- The Ingress API itself. The Kubernetes docs say it is generally available, frozen, and there are no plans to remove it. In my drill, a v1.37 API server accepted Ingress objects as usual.
- Other Ingress controllers. Many are still maintained, so staying on Ingress with a different controller is a valid choice.
- F5’s products. The F5 NGINX Ingress Controller (
nginx/kubernetes-ingress, annotations undernginx.org/) had release v5.6.3 in September 2026. A February 2026 kubernetes.io post spells it out: both use NGINX as the data plane “but are otherwise unrelated”.
First, find out what you actually run
The announcement suggests this check, with cluster admin rights:
kubectl get pods --all-namespaces \
--selector app.kubernetes.io/name=ingress-nginx
# Which ingress-nginx annotations do your Ingresses use, and how often?
kubectl get ingress -A -o json \
| jq -r '.items[].metadata.annotations // {} | keys[]' \
| grep '^nginx.ingress.kubernetes.io/' | sort | uniq -c | sort -rn
I tested the jq pipeline on my sample manifests. Annotations are only part of the picture. Also export the controller ConfigMap and the controller arguments. Global settings such as forwarded header handling, the default certificate, or --enable-ssl-passthrough change behaviour for every Ingress. ingress2gateway reads Ingress objects and provider CRDs, not your controller flags. The final annotations page mentions about 130 distinct nginx.ingress.kubernetes.io/ keys, so your own inventory, not the full list, decides how hard the migration will be.
Gateway API in one page
Ingress puts the entry point and the routing in one object, and the controller decides the rest through annotations. Gateway API splits these into roles:
- GatewayClass: which implementation runs the proxy. Usually installed by the platform team or the provider.
- Gateway: listeners (ports, protocols, hostnames, certificates) and which namespaces may attach routes. By default
allowedRoutesallows only the Gateway’s own namespace. - HTTPRoute and friends: matches, filters and backends, owned by the application team.
Every feature also has a support level. The conformance guide describes Core as portable with a roadmap for all implementations, Extended as portable but not universally supported (same behaviour where supported), and Implementation-specific as not portable.
What is Standard in v1.6.2
From the CRDs in the v1.6.2 standard-install.yaml:
Resource
Standard channel
Served version
GatewayClass, Gateway, HTTPRoute
Yes
v1 (v1beta1 still served)
GRPCRoute
Yes
v1
TLSRoute, ListenerSet
Yes, since v1.5
v1
TCPRoute, UDPRoute
Yes, since v1.6
v1 (v1alpha2 deprecated)
BackendTLSPolicy
Yes
v1
ReferenceGrant
Yes
v1 and v1beta1
XBackend, XBackendTrafficPolicy, XMesh
No, experimental only
v1alpha1, new gateway.networking.x-k8s.io group
Inside HTTPRoute, the Standard channel has path, header, query and method matches; header modifiers, redirects, URL rewrites, mirroring and the CORS filter; weighted backends; and timeouts. The retry and sessionPersistence fields and the ExternalAuth filter exist only in the experimental CRDs.
Two practical points from the release notes. First, since v1.5 a ValidatingAdmissionPolicy, safe-upgrades, blocks installing experimental CRDs over standard ones and blocks downgrades. Second, CRDs are cluster wide, and the CRD guide says a cluster admin or provider should own them. Envoy Gateway’s compatibility matrix, for example, lists v1.9 as built with Gateway API v1.6.1. Decide who owns the CRD version before two teams install two charts.
How ingress-nginx annotations map
The third column shows what ingress2gateway v1.2.0 does with the default standard emitter, from its provider README and my run.
ingress-nginx annotation
Gateway API equivalent
ingress2gateway v1.2.0
ssl-redirect (308 by default)
RequestRedirect filter on the HTTP listener
Converted, 308
rewrite-target with a fixed path
URLRewrite filter
Converted
rewrite-target with $1, $2
None in Standard
Not converted, warning
use-regex
RegularExpression path match (implementation specific)
Converted to case-insensitive regex
canary, canary-weight
Weighted backendRefs
Converted
canary-by-header, canary-by-header-value
Header match
Converted
canary-by-cookie
None
Warning only
proxy-connect/send/read-timeout
timeouts.request, timeouts.backendRequest (different meaning)
Best effort, see the drill
enable-cors, cors-*
CORS filter (Standard since v1.5)
Converted
backend-protocol: GRPC or HTTPS
GRPCRoute, BackendTLSPolicy
Converted
ssl-passthrough
TLSRoute in Passthrough mode
Converted
proxy-body-size
None
Dropped with a warning; vendor policy with some emitters
whitelist-source-range
None
Dropped with a warning; vendor policy with some emitters
affinity: cookie
sessionPersistence (experimental)
Not supported
limit-rps, limit-rpm
None in either channel
Not supported
auth-url
ExternalAuth filter (experimental)
Not supported
configuration-snippet
None, by design
Not supported
Two behaviours deserve attention because they are invisible in a single YAML file. The ingress-nginx docs say that if use-regex or rewrite-target is set on any Ingress for a host, case-insensitive regex matching applies to all paths of that host, whatever Ingress they live in. And nginx’s proxy_read_timeout applies “between two successive read operations”, while Gateway API’s timeouts.request covers close to the whole request and response. A kubernetes.io post from February 2026, “Before You Migrate”, walks through these quirks with examples. Read it before your first conversion.
Choosing where to go
There are three sensible paths, and one risky one:
- Gateway API with a conformant implementation. This is what Kubernetes recommends, and it gives you the role split.
- Another Ingress controller. Fewer YAML changes now, but you stay on a frozen API. Traefik documents an Ingress NGINX provider that reads many ingress-nginx annotations, with listed differences. F5 publishes a migration guide to its NGINX Ingress Controller. Both are the vendors’ own descriptions.
- Your cloud’s load balancer controller, if you are on one provider and accept its limits.
- Stay on the retired controller. Only as a short, written exception with a deadline, extra controls in front, and nobody allowed to create Ingress objects without review.
For Gateway API, here are the main open source options. Versions are the latest GitHub releases on 6 October 2026. “Report” means a conformance report submitted for Gateway API v1.6.0 in the gateway-api repository.
Project
Data plane
Latest release
v1.6.0 report, HTTP core
Worth knowing
Envoy Gateway
Envoy
v1.9.2
v1.9.0, passed
Own policies for rate limits, auth, IP rules; v1.9 end of life 14 Feb 2027 per its matrix
Istio
Envoy
1.31.1
1.31, passed
Says it intends Gateway API to become its default traffic API
Cilium
Envoy, in the CNI
v1.20.2
v1.20.0, passed
Needs kubeProxyReplacement=true; it is a CNI decision too
NGINX Gateway Fabric (F5)
NGINX
v2.7.2
v2.7.0, passed
Creates an NGINX data plane Deployment per Gateway
Traefik Proxy
Traefik
v3.7.14
v3.7.10, partial (one test skipped)
Also has the Ingress NGINX compatibility provider
Kong Operator
Kong Gateway
v2.3.2 (also a v2.4.0-rapid.2.0 tag)
v2.3.2, passed
Kong says new Kubernetes work lands here, not in KIC
HAProxy Unified Gateway
HAProxy
v1.0.7
No v1.6.0 report
Its docs list compliance with Gateway API 1.5.0 experimental, 1.4.0, 1.3.0
Core results look the same, so compare extended features you actually use. From the same reports (HTTP profile):
Extended feature claimed
Envoy Gateway 1.9.0
Istio 1.31
Cilium 1.20.0
NGF 2.7.0
Traefik 3.7.10
Path rewrite
Yes
Yes
Yes
Yes
Yes
Request timeout
Yes
Yes
Yes
No
No
CORS filter
Yes
Yes
Yes
Yes
No
Request mirror
Yes
Yes
Yes
Yes
No
ListenerSet
Yes
Yes
Yes
Yes
No
“No” means not claimed in that report, not impossible. Vendor CRDs may cover it. Kong’s report depends on its router mode, so I left it out of this table.
Before and after
A small example: one Ingress plus a canary Ingress, and a hand-written Gateway API version that separates platform and application ownership. I applied the “after” manifests to a v1.37 API server with the v1.6.2 Standard CRDs. No controller was running, so this only proves they are valid.
# Before: ingress-nginx
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: orders
namespace: orders
annotations:
nginx.ingress.kubernetes.io/proxy-read-timeout: "60"
spec:
ingressClassName: nginx
tls:
- hosts: [orders.example.in]
secretName: orders-tls
rules:
- host: orders.example.in
http:
paths:
- path: /api
pathType: Prefix
backend:
service: {name: orders-api, port: {number: 8080}}
# plus an "orders-canary" Ingress for orders-api-v2 with
# canary: "true" and canary-weight: "10"
---
# After: Gateway owned by the platform team (namespace infra)
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: public
namespace: infra
spec:
gatewayClassName: my-gateway-class # from your implementation
listeners:
- name: http
protocol: HTTP
port: 80
allowedRoutes:
namespaces: {from: Same} # only the redirect route below
- name: orders-https
protocol: HTTPS
port: 443
hostname: orders.example.in
tls:
mode: Terminate
certificateRefs:
- name: orders-tls # Secret now lives in infra
allowedRoutes:
namespaces:
from: Selector
selector:
matchLabels: {gateway-access: public}
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: https-redirect
namespace: infra
spec:
parentRefs:
- {name: public, sectionName: http}
rules:
- filters:
- type: RequestRedirect
requestRedirect: {scheme: https, statusCode: 308}
---
# After: route owned by the application team (namespace orders)
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: orders
namespace: orders
spec:
parentRefs:
- {name: public, namespace: infra, sectionName: orders-https}
hostnames: [orders.example.in]
rules:
- matches:
- path: {type: PathPrefix, value: /api}
timeouts:
request: 60s # a decision, not a copy of proxy-read-timeout
backendRefs:
- {name: orders-api, port: 8080, weight: 90}
- {name: orders-api-v2, port: 8080, weight: 10}
The canary becomes two weighted backends in one rule. The certificate moves to the Gateway’s namespace (or stays put with a ReferenceGrant). The orders namespace needs the gateway-access: public label.
Drill: ingress2gateway on sample manifests
I ran this on 6 October 2026 on a shared VM container: 8 vCPUs, 15 GB RAM, Debian 13, Linux 6.12. Tools: the ingress2gateway v1.2.0 Linux release binary (checksum verified), and a local kube-apiserver v1.37.0 with etcd from the controller-runtime envtest assets, with the Gateway API v1.6.2 Standard CRDs installed. No Gateway controller ran and no traffic was sent, so these results are about translation and API validation only.
Input: three Ingresses on one host. The main one used 9 annotations (body size, read timeout, CORS, IP allow list, limit-rps, auth-url, cookie affinity, a configuration snippet); a canary Ingress used weight 10; a third used use-regex with rewrite-target: /$2.
Check
What I saw
Standard emitter
Exit code 0. One Gateway (HTTP and HTTPS listeners), two HTTPRoutes, 14 warnings and 1 info note
Unsupported annotations
auth-url, limit-rps and configuration-snippet named as unsupported; cookie affinity “not supported”
use-regex on one Ingress
Paths from all three Ingresses became case-insensitive regex, for example (?i)/api.*
rewrite-target: /$2
Not converted (“capture group references are not supported”). The route kept the regex match with no rewrite, so the backend would see the original path
proxy-read-timeout: "60"
timeouts.request: 10m0s. The source takes the largest of the three timeouts and multiplies by 10
Canary weight 10
Backends weighted 10 and 90
envoy-gateway emitter
Added two BackendTrafficPolicies (request buffer 8Mi) and two SecurityPolicies (allow 10.0.0.0/8, default deny)
Host with 20 paths, no TLS
One HTTPRoute with 20 rules. The API server rejected it: “spec.rules: Too many: 20: must have at most 16 items”. Also a warning that ingress-nginx served HTTPS for this host with a self-signed certificate, which is not translated
Experimental CRDs over Standard
Denied by the safe-upgrades policy
HTTPRoute with retry on Standard CRDs
Rejected as an unknown field. With --validate=false, the field was silently dropped
Reading from the API server instead of files
Same objects; only the order of two backends differed
The lesson is not that the tool is weak. Its warnings were specific and honest. The lesson is that a clean exit code does not mean the same behaviour. Apply the output with server-side validation, read every warning, and test traffic.
Migration checklist
- Inventory. Controllers, IngressClasses, Ingress count per namespace, annotation counts, ConfigMap, controller flags, and anything that depends on the controller’s IP or
X-Forwarded-*headers. - Classify each annotation: Standard equivalent, implementation policy, or behaviour you will drop on purpose. Snippets and auth usually need the most work.
- Choose the target using the extended features and policies you need, and your team’s comfort with Envoy, NGINX, HAProxy or Traefik.
- Decide CRD ownership. Install Standard channel CRDs once, as the cluster owner, and pin the version.
- Run side by side. The Gateway API guide for Ingress-NGINX users recommends this: the new controller gets its own external IP and production stays untouched.
- Convert per namespace with ingress2gateway, the emitter for your implementation, and
--input-fileor-n. Commit the output and the warnings for review. - Fix by hand. Split routes over 16 rules, rewrite regex and capture-group paths, set timeouts deliberately, move certificates or add ReferenceGrants.
- Test behaviour. Use
curl --resolveagainst the new IP for real paths, redirects, trailing slashes, mixed-case paths, large uploads, CORS preflight and gRPC. Compare status codes and headers with the old controller. - Shift traffic gradually. Lower DNS TTLs a day early, move one host at a time, and watch errors and latency.
- Clean up. Remove ingress-nginx, its admission webhook, IngressClass and RBAC, and the Ingress objects it served, so nothing falls back to it later.
Common mistakes
- Mixing up the projects. F5’s NGINX Ingress Controller and NGINX Gateway Fabric are maintained. Community ingress-nginx is retired.
- Treating a clean conversion as done. Warnings and limits like the 16 rule cap only show up when you read and apply the output.
- Copying timeouts one to one. NGINX read timeouts are per read. Gateway API request timeouts cover the whole request.
- Forgetting host-wide regex. One
use-regexIngress changed matching for its neighbours. - Installing experimental CRDs for one field without accepting that the channel has no compatibility promise.
- Letting each team install its own Gateway API CRDs through different Helm charts.
- Leaving the old controller running “just in case” with its cluster-wide access to Secrets.
What to unlearn and re-learn
- Unlearn “retired means it stops working”. Re-learn that it keeps working without fixes, which is riskier because it is quiet.
- Unlearn “annotations are configuration”. Re-learn them as controller behaviour that must be mapped, replaced or dropped on purpose.
- Unlearn “one Ingress object per app is the whole story”. Re-learn routing as a split between platform owned Gateways and team owned routes.
- Unlearn “the converter migrates for me”. Re-learn it as a draft generator that saves typing and lists the gaps.
Revisit your ingress before you migrate
Ingress NGINX served many of us well, and its retirement is not a reason to panic. It is a reason to look closely at what our edge actually does. Learn the Gateway API resources and channels, unlearn the habit of solving everything with an annotation, re-learn ownership between platform and application teams, practise the conversion on a copy of your manifests, and apply the move one host at a time. An inventory turns a vague worry into a list of concrete items, and the edge you end up with is easier to reason about.
Sources
- Kubernetes blog: Ingress NGINX Retirement: What You Need to Know, 11 November 2025
- Kubernetes blog: Ingress NGINX: Statement from the Kubernetes Steering and Security Response Committees, 29 January 2026
- Kubernetes blog: Before You Migrate: Five Surprising Ingress-NGINX Behaviors You Need to Know, 27 February 2026
- Kubernetes blog: Ingress-nginx CVE-2025-1974: What You Need to Know, 24 March 2025
- Kubernetes blog: Announcing Ingress2Gateway 1.0, 20 March 2026
- Kubernetes blog: Gateway API v1.5: Moving features to Stable, 21 April 2026
- Kubernetes blog: Gateway API v1.6: TCPRoute and UDPRoute Graduate to Standard, 3 August 2026
- Kubernetes blog: Kubernetes v1.37 release, 26 August 2026
- Kubernetes docs: Ingress and Ingress Controllers
- kubernetes/ingress-nginx repository (archived) and README
- ingress-nginx releases, including controller-v1.15.1
- Kubernetes dev mailing list: Ingress-nginx final releases, 13 March 2026
- ingress-nginx docs: annotations, path matching, ConfigMap
- nginx docs: proxy_read_timeout
- Gateway API: Welcome guide for Ingress-NGINX users and Migrating from Ingress
- Gateway API: Versioning and CRD Management
- Gateway API v1.6.2 release, v1.6 changelog, v1.5 changelog
- Gateway API v1.6.0 conformance reports
- ingress2gateway README, ingress-nginx provider README, v1.2.0 release, timeout handling in source
- Envoy Gateway compatibility matrix and releases
- Istio docs: Gateway API
- Cilium docs: Gateway API support
- NGINX Gateway Fabric docs and Gateway API compatibility
- F5 docs: Migrate from Ingress-NGINX Controller to NGINX Ingress Controller and nginx/kubernetes-ingress releases
- Traefik docs: Kubernetes Ingress NGINX provider (v3.7) and annotation support
- Kong Ingress Controller README (KIC and Kong Operator) and Kong Operator releases
- HAProxy Unified Gateway release notes and releases
- controller-runtime envtest
Releted Posts
MinIO and its alternatives: revisit your self-hosted S3 in 2026
For many teams, MinIO was the default answer to one simple need: “give us S3 on our own machines”. It sat behind CI pipelines, backup jobs, data lake experiments and almost every docker-compose file that needed a bucket.
Read moreElasticsearch and OpenSearch: revisit the fork before you pick one
Many teams still talk about “our Elastic cluster” when the cluster is actually Amazon OpenSearch Service, or about “OpenSearch” when half the code paths still use an old Elasticsearch client.
Read moreKafka and Redpanda: revisit the choice before you treat them as the same bus
Many teams say “we run Kafka” when they mean “our services talk to a Kafka-compatible broker”. That shortcut was useful when one open source broker dominated the protocol.
Read more