In November 2025, Kubernetes SIG Network and the Security Response Committee announced that the community ingress-nginx controller would be retired, with best-effort maintenance until March 2026 and no releases or security fixes after that. Existing installations keep working, but running an internet-facing proxy without security updates is not a plan. The recommended direction is Gateway API, or another maintained Ingress controller if you need to stay on the Ingress API for now.
We run 140 Ingress objects across four clusters. This guide is the plan we followed. It is written to be reused: inventory, decide, convert, run in parallel, cut over, clean up.
Step 1: inventory what you actually use
The difficult part of leaving ingress-nginx is not the Ingress resource. It is the annotations, which encode behaviour that has no equivalent field in the Ingress API. List them first:
kubectl get ingress -A -o json \
| jq -r '.items[] | .metadata.annotations // {} | keys[]' \
| grep '^nginx.ingress.kubernetes.io/' | sort | uniq -c | sort -rn
Our top results:
118 nginx.ingress.kubernetes.io/proxy-body-size
97 nginx.ingress.kubernetes.io/force-ssl-redirect
41 nginx.ingress.kubernetes.io/proxy-read-timeout
23 nginx.ingress.kubernetes.io/rewrite-target
12 nginx.ingress.kubernetes.io/auth-url
9 nginx.ingress.kubernetes.io/configuration-snippet
6 nginx.ingress.kubernetes.io/canary
4 nginx.ingress.kubernetes.io/limit-rps
Also export the controller's ConfigMap: global settings like use-forwarded-headers, proxy-buffering, and custom log formats often matter more than per-Ingress annotations.
configuration-snippet and server-snippet deserve special attention. They inject raw nginx config, have been the source of serious security issues, and have no portable equivalent. Each one needs a human to read it and decide what it really does.
Step 2: choose an implementation
Gateway API is a specification; you still need a controller. Main criteria for us:
- Conformance: the implementation passes the Gateway API conformance tests for the features we need (HTTPRoute, header modification, redirects, timeouts).
- Policy support for what annotations used to do: auth, rate limits, body size, timeouts. Many of these are implementation-specific policy resources, so they still lock you in somewhat.
- Operational fit: how it scales, how it reloads config, what metrics it exposes.
- Long-term maintenance: a healthy maintainer base, which is exactly what ingress-nginx lacked.
Several mature implementations exist (Envoy Gateway, Istio, Cilium, NGINX Gateway Fabric, Traefik, Kong, and cloud provider gateways). We chose one based on a two-week trial with our ten most complex Ingresses.
Step 3: map concepts
| ingress-nginx | Gateway API |
|---|---|
| IngressClass + controller deployment | GatewayClass + Gateway (owned by platform team) |
Ingress rules.host + paths |
HTTPRoute hostnames + rules.matches |
tls section |
Gateway listeners with certificateRefs |
force-ssl-redirect |
HTTP listener + HTTPRoute with RequestRedirect filter |
rewrite-target |
URLRewrite filter |
canary annotations |
Weighted backendRefs |
proxy-read-timeout |
HTTPRoute timeouts (where supported) or implementation policy |
auth-url, limit-rps, proxy-body-size |
Implementation-specific policies |
The ownership split is a real improvement: the platform team owns Gateways (listeners, certificates, addresses), application teams own HTTPRoutes and attach them to allowed Gateways.
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: shop-api
namespace: shop
spec:
parentRefs:
- name: public
namespace: gateway-system
sectionName: https
hostnames: ["api.example.com"]
rules:
- matches:
- path: {type: PathPrefix, value: /v2}
filters:
- type: URLRewrite
urlRewrite:
path: {type: ReplacePrefixMatch, replacePrefixMatch: /}
backendRefs:
- name: shop-api
port: 8080
Step 4: convert, then review
ingress2gateway converts Ingress resources (including some ingress-nginx annotations) into Gateway API resources. We used it to generate a first draft per namespace and reviewed every diff by hand. Unsupported annotations are reported, which gave us a precise list of manual work.
ingress2gateway print --providers ingress-nginx --namespace shop > shop-gateway.yaml
Step 5: run both side by side
The new controller got its own load balancer address. For each host:
- Deploy the HTTPRoute to the new Gateway. The old Ingress keeps serving.
- Test with
curl --resolve api.example.com:443:<new-ip>against real paths, including redirects, auth, uploads and websockets. - Compare access logs and error rates using synthetic traffic.
Nothing changes for users until DNS moves.
Step 6: cut over host by host
Lower the DNS TTL to 60 seconds a day ahead. Switch one host's DNS record to the new address, watch error rates and latency for an hour, then continue. Start with internal and low-traffic hosts. Rollback is a DNS change back, which is why the old Ingress stays in place until the host has been stable for a week.
Watch for things that differ between proxies in subtle ways: default timeouts, maximum header sizes, how X-Forwarded-For is built, HTTP/2 to backends, and gzip. Most of our post-cutover issues were in this list.
Step 7: clean up
After all hosts moved: remove Ingress objects, uninstall ingress-nginx, delete its load balancer, and remove annotation-based tooling and dashboards. Update templates and docs so new services start with HTTPRoutes.
Common mistakes
| Mistake | Why it hurts | Do this instead |
|---|---|---|
| Converting without an annotation inventory | Behaviour silently lost | Inventory annotations and controller ConfigMap first |
| Copying snippets into new raw config | Same risks, new place | Re-implement intent with supported features |
| Big-bang DNS switch | Hard to isolate problems | Host by host with low TTLs |
| Deleting old Ingresses on cutover day | No fast rollback | Keep them for a week after stability |
| Ignoring proxy default differences | Timeouts and header limits change | Compare defaults explicitly and test edge cases |
Key takeaways
- ingress-nginx stopped receiving releases and security fixes after March 2026; plan the move now.
- Annotations and snippets are the real migration work; inventory them first.
- Gateway API separates platform-owned Gateways from team-owned routes.
- Convert with tooling, review by hand, run in parallel, cut over per host.
- Keep a DNS-based rollback path until each host is proven.
References
- Kubernetes blog, Ingress NGINX retirement — https://kubernetes.io/blog/2025/11/11/ingress-nginx-retirement/
- Gateway API documentation — https://gateway-api.sigs.k8s.io/
- Gateway API implementations — https://gateway-api.sigs.k8s.io/implementations/
- ingress2gateway — https://github.com/kubernetes-sigs/ingress2gateway
- Migrating from Ingress — https://gateway-api.sigs.k8s.io/guides/migrating-from-ingress/
Comments
0No comments yet. Be the first to comment!
Sign In to post a comment.