Skip to main content
Back to Blog
DevOps

Ingress-NGINX Is Retired: How to Migrate to Gateway API Safely

Ingress-NGINX stopped getting patches in March 2026. How to find it in your clusters, translate it with ingress2gateway, and cut over to Gateway API.

PlatOps Team
Author
Published: October 22, 2026
8 min read

If your Kubernetes cluster serves web traffic, there is a good chance a single pod called ingress-nginx-controller sits between the internet and every app you run. That controller was retired in March 2026. The GitHub repository was archived on March 24, 2026, and the project's own README now says plainly that if you are not already running it, you should not deploy it.

Nothing broke on retirement day, which is the problem. The Kubernetes Steering and Security Response Committees put it this way: "Existing deployments will continue to work, so unless you proactively check, you may not know you are affected until you are compromised." The same statement cites Datadog research that about 50% of cloud native environments relied on the controller.

Seven months later, two things make this week a good time to finish the job. Kubernetes 1.37 shipped in August, and the final ingress-nginx release lists end-to-end testing only up to Kubernetes 1.35. And if you use the AKS application routing add-on, Microsoft's critical security patches for its NGINX resources end in November 2026.

TL;DR

  1. Ingress-NGINX gets no releases, bug fixes, or security patches after March 2026; existing installs keep running, unpatched.
  2. Check every cluster with one kubectl command, and check managed add-ons too: the AKS application routing add-on runs ingress-nginx under a different class name.
  3. Gateway API is the upstream replacement; the Ingress API itself is frozen, not removed, so another Ingress controller is also a valid short-term path.
  4. ingress2gateway 1.0 translates over 30 common ingress-nginx annotations, but its warnings are the real deliverable: snippets, body size limits, and URL normalization need human decisions.
  5. Ingress-NGINX regex paths are case-insensitive prefix matches and it adds trailing-slash redirects silently; a literal translation can turn working URLs into 404s.
  6. Run old and new data planes side by side, test against the new IP with curl --resolve, then move traffic with a low-TTL DNS change you can reverse.

What "retired" means in practice

The retirement announcement is precise: after March 2026 "there will be no further releases, no bugfixes, and no updates to resolve any security vulnerabilities that may be discovered." Helm charts and container images stay available, so helm upgrade still works and nothing alerts you.

Two clarifications save a lot of confusion in planning meetings:

  • Ingress-NGINX is not NGINX Ingress. The retired project is the community controller at kubernetes/ingress-nginx. F5's NGINX Ingress Controller is a separate product that also uses NGINX as its data plane. Check which one you run before you plan anything.
  • The Ingress API is not going away. The Ingress documentation states the API "has been frozen": no new features, but your Ingress objects remain valid. Upstream recommends Gateway API, and if you need to stay on Ingress for now, the list of other Ingress controllers is the alternative it points to.

Step 1: Find every copy

The committees' check, run with cluster-admin rights against each cluster:

kubectl get pods --all-namespaces \
  --selector app.kubernetes.io/name=ingress-nginx

The selector depends on labels the official Helm chart sets, so a hand-rolled install can slip through. Two more checks catch most of those:

# IngressClasses served by the community controller
kubectl get ingressclass \
  -o custom-columns=NAME:.metadata.name,CONTROLLER:.spec.controller

# Any container running the community controller image
kubectl get pods -A \
  -o jsonpath='{range .items[*]}{.metadata.namespace}{"\t"}{.metadata.name}{"\t"}{.spec.containers[*].image}{"\n"}{end}' \
  | grep 'ingress-nginx/controller'

An IngressClass whose controller is k8s.io/ingress-nginx belongs to the retired project. On AKS, also run kubectl get nginxingresscontrollers: the application routing add-on can run several controllers, each with its own class (the default is webapprouting.kubernetes.azure.com) and its own load balancer IP.

Then list what depends on it: kubectl get ingress -A gives every hostname and the IP it resolves to. Write down anything upstream that pins that IP, such as a CDN origin, a WAF, a partner's firewall allow list, or a health check in your uptime monitor. Those are the things that break during cutover, not the YAML.

Step 2: Pick where you are going

Your situationSensible target
AKS with the application routing add-onMicrosoft's application routing Gateway API implementation (approuting-istio), or Application Gateway for Containers
Self-managed ingress-nginx on EKS, GKE, AKS, or bare metalA conformant Gateway API implementation, such as Envoy Gateway, Istio, or kgateway
Heavy use of configuration-snippet or Lua, no time for a redesignAnother Ingress controller now, Gateway API later, with a dated plan
Already running a service meshThat mesh's Gateway API support

For a small team, the deciding factor is usually who patches the controller. A cloud-managed implementation moves that job to your provider. A self-installed one keeps it with you, which is the same situation that got you here, so pick a project with an active release cadence and put its upgrades on the same calendar as your cluster upgrades.

Step 3: Translate with ingress2gateway, then read the warnings

Ingress2Gateway 1.0, released by SIG Network on March 20, 2026, reads your Ingress objects and their ingress-nginx annotations and writes Gateway API manifests. Support went from three annotations to over 30, including CORS, backend TLS, regex matching, and path rewrites, each backed by tests that compare live controller behavior.

go install github.com/kubernetes-sigs/ingress2gateway@v1.0.0
# or: brew install ingress2gateway

ingress2gateway print --providers=ingress-nginx --all-namespaces > gwapi.yaml

Add --emitter envoy-gateway (or kgateway, agentgateway) to get implementation-specific extensions for settings that standard Gateway API cannot express. On AKS, Microsoft notes the tool reads only the nginx class by default; pass --ingress-nginx-ingress-class=<class> once per add-on class.

The output file is the easy half. The warnings it prints tell you where behavior will change:

  • configuration-snippet is not translated. Raw NGINX config has no Gateway API equivalent. Every snippet needs a decision: move it into the application, use an implementation extension, or delete it. Where snippets are enabled, they are also a security concern in their own right: anyone who can edit an Ingress can inject proxy configuration.
  • proxy-body-size is not translated. Upload endpoints that worked at 1 GB may start returning 413 errors against your new proxy's default limit.
  • Timeouts are approximated. Ingress-NGINX timeouts are TCP-level; the tool maps them to a Gateway API request timeout, which is a different thing. Set it per route from what the service needs.
  • URL normalization varies by implementation and standard Gateway API cannot configure it.

Step 4: Know the five behaviors that cause outages

SIG Network published a post titled Five Surprising Ingress-NGINX Behaviors, and every one of them turns a "correct" translation into a 404. The short version:

  1. Regex paths are prefix-based and case-insensitive. /[A-Z]{3} under ingress-nginx also matches /uuid/anything. Envoy-based implementations do a full, case-sensitive match.
  2. use-regex: "true" leaks across Ingresses. It turns every path on that host into a regex, including paths in other Ingress objects marked Exact.
  3. rewrite-target implies regex, with the same host-wide side effect.
  4. Missing trailing slashes get a 301. A Prefix path of /my-path/ redirects /my-path. Conformant Gateway API implementations never add redirects silently.
  5. URLs are normalized before matching, so ////uuid and /ip/abc/../../uuid reach the /uuid backend.

Ingress2gateway preserves the first one by writing (?i)/users/(\d+).*. That keeps current behavior, but it also keeps routing traffic your developers never meant to expose. Decide route by route. If clients depend on the trailing-slash redirect, add it explicitly:

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: trailing-slash-redirect
spec:
  parentRefs:
  - name: public-gateway
  hostnames: ["app.example.com"]
  rules:
  - matches:
    - path:
        type: Exact
        value: /my-path
    filters:
    - type: RequestRedirect
      requestRedirect:
        path:
          type: ReplaceFullPath
          replaceFullPath: /my-path/
        statusCode: 301

The fastest way to find which behaviors you rely on is your access logs: export a week of request paths from the ingress-nginx controller and replay a sample against the new gateway in staging.

Step 5: Cut over with DNS, keep a way back

Both upstream and Microsoft describe the same sequence: run both data planes at once, prove the new one, then move clients.

  1. Lower DNS TTLs to 60 seconds for each affected hostname, then wait out the old TTL before anything else.

  2. Deploy the Gateway and HTTPRoutes alongside the existing Ingress. The new gateway gets its own load balancer IP; the old path keeps serving users.

  3. Test the new IP directly, every hostname and path, over HTTPS with the production certificate:

    curl -sS -o /dev/null -w '%{http_code}\n' \
      --resolve "app.example.com:443:${GW_IP}" \
      https://app.example.com/api/health
    

    Exercise POST bodies, authentication, WebSockets, gRPC, and large uploads, not only GET /.

  4. Point DNS at the gateway and update anything upstream that pinned the old IP. On AKS, Microsoft warns you cannot move the old IP over: managed public IPs are deleted with their Service even when marked Static.

  5. Watch the old controller's request rate fall to zero, keep TTLs low for a few hours, then delete the Ingress objects and uninstall ingress-nginx.

Until step 5, rollback is one DNS change. That is the property worth protecting, so keep the Ingress objects in place until you are sure.

Where this fits in your platform work

Treat this as a cluster lifecycle task, not a one-off ticket. Whatever replaces ingress-nginx will have its own CVEs and release notes, and someone needs to own them on the same schedule as node and control-plane upgrades. Ingress traffic also belongs in your supply chain and dependency inventory: an internet-facing proxy that stopped receiving patches is exactly what that inventory exists to catch.

If you would rather hand over the inventory and the plan, our Cloud & DevOps Audit Sprint covers retired components like this one alongside cost and reliability findings, and our Kubernetes service runs migrations like this end to end. For the rest of the cluster, see our guides to Kubernetes cost optimization and securing AI agents on Kubernetes.

Frequently asked questions

Will ingress-nginx stop working now that it is retired? No. Existing deployments keep running and the images and Helm charts remain available. What stopped is maintenance: no releases, bug fixes, or security patches since March 2026. The risk grows over time, as new vulnerabilities go unpatched and new Kubernetes versions go untested; the final release lists testing only up to Kubernetes 1.35.

Do I have to move to Gateway API, or can I switch to another Ingress controller? Both work. The Ingress API is frozen, not deprecated for removal, so another maintained Ingress controller is a valid way to get off an unpatched component quickly. Gateway API is where upstream development happens, and a move to it now avoids a second migration later. Teams with heavy NGINX snippet use often switch controllers first and redesign afterwards.

How long does a migration take for a small team? For a single cluster with a dozen or so Ingress objects and few custom annotations, the translation is a day or two. Most of the time goes into testing the behaviors above, finding upstream systems pinned to the old IP, and waiting out DNS TTLs. Plan for a couple of weeks of calendar time per cluster, longer if you rely on configuration snippets.

Put this into practice

Book a free 1-hour call with a senior engineer about your infrastructure, or check your email security in 30 seconds.

Tags:kubernetesgateway-apiingress-nginxdevopssecurity

Get articles like this in your inbox

Practical security, infrastructure, and DevOps insights for teams in regulated industries. Published weekly.

Weekly digestUnsubscribe anytimeNo spam, ever

By subscribing, you agree to our Privacy Policy. Unsubscribe anytime.

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

Want to Discuss This Topic?

Schedule a call with our team to discuss how these concepts apply to your organization.

1 Hour

A focused conversation

Video or Phone

Your preferred format

No Sales Pitch

Honest, practical advice

Book a free 1-hour call