Ingress NGINX Is Retiring — Why I Switched to Traefik Before the Deadline

In November 2025, the Kubernetes SIG Network and Security Response Committee dropped a bombshell: Ingress NGINX is being retired. March 31st, 2026 was the deadline. After that date, no more security patches. No more bug fixes. The repos go read-only and move to kubernetes-retired.
I had Ingress NGINX running across three production clusters. Like most of you, I never gave it a second thought — it was the default, it worked, and the annotations were burned into my muscle memory. nginx.ingress.kubernetes.io/rate-limit, nginx.ingress.kubernetes.io/ssl-redirect, nginx.ingress.kubernetes.io/proxy-body-size — I could write those from memory but couldn’t tell you who maintained the project.
Turns out, that was the problem. The project had been running on 1–2 volunteers working off-hours for years. The “flexibility” of snippet annotations that let you inject arbitrary NGINX config? Now classified as critical security vulnerabilities. The community attempted a successor project called InGate. That’s being retired too.
I didn’t wait until March. Here’s exactly how I migrated to Traefik with zero downtime, what broke, and the one mistake that cost me two hours of debugging.
Why Traefik and Not Gateway API?
The official Kubernetes recommendation is to migrate to Gateway API. And for greenfield projects, that’s probably the right call. But I had 47 Ingress resources across three clusters with a mix of rate limiting, path rewrites, basic auth, and TLS configurations. Rewriting all of that to Gateway API’s HTTPRoute resources wasn’t something I could do before the deadline without risking production stability.
Traefik v3.6 offered something no other option did: a Kubernetes Ingress NGINX Provider that natively translates nginx.ingress.kubernetes.io annotations. My existing Ingress YAML files worked unchanged. I could migrate the controller first and modernize to Gateway API on my own timeline.
Here’s the comparison I ran through before deciding:
| Criteria | Traefik | Gateway API | HAProxy |
|---|---|---|---|
| Drop-in replacement | ✅ NGINX provider handles 80% of annotations | ❌ Requires full rewrite to HTTPRoute | ⚠️ Partial annotation support |
| Learning curve | Low — Ingress resources stay the same | High — entirely new API | Medium — familiar concepts |
| Gateway API support | ✅ Full v1.4 in v3.6 | ✅ Native | ⚠️ Partial |
| Built-in Let’s Encrypt | ✅ Yes | ❌ Requires cert-manager | ❌ Requires cert-manager |
| Dashboard | ✅ Built-in | ❌ External tools needed | ⚠️ Stats page only |
I chose Traefik because it solved my immediate problem (unmaintained Ingress controller) without creating a new one (learning Gateway API under deadline pressure). Both problems are worth solving — just not simultaneously.
The Migration: Step by Step
Step 1: Backup Everything
Before touching anything, I exported all existing resources:
# Export all Ingress resources across all namespaces
kubectl get ingress --all-namespaces -o yaml > ingress-backup-$(date +%Y%m%d).yaml
# Export NGINX ConfigMaps (custom configurations)
kubectl get configmap --all-namespaces \
-l app.kubernetes.io/name=ingress-nginx \
-o yaml > nginx-configmaps-backup.yaml
# Document current LoadBalancer IPs
kubectl get svc -n ingress-nginx ingress-nginx-controller \
-o jsonpath='{.status.loadBalancer.ingress[0].ip}'
This took 30 seconds. It saved me two hours later. More on that in the “What Went Wrong” section.
Step 2: Install Traefik Alongside NGINX
The key insight: don’t uninstall Ingress NGINX first. Run both controllers simultaneously, verify Traefik handles routing correctly, then shift traffic.
# Add the Traefik Helm repo
helm repo add traefik https://traefik.github.io/charts
helm repo update
# Install Traefik with the NGINX compatibility provider
helm upgrade --install traefik traefik/traefik \
--namespace traefik \
--create-namespace \
--set="image.tag=v3.6.2" \
--set="providers.kubernetesIngressNginx.enabled=true" \
--set="service.type=ClusterIP" \
--set="logs.general.level=DEBUG"
Running as ClusterIP initially means Traefik doesn’t compete with NGINX for the LoadBalancer IP. It’s invisible to external traffic until you’re ready.
Step 3: Verify Traefik Is Discovering Ingress Resources
# Check that Traefik sees your Ingress resources
kubectl logs -n traefik deployment/traefik | grep -i "ingress"
# You should see lines like:
# "Ingress ingress detected with NGINX annotations"
# "Configuration loaded from Kubernetes provider"
If you don’t see your Ingress resources being picked up, check that the kubernetesIngressNginx provider is enabled. This was my first stumbling block — I forgot the flag and spent 20 minutes wondering why nothing worked.
Step 4: Test Routing Before Flipping DNS
Here’s where the backup pays off. Use curl --connect-to to test Traefik’s routing without changing DNS:
# Get Traefik's ClusterIP
TRAEFIK_IP=$(kubectl get svc -n traefik traefik -o jsonpath='{.spec.clusterIP}')
# Test routing by forcing traffic to Traefik's IP
curl --connect-to "api.example.com:80:${TRAEFIK_IP}:80" \
"http://api.example.com" -v
I tested every Ingress resource this way. All 47 of them. Yes, it was tedious. Yes, it caught three that had custom NGINX snippets that Traefik’s compatibility layer didn’t handle.
Step 5: Shift Traffic with Zero Downtime
Once Traefik passed all routing tests, I promoted it to LoadBalancer:
helm upgrade traefik traefik/traefik \
--namespace traefik \
--set="providers.kubernetesIngressNginx.enabled=true" \
--set="service.type=LoadBalancer"
Then I updated DNS to point to Traefik’s LoadBalancer IP. Here’s the critical part: keep Ingress NGINX running for 48 hours after the DNS switch. ISPs cache DNS records longer than their TTL, and you want a safety net.
Step 6: Handle ExternalDNS Conflicts
If you use ExternalDNS (and you should), both controllers will try to update the Ingress status with their own LoadBalancer IPs, causing ExternalDNS to flip-flop. The fix:
# Traefik values — disable publishService during migration
publishService:
enabled: false
# Ingress NGINX values — publish Traefik's IP instead
controller:
publishService:
pathOverride: "traefik/traefik"
This makes NGINX report Traefik’s LoadBalancer IP to ExternalDNS, so DNS records point correctly even though NGINX is still technically the “active” controller. Once you’ve verified Traefik handles all traffic and are ready to uninstall NGINX, re-enable publishService on Traefik.
The Annotation Compatibility Reality Check
Traefik’s NGINX provider handles most annotations, but not all. Here’s what I found across my 47 Ingress resources:
| Annotation | Traefik Support | Migration Effort |
|---|---|---|
nginx.ingress.kubernetes.io/ssl-redirect | ✅ Native | None |
nginx.ingress.kubernetes.io/proxy-body-size | ✅ Native | None |
nginx.ingress.kubernetes.io/rate-limit | ✅ Via Middleware | None |
nginx.ingress.kubernetes.io/rewrite-target | ✅ Native | None |
nginx.ingress.kubernetes.io/auth-type (basic auth) | ✅ Via MiddlewareSecret | Low |
nginx.ingress.kubernetes.io/configuration-snippet | ❌ Not supported | Manual rewrite |
nginx.ingress.kubernetes.io/server-snippet | ❌ Not supported | Manual rewrite |
nginx.ingress.kubernetes.io/custom-headers | ⚠️ Partial | Review needed |
The *-snippet annotations are the ones that require manual work. They let you inject arbitrary NGINX configuration blocks, which is powerful but also the exact security vulnerability that contributed to Ingress NGINX’s retirement. In my case, I had three Ingress resources using configuration-snippet for custom add_header directives. I replaced them with Traefik Middleware:
# Before: NGINX snippet annotation
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
nginx.ingress.kubernetes.io/configuration-snippet: |
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
# After: Traefik Middleware
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
name: security-headers
spec:
headers:
customResponseHeaders:
X-Frame-Options: "DENY"
X-Content-Type-Options: "nosniff"
Then reference it from your Ingress with the traefik.ingress.kubernetes.io/router.middlewares annotation. Clean, declarative, and no arbitrary config injection.
What Went Wrong (So You Don’t Have To)

Two things broke during migration:
1. Let’s Encrypt HTTP challenges failed. During the migration window, Traefik wasn’t publicly exposed (ClusterIP mode), so HTTP-01 challenges couldn’t be completed. If your certificates were expiring during the migration, you’d get a gap. The fix: use pre-existing TLS secrets (spec.tls[0].secretName) and don’t rely on HTTP challenges during migration. For new certificates, use DNS-01 challenges instead.
2. I lost a custom ConfigMap override. One of my clusters had a custom proxy-read-timeout set in the Ingress NGINX ConfigMap. When I uninstalled Ingress NGINX, that ConfigMap went with it. I hadn’t backed it up separately because kubectl get configmap --all-namespaces -l app.kubernetes.io/name=ingress-nginx only catches labels matching ingress-nginx — my custom ConfigMap had a different label. I caught it because I’d saved the raw output earlier, but it cost me two hours of debugging 502 errors on one service. Lesson: kubectl get configmap --all-namespaces -o yaml — back up everything, not just labeled resources.
What People Get Wrong About This Migration
| Concern | Reality |
|---|---|
| ”Traefik is too complex compared to NGINX” | The NGINX compatibility layer means your existing Ingress YAML works unchanged. You can learn Traefik’s native features at your own pace. |
| ”We invested heavily in NGINX annotations — migration is risky” | Traefik handles 80% of real-world annotation usage natively. The remaining 20% (mainly snippets) require manual work but are usually small config blocks. |
| ”What about cert-manager integration?” | Traefik has built-in Let’s Encrypt support. You can actually drop cert-manager if you only use it for Ingress TLS. Keep it if you need it for other resources. |
| ”Gateway API is the future — shouldn’t we skip Traefik?” | Yes, Gateway API is the future. But migrating an Ingress controller and rewriting to Gateway API simultaneously is a recipe for production incidents. Do one at a time. |
| ”Our team doesn’t know Traefik” | If they know Ingress resources (and they do, because they’ve been using Ingress NGINX), they already know 80% of Traefik. The rest is Traefik-specific Middleware, which is well-documented. |
What I’d Do Differently
If I had to do this migration again:
-
Start with a non-production cluster first. I tested on staging, but staging traffic patterns don’t match production. Run Traefik alongside NGINX in production for a week with a small traffic percentage before fully switching.
-
Audit
*-snippetannotations before starting.kubectl get ingress --all-namespaces -o yaml | grep -A2 'snippet'will show you every Ingress using custom NGINX config. These are the ones that need manual work. Identify them early. -
Use Traefik’s dashboard during migration. Enable it with
--set="dashboard.enabled=true" --set="dashboard.ingressRoute.enabled=true". Seeing real-time routing decisions in a UI is invaluable when debugging why a specific path isn’t working. -
Plan for certificate management. If you’re using cert-manager with Ingress NGINX’s
cert-manager.io/cluster-issuerannotation, it continues to work with Traefik. But HTTP-01 challenges need Traefik to be publicly reachable. DNS-01 challenges avoid this entirely.
The Bottom Line
Ingress NGINX retiring wasn’t a surprise to anyone paying attention — one or two maintainers can’t sustain a project that handles billions of requests. But the timeline was aggressive, and the reality of migrating 47 Ingress resources across three clusters under a deadline was stressful.
Traefik’s NGINX compatibility layer made it manageable. I didn’t have to rewrite 47 YAML files. I didn’t have to retrain my team on a new API overnight. I installed Traefik, verified it worked, shifted traffic, and decommissioned NGINX. The entire process took about a week of part-time work per cluster.
Gateway API is where the ecosystem is heading, and I’ll migrate there eventually. But doing it on my timeline — not under a March deadline with security patches ending — is the difference between a thoughtful migration and a panicked one.
If you’re still running Ingress NGINX and haven’t started planning, start today. Even a kubectl get ingress --all-namespaces inventory is progress. The deadline has already passed — every day without security patches is a day you’re running unsupported infrastructure.
🚀 Master Kubernetes Networking
I recommend Kubernetes Networking by O'Reilly — it covers Ingress, Gateway API, Service Mesh, and everything between. It's the reference I keep on my desk for production networking decisions.
Disclosure: This link may earn a commission at no extra cost to you.
Have you migrated from Ingress NGINX yet? Which controller did you choose and what surprised you? Share your experience in the comments — the collective war stories help everyone.
Enjoying the content? Here are tools I personally use and recommend:
- 🌐 Hosting: Bluehost — what this blog runs on
- 🛒 Tech Gear: My Amazon Store — keyboards, monitors, dev tools I use
Purchases through my links help keep this blog ad-free 💙
Enjoyed this post?
Subscribe to the newsletter or follow on YouTube for more dev content.
🎬 Watch Shorts