Ingress-NGINX migration across Kubernetes clusters

How to Migrate Off Ingress-NGINX Across Every Kubernetes Cluster

Matthew Holmes

Matthew Holmes

July 14, 2026 · // 15 min read

Migrating off Ingress-NGINX across a Kubernetes fleet means running two coordinated passes on every affected repository and cluster: an annotation-and-manifest rewrite pass (converting nginx.ingress.kubernetes.io/* annotations to the target controller’s syntax) and a controller-swap pass (deploying the new ingress controller alongside, cutting over, then removing the old one). The rewrite pass is mechanical for most annotations. The cutover is where migrations break, because it needs per-cluster testing and every cluster’s traffic pattern is different.

Ingress-NGINX was retired by the Kubernetes community in March 2026. According to Traefik Labs, roughly 41-50% of internet-facing Kubernetes clusters were running it at the time of retirement. There will be no future CVE patches, no bug fixes, and no compatibility updates for future Kubernetes versions. Existing installations keep working, but every vulnerability discovered after March 2026 stays unpatched. That’s the security exposure.

Migrating off Ingress-NGINX across a fleet means picking a replacement controller (Traefik, HAProxy, F5 NGINX Ingress Controller, or a Gateway API implementation), rewriting nginx.ingress.kubernetes.io annotations to that controller’s equivalents, then cutting over cluster by cluster while monitoring live traffic before removing the old controller.

This guide covers choosing a replacement that matches your existing setup, applying the mechanical annotation and manifest changes, and running the coordinated cutover across every cluster in scope without losing a quarter to team-by-team scheduling.

What does an Ingress-NGINX migration actually involve?

The work splits into three layers, and each one fails differently.

Layer one is choosing the replacement. There is no single right answer. Traefik, HAProxy, F5 NGINX Ingress Controller (a different project from Ingress-NGINX, still actively maintained), Envoy Gateway, and Cilium Gateway API all cover the Ingress-NGINX use case. Some are drop-in replacements. Others require rewriting your Ingress resources to Gateway API resources. The choice depends on how annotation-heavy your configuration is, whether you want to modernize to Gateway API now or later, and what your team already knows.

Layer two is rewriting annotations and manifests. Ingress-NGINX had a rich set of annotations (nginx.ingress.kubernetes.io/rewrite-target, nginx.ingress.kubernetes.io/proxy-body-size, nginx.ingress.kubernetes.io/ssl-redirect, and dozens more) that don’t map one-to-one to other controllers. Most have direct equivalents. Some require config changes at the controller level, not the Ingress resource level. Snippet annotations (nginx.ingress.kubernetes.io/configuration-snippet) are the hardest to migrate, because they inject arbitrary NGINX config that no other controller supports directly.

Layer three is the cutover. Deploy the new controller. Point one non-critical Ingress at it. Confirm traffic flows. Repeat for every Ingress in the cluster. Then remove the old controller. Each step touches production traffic, and the failure modes show up immediately (a broken Ingress means a service goes down or serves 502s). This is the part that stalls teams: it’s per-cluster, real-time, and can’t be automated end-to-end.

Choosing a replacement for Ingress-NGINX

The replacement decision drives everything else. Pick before you start.

Traefik with the NGINX Ingress Provider is the closest to a drop-in swap. Traefik natively parses nginx.ingress.kubernetes.io/* annotations, so most existing Ingress resources work unchanged. Good choice if your configuration is annotation-heavy and you want the smallest migration effort. Downside: you’re moving from one controller to another, not modernizing to Gateway API.

F5 NGINX Ingress Controller (the F5-maintained project, distinct from the retired Ingress-NGINX) is a similar drop-in. Same NGINX engine, actively maintained by a team at F5, supports most existing annotations. Good choice if your team is comfortable with NGINX config and wants a minimal learning curve. Downside: it’s still an Ingress-first controller, not Gateway API-first.

HAProxy Kubernetes Ingress Controller is a drop-in for teams that want a different underlying proxy. Actively maintained, supports both Ingress and Gateway API resources through the HAProxy Unified Gateway (Gateway API available now, Ingress support coming in 2026). Strong performance profile. Downside: less familiar to teams used to NGINX.

Gateway API implementations (Envoy Gateway, Cilium Gateway API) are the future-aligned choice. They rewrite your Ingress resources to Gateway API resources (Gateway, GatewayClass, HTTPRoute, TLSRoute). Not a drop-in, and a bigger migration effort. Good choice if you want to modernize your ingress layer while you’re already doing a mandatory migration.

Dual-support controllers (Traefik, Cilium, HAProxy Unified Gateway) support both Ingress and Gateway API at the same time. Deploy one as a drop-in replacement for Ingress-NGINX today, keep existing Ingress resources working, and convert routes to Gateway API on your own timeline. The pragmatic choice if you want to close the retirement gap now and modernize later.

For teams with heavy annotation usage, Traefik is often the fastest path. For teams building multi-tenant platforms, Gateway API is worth the extra effort. For teams that need to close the security gap immediately with the least learning curve, F5 NGINX Ingress Controller is the closest match.

The rest of this guide assumes you’ve picked Traefik with the NGINX Ingress Provider. The pattern applies to the other controllers with different annotation mappings.

The mechanical fix list for an Ingress-NGINX migration

Run this across every cluster and every Ingress resource in scope.

Controller deployment: Deploy the new controller alongside the existing one. Do not remove Ingress-NGINX yet.

helm repo add traefik https://traefik.github.io/charts
helm install traefik traefik/traefik \
  --namespace traefik --create-namespace \
  --set providers.kubernetesIngress.enabled=true \
  --set providers.kubernetesIngress.ingressClass=traefik

IngressClass migration: For each Ingress resource, update the ingressClassName field.

Ingress-NGINX patternReplacement
ingressClassName: nginxingressClassName: traefik
kubernetes.io/ingress.class: nginx (deprecated annotation)ingressClassName: traefik (proper field)

Annotation mapping: Most Ingress-NGINX annotations have Traefik equivalents. The most common ones:

Ingress-NGINX annotationTraefik equivalent
nginx.ingress.kubernetes.io/rewrite-target: /traefik.ingress.kubernetes.io/router.middlewares (with ReplacePath middleware)
nginx.ingress.kubernetes.io/ssl-redirect: "true"traefik.ingress.kubernetes.io/router.middlewares (with RedirectScheme middleware)
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"Same as above
nginx.ingress.kubernetes.io/proxy-body-size: 50mController-level: providers.kubernetesIngress.allowMaxBodySize
nginx.ingress.kubernetes.io/proxy-read-timeout: "600"traefik.ingress.kubernetes.io/router.middlewares (with timeout middleware)
nginx.ingress.kubernetes.io/cors-allow-origintraefik.ingress.kubernetes.io/router.middlewares (with Headers middleware)
nginx.ingress.kubernetes.io/whitelist-source-rangetraefik.ingress.kubernetes.io/router.middlewares (with IPWhiteList middleware)
nginx.ingress.kubernetes.io/auth-basictraefik.ingress.kubernetes.io/router.middlewares (with BasicAuth middleware)
nginx.ingress.kubernetes.io/configuration-snippetNo direct equivalent. Requires review.

Middleware resources: Many annotation behaviors that were inline in Ingress-NGINX become separate Middleware resources in Traefik. Example: redirecting HTTP to HTTPS.

apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
  name: redirect-https
  namespace: default
spec:
  redirectScheme:
    scheme: https
    permanent: true

Then reference it from the Ingress:

metadata:
  annotations:
    traefik.ingress.kubernetes.io/router.middlewares: default-redirect-https@kubernetescrd

Update the Helm charts and Kustomize overlays where the ingress class is templated, not just the raw Ingress manifests. This is where most repos declare their ingress config, not in one-off YAML files.

Update CI validation. If your CI runs kubectl apply --dry-run or helm template | kubectl apply --dry-run, make sure the Traefik CRDs are known to CI so validation doesn’t fail on Middleware resources.

A prompt you can run today for a single-repo migration

If you’re already running AI-assisted code changes, this is the prompt to hand it, repo by repo:

Migrate this repository off Ingress-NGINX to Traefik.

For each Kubernetes manifest, Helm chart, or Kustomize overlay:
1. Change `ingressClassName: nginx` to `ingressClassName: traefik`.
2. Remove the deprecated `kubernetes.io/ingress.class: nginx` annotation
   in favor of the `ingressClassName` field.
3. Map Ingress-NGINX annotations to Traefik equivalents:
   - `nginx.ingress.kubernetes.io/rewrite-target` → Middleware resource
     with ReplacePath
   - `nginx.ingress.kubernetes.io/ssl-redirect` and
     `force-ssl-redirect` → Middleware resource with RedirectScheme
   - `nginx.ingress.kubernetes.io/proxy-body-size` → controller-level
     config (flag this for controller config update, not the Ingress
     resource)
   - `nginx.ingress.kubernetes.io/proxy-read-timeout` → Middleware
     resource with timeout settings
   - `nginx.ingress.kubernetes.io/cors-allow-origin`,
     `nginx.ingress.kubernetes.io/cors-allow-methods` → Middleware
     resource with Headers configuration
   - `nginx.ingress.kubernetes.io/whitelist-source-range` → Middleware
     resource with IPWhiteList
   - `nginx.ingress.kubernetes.io/auth-basic` → Middleware resource
     with BasicAuth
4. Flag any `nginx.ingress.kubernetes.io/configuration-snippet` or
   `nginx.ingress.kubernetes.io/server-snippet` annotations for
   manual review. These inject arbitrary NGINX config and have no
   direct Traefik equivalent.
5. Create Middleware resources in the appropriate namespace.
   Reference them from the Ingress using the
   `traefik.ingress.kubernetes.io/router.middlewares` annotation.
6. Do not remove Ingress-NGINX yet. The new Traefik controller must
   be deployed alongside first, tested, and cut over per-Ingress
   before Ingress-NGINX is removed.

That prompt works on one repo. The question that actually stalls these migrations is what happens with twenty clusters, forty teams, and every team owning a subset of the manifests.

Why isn’t the mechanical fix the hard part?

A single repo migration like the one above takes an afternoon. The math changes once you have twenty clusters’ worth of manifests spread across forty teams.

Each cluster has its own traffic profile. Some serve external customer traffic, where a five-minute outage during cutover is a customer-visible incident. Others serve internal tools, where an outage is a Slack complaint. The cutover procedure has to reflect that difference, cluster by cluster.

Each team owns a subset of the manifests, and no team can see the others. A payments team knows it uses rewrite-target and whitelist-source-range. The team next to them uses configuration-snippet for a custom logging directive. Nobody has a single view of every annotation in use across the org.

Snippet annotations are the hardest failure mode. Snippet annotations account for under 5% of Ingress resources in most fleets, but they’re arbitrary NGINX config with no automated mapping to any other controller. Every one needs a person to read the snippet, understand what it does, and decide whether to replicate the behavior in the new controller or drop the requirement.

CI has to be updated in every repo that validates manifests. If your CI runs kubectl apply --dry-run against a cluster that only has Ingress-NGINX CRDs, the Traefik Middleware resources fail validation. Every repo touching manifests needs its CI updated to recognize the new CRDs.

Multiply that by twenty clusters, forty teams, and eight hundred Ingress resources, and the migration that took an afternoon in one repo becomes a quarter-long project nobody has time to run end to end. That’s before the actual cutover, which has to happen cluster by cluster with monitoring in real time.

That’s why so many organizations are still running Ingress-NGINX in July 2026, four months after retirement. The technical fix was never the blocker. Coordinating the fix across clusters, teams, and cutover windows is what makes the work stall.

A platform engineer we work with put it this way: “We knew what to do. We spent two months figuring out how to do it everywhere.”

Running an Ingress-NGINX migration across every cluster, not just one

The change is defined. The annotation mapping and middleware pattern above cover the actual code. What’s missing for most teams isn’t knowing what to change. It’s executing that change consistently across every cluster and repo that needs it, and tracking each cutover to completion without a spreadsheet.

Tidra is an AI coding agent for both implementation and coordination of code changes across your organization. For a migration like this one, that means:

  1. Target every repo with Ingress-NGINX manifests using one filter. Set up the initiative once, scoped to repos that still declare ingressClassName: nginx or the deprecated kubernetes.io/ingress.class annotation, and skip the manual repo-by-repo hunt.
  2. Run the same prompt across all of them. The annotation mappings and middleware creation above become the initiative’s instructions, and Tidra generates the change for each repo individually, respecting that repo’s specific annotation set and manifest structure.
  3. Review the plan before any code moves. Nothing gets touched until you’ve seen what Tidra proposes to do in each repo. Snippet annotations get flagged for manual review automatically, and you adjust the plan wherever a repo needs a different middleware configuration or mapping.
  4. Bulk-create PRs, not one-off ones. Each repo gets its own reviewable pull request. Your engineers review and merge it, same as any other PR. The cutover itself stays in your team’s hands, because it has to happen live against real clusters.
  5. Track completion from one dashboard instead of a spreadsheet that goes stale within a week.

Tidra doesn’t replace the judgment calls in snippet review or the per-cluster cutover. The configuration-snippet annotations still need a person to look at them, and the traffic cutover still requires monitoring. What changes is that the eight hundred Ingress resources across forty teams stop being forty separate side projects competing for attention and become one initiative with a single owner and a status you can actually see.

Before and after: what an Ingress-NGINX to Traefik diff looks like

A single Ingress resource migration:

--- ingress.yaml
+++ ingress.yaml
@@ -2,15 +2,13 @@
 kind: Ingress
 metadata:
   name: web-app
-  annotations:
-    kubernetes.io/ingress.class: nginx
-    nginx.ingress.kubernetes.io/ssl-redirect: "true"
-    nginx.ingress.kubernetes.io/proxy-body-size: 50m
+  annotations:
+    traefik.ingress.kubernetes.io/router.middlewares: default-redirect-https@kubernetescrd
 spec:
+  ingressClassName: traefik
   tls:
     - hosts:
         - app.example.com
       secretName: web-app-tls
   rules:
     - host: app.example.com

And the new Middleware resource that replaces the SSL-redirect annotation:

--- middleware-redirect-https.yaml (new file)
+++ middleware-redirect-https.yaml (new file)
@@ -0,0 +1,10 @@
+apiVersion: traefik.io/v1alpha1
+kind: Middleware
+metadata:
+  name: redirect-https
+  namespace: default
+spec:
+  redirectScheme:
+    scheme: https
+    permanent: true

That’s what a single repo’s PR looks like. Across eight hundred Ingress resources, it’s eight hundred PRs of similar shape, each generated against that repo’s actual manifests, each opened for the team that owns it to review.

Ingress-NGINX migration checklist

Use this as a per-repo gate before you call a migration done:

  • Replacement controller (Traefik / HAProxy / F5 NGINX / Gateway API implementation) chosen and documented
  • New controller deployed alongside Ingress-NGINX in every affected cluster
  • ingressClassName field updated on every Ingress resource
  • Deprecated kubernetes.io/ingress.class annotation removed
  • nginx.ingress.kubernetes.io/* annotations mapped to the target controller’s equivalents
  • Middleware resources created for behaviors without direct annotation equivalents
  • Snippet annotations reviewed by a person, not just the automated pass
  • Controller-level config (body size limits, timeouts) updated in Helm values or Kustomize base
  • CI validation updated to include the new controller’s CRDs
  • Cutover tested in a staging cluster before production
  • Cutover completed cluster by cluster with monitoring
  • Ingress-NGINX removed after every Ingress resource is cut over

What usually breaks after an Ingress-NGINX migration ships

Almost always the same set of things:

Snippet behaviors that weren’t replicated. A configuration-snippet was doing something subtle (adjusting a header, setting a specific cache directive, running a custom Lua script) that nobody noticed until an integration test failed weeks later. Keep the old Ingress-NGINX deployment archived, not running, just archived, for a few weeks after every migrated cluster so you can go back and check what the original snippet was doing when something breaks.

Middleware ordering. Traefik middlewares apply in the order listed. If multiple Ingress-NGINX annotations interacted (a rewrite followed by an auth check followed by a redirect), the middleware order in Traefik has to match. This is where hard-to-diagnose bugs live.

TLS certificate reuse. If your Ingress-NGINX setup relied on the Ingress resource itself provisioning certs via cert-manager, confirm cert-manager still sees the migrated Ingress resources. IngressClass changes can quietly break cert-manager’s discovery logic.

Metrics dashboards. The Prometheus metrics from Traefik don’t carry the same labels as Ingress-NGINX metrics. Grafana dashboards need updating. This usually gets noticed a week after cutover, when someone tries to answer “what’s our p99 latency” and the query returns nothing.

FAQ

Is Ingress-NGINX safe to keep running? No. Ingress-NGINX reached end of life in March 2026. There are no more security patches, no bug fixes, and no compatibility updates for future Kubernetes versions. Every vulnerability discovered after March 2026 remains unpatched in Ingress-NGINX indefinitely.

Is the F5 NGINX Ingress Controller the same as Ingress-NGINX? No. Ingress-NGINX (kubernetes/ingress-nginx) was the community-maintained project that has been retired. F5 NGINX Ingress Controller (nginxinc/kubernetes-ingress) is a different project maintained by F5, still actively supported, with both open source and commercial editions. It uses the same NGINX engine but is a separate codebase.

Do I have to migrate to Gateway API? No. The Ingress API itself is not deprecated and is not being removed. It’s feature-frozen, meaning no new capabilities will be added, but existing Ingress resources keep working. You can migrate to another Ingress controller (Traefik, F5 NGINX, HAProxy) and stay on the Ingress API. Gateway API is the future-aligned choice, but not a mandatory step in an Ingress-NGINX migration.

How long does an Ingress-NGINX migration take? For a single Ingress resource, minutes. For a repo with a handful of resources, an afternoon. For an organization with dozens of clusters and hundreds of Ingress resources across many teams, budget three to six months if run manually, or three to six weeks if run as a coordinated initiative that handles the per-repo work and lets your teams focus on the cutover.

What about the snippet annotations? nginx.ingress.kubernetes.io/configuration-snippet and nginx.ingress.kubernetes.io/server-snippet inject arbitrary NGINX config and have no direct equivalent in Traefik, HAProxy, or Gateway API. Every snippet needs a person to review it and decide whether the behavior can be replicated with the target controller’s primitives, moved to controller-level config, or dropped. This is the failure mode most likely to produce post-cutover regressions.

How do I migrate off Ingress-NGINX across many clusters at once? Define the annotation mapping and middleware pattern once, then run it as a single initiative scoped to every repo and cluster still using Ingress-NGINX. Each repo gets its own generated change and its own PR, reviewed by the team that owns it, tracked from one place. Cutover per cluster is coordinated based on traffic sensitivity and change windows.

Every organization still on Ingress-NGINX in mid-2026 is running the same math: a mechanical fix that takes an afternoon per repo, multiplied by however many repos and clusters they have, against a security clock that started in March. The teams that close the gap fastest aren’t the ones with the cleverest annotation mapping. They’re the ones who stopped treating each repo as its own project.


Ready to run this across your clusters? Connect your Git provider and Tidra opens pull requests in every repo that needs them: tidra.ai/get-started/