Replacing ingress-nginx: Gateway API in practice

ingress-nginx ends in March 2026. How to migrate to the Gateway API with Kong, Traefik or NGINX Gateway Fabric: config, commands, costs.

Black and white photo of a woman typing code on a laptop resting on her lap

Short answer: the ingress-nginx project has reached end of life — the Kubernetes team has announced that maintenance and security fixes will stop in March 2026. The official successor is not another Ingress controller but an API: the Gateway API, which went GA (v1) in October 2023. Migrating means installing the Gateway API CRDs, deploying a conformant controller (Kong, Traefik or NGINX Gateway Fabric), translating each Ingress into a Gateway + HTTPRoute, then switching DNS. Both can run side by side during the transition.

Many teams running a cluster for a small or mid-sized business find out by reading a security bulletin: "ingress-nginx retirement". The controller installed three years ago with a single helm install and never touched since becomes, overnight, an unmaintained component exposed to the internet. It isn't an emergency to be fixed in one night, but it is a project to plan now, because it affects the entry point for all production HTTP traffic.

Black and white photo of a woman typing code on a laptop resting on her lap

Why ingress-nginx is shutting down and what changes

ingress-nginx is the Kubernetes project's historical Ingress controller, maintained by SIG Network. The retirement announcement, published on the Kubernetes blog in November 2025, points to two reasons: a maintainer team reduced to a handful of volunteers for a critical component used by a majority of clusters, and technical debt that had become hard to carry — notably the well-known configuration-by-annotation model and configuration-snippet, the source of several high-impact CVEs (including CVE-2025-1974, "IngressNightmare", which allowed arbitrary code execution inside the controller).

In practical terms:

  • after the shutdown, no more releases, no more security fixes, no more updated images;
  • your cluster keeps running — nothing breaks on day one;
  • but any new CVE in NGINX, in Lua or in the controller itself will remain without an official patch.

An Ingress controller is publicly exposed and holds, by design, a ClusterRole able to read every Secret in the cluster (for TLS certificates). That is exactly what made IngressNightmare so devastating. Staying on an unmaintained component at that spot in the system is not a tenable option beyond a few months.

Don't confuse the two: ingress-nginx (the Kubernetes community controller) and NGINX Ingress Controller, the product maintained by F5/NGINX. The latter continues to exist. Most clusters set up via helm repo add ingress-nginx use the former.

Ingress itself is also running out of road

The problem goes beyond a single controller. The networking.k8s.io/v1 Ingress API is frozen: it only covers HTTP routing by host and path. Everything else — rewrites, timeouts, mTLS, traffic splitting between two versions, headers, gRPC, TCP — is expressed through proprietary annotations that differ from vendor to vendor. An Ingress with ten nginx.ingress.kubernetes.io/* annotations is portable nowhere.

The Gateway API answers that with typed resources, validated by the API server, and a model of shared responsibilities between the platform team and the application teams.

The Gateway API model in three resources

ResourceWho owns itRole
GatewayClassProvider / platformDesignates the implementation (Kong, Traefik, NGINX…)
GatewayPlatform teamThe listeners: ports, protocols, hostnames, TLS certificates
HTTPRouteApplication teamThe routing: paths, headers, backends, weights, filters

The operational benefit is immediate: the platform team owns the entry point and the certificates, developers declare their routes in their own namespace, with no way to touch the 443 listener or hijack a hostname. Authorisation goes through allowedRoutes on the Gateway and ReferenceGrant for cross-namespace references.

The versions that matter in November 2026: GATEWAY_API_VERSION=v1.3.0 brings HTTPRoute, GRPCRoute and GatewayClass to v1 (stable), while TCPRoute, UDPRoute and TLSRoute remain in v1alpha2. The CRDs don't ship with Kubernetes: you have to install them explicitly.

kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.3.0/standard-install.yaml
kubectl get crd | grep gateway.networking.k8s.io

The standard channel is enough for classic HTTP/HTTPS. The experimental channel is required for TCPRoute, BackendTLSPolicy or advanced percentage-based routing, depending on the implementation.

Choosing your implementation: Kong, Traefik or NGINX Gateway Fabric

Three serious candidates for an SMB cluster, depending on what you were already doing with your annotations.

Kong Ingress Controller — if you need a real API gateway

Kong is the relevant choice as soon as the entry point does more than route: key-based or OIDC authentication, per-consumer rate limiting, request transformation, structured logging. The controller (KIC 3.x) is Gateway API conformant and exposes its features through KongPlugin objects attached to an HTTPRoute.

helm repo add kong https://charts.konghq.com
helm repo update
helm install kong kong/ingress -n kong --create-namespace \
  --set controller.ingressController.image.tag=3.5 \
  --set gateway.env.database=off

DB-less mode (database=off) is the one to favour: configuration lives in the Kubernetes CRDs, not in a PostgreSQL database you have to back up. A rate-limiting plugin then attaches to a specific route:

apiVersion: configuration.konghq.com/v1
kind: KongPlugin
metadata:
  name: rate-limit-api
  namespace: production
plugin: rate-limiting
config:
  minute: 120
  policy: local
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: api
  namespace: production
  annotations:
    konghq.com/plugins: rate-limit-api
spec:
  parentRefs:
    - name: public-gateway
      namespace: infra
  hostnames:
    - api.example.com
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /v1
      backendRefs:
        - name: api-svc
          port: 8080

The cost: Kong Gateway OSS is free (Apache 2.0 licence), but the enterprise plugins (full OIDC, RBAC, Konnect) are paid. Budget two pods of 256 MiB to 512 MiB of RAM for moderate traffic.

Traefik — the easiest to roll out

Traefik v3 supports the Gateway API alongside its long-standing IngressRoute objects. If you were already using Traefik, or if your needs come down to routing + TLS + a few middlewares, this is the cheapest migration.

helm repo add traefik https://traefik.github.io/charts
helm install traefik traefik/traefik -n traefik --create-namespace \
  --set providers.kubernetesGateway.enabled=true \
  --set gateway.enabled=false

Watch out for one detail that can cost you half a day: by default, the Traefik chart creates a Gateway of its own. If you manage yours in GitOps, disable it (gateway.enabled=false), otherwise you end up with two objects fighting over the same listener.

NGINX Gateway Fabric — to stay on NGINX

NGF is the Gateway API implementation maintained by NGINX/F5, with NGINX as the data plane. It's the path of least surprise if your teams read NGINX directives fluently and you want to keep the same proxy behaviour (buffering, keepalive, timeouts). The trade-off is a policy ecosystem that is younger than Kong's.

Hands of an adult man typing on a laptop showing code in a dark editor

Translating your Ingress objects into HTTPRoutes

Here's the concrete exercise. A typical Ingress straight out of a production cluster:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: site
  annotations:
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/rewrite-target: /$2
    cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
  tls:
    - hosts: [www.example.com]
      secretName: site-tls
  rules:
    - host: www.example.com
      http:
        paths:
          - path: /api(/|$)(.*)
            pathType: ImplementationSpecific
            backend:
              service: { name: api-svc, port: { number: 8080 } }
          - path: /
            pathType: Prefix
            backend:
              service: { name: web-svc, port: { number: 3000 } }

The equivalent Gateway, owned by the platform team:

apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: public-gateway
  namespace: infra
spec:
  gatewayClassName: kong
  listeners:
    - name: https
      protocol: HTTPS
      port: 443
      hostname: "*.example.com"
      tls:
        mode: Terminate
        certificateRefs:
          - name: site-tls
      allowedRoutes:
        namespaces:
          from: Selector
          selector:
            matchLabels:
              gateway-access: "true"

And the route, on the application side, with the rewrite expressed as a typed filter instead of an annotation:

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: site
  namespace: production
spec:
  parentRefs:
    - name: public-gateway
      namespace: infra
  hostnames:
    - www.example.com
  rules:
    - matches:
        - path: { type: PathPrefix, value: /api }
      filters:
        - type: URLRewrite
          urlRewrite:
            path:
              type: ReplacePrefixMatch
              replacePrefixMatch: /
      backendRefs:
        - name: api-svc
          port: 8080
    - matches:
        - path: { type: PathPrefix, value: / }
      backendRefs:
        - name: web-svc
          port: 3000

Three things to remember:

  1. The regex is gone. rewrite-target: /$2 becomes a URLRewrite filter with ReplacePrefixMatch. More readable, but genuinely regular rewrites (multiple capture groups) have no standard equivalent: they go through an implementation-specific policy.
  2. ReferenceGrant is mandatory if the TLS Secret or a backend Service lives in a different namespace from the object referencing it. Without it, the route's status stays at ResolvedRefs: False and traffic falls over with a 500.
  3. cert-manager can issue certificates for a Gateway: enable --enable-gateway-api on the controller and annotate the Gateway with cert-manager.io/cluster-issuer. This has been supported since cert-manager 1.15.

The tool that does 80% of the work

The Gateway API project provides ingress2gateway, which reads your Ingress objects (including some nginx and kong annotations) and generates the matching manifests:

go install github.com/kubernetes-sigs/ingress2gateway@latest
ingress2gateway print --providers ingress-nginx --all-namespaces > gateway-api.yaml

The output is a starting point, not a deliverable: review it, consolidate the Gateway objects (the tool often creates one per Ingress) and check every annotation it failed to translate.

Switching over without downtime

The good news: both worlds can coexist. An Ingress controller and a Gateway can run side by side, each behind its own LoadBalancer-type Service, and therefore its own IP.

  1. Install the CRDs and the new controller in a dedicated namespace. Check that an IP has been assigned: kubectl get gateway -A -o wide.
  2. Test outside DNS, forcing the Host header against the new IP:
    curl -sk --resolve www.example.com:443:203.0.113.42 https://www.example.com/api/health -i
    
  3. Validate route status before any cutover: kubectl describe httproute site -n production must show Accepted: True and ResolvedRefs: True.
  4. Lower the DNS TTL to 60 s the day before, then switch the A/AAAA record. On OVHcloud or Scaleway, expect a few minutes of real propagation beyond the advertised TTL.
  5. Observe 4xx/5xx codes and p95 latencies on both the old and the new IP for 24 to 48 hours. Rollback is a simple DNS change back, as long as the old controller is still running.
  6. Uninstall ingress-nginx only after a week with no residual traffic on its Service.

The classic trap is WebSockets and long-lived connections: they aren't replayed, they just drop when the client reconnects. Plan the cutover outside peak hours, and check the timeouts on the new Gateway (Kong: KongIngress/KongUpstreamPolicy; Traefik: ServersTransport).

Computer screen showing colourful lines of Python code, close-up at an angle

On the cost side, the item to watch isn't the licence but the load balancer. Every Gateway with an exposed listener creates a LoadBalancer service, hence an IP billed by the provider — around €10 to €15 a month at OVHcloud or Scaleway for an entry-level managed LB. During the coexistence phase, you pay for both. Consolidate afterwards: a single Gateway shared by several namespaces, thanks to allowedRoutes, instead of one Gateway per application. This is exactly the kind of trade-off we handle in our cloud infrastructure and operations engagements.

Don't forget registry secrets if your images are private: the new controller's pods, and above all your applications redeployed into new namespaces, need their imagePullSecret — the topic is covered in detail in our guide to private GitLab images on Kubernetes.

Frequently asked questions

Do you absolutely have to migrate before March 2026?

No: nothing stops working technically on that date. What stops is the publication of fixes. The real criterion is your exposure: a public cluster holding customer data should be migrated within the quarter; an internal cluster behind a VPN can wait for a more comfortable window. Either way, document the decision and keep an eye on NGINX CVE announcements.

Can you keep ingress-nginx and add a Gateway?

Yes, and that's the recommended strategy for the transition. Both APIs coexist in the same cluster. Just make sure the same hostname isn't served by both at once: you'd get DNS-dependent behaviour, and therefore incidents that are impossible to reproduce.

Kong, Traefik or NGINX Gateway Fabric: which one for an SMB?

Traefik if you want minimum friction and routing + TLS is enough. Kong if the entry point needs to act as an API gateway (keys, quotas, OIDC, logging). NGINX Gateway Fabric if your team wants to keep NGINX as the data plane. All three are HTTPRoute v1 conformant, so your routes stay portable: only the vendor-specific policies (plugins, middlewares) tie you to a particular vendor.

What happens to annotations that have no equivalent?

They move to implementation-specific CRDs: KongPlugin and KongUpstreamPolicy with Kong, Middleware with Traefik, ClientSettingsPolicy and ObservabilityPolicy with NGF. Use the migration as a chance to take inventory: in practice, a third of the annotations accumulated over the years no longer serve any purpose.

Does the Gateway API handle TCP and gRPC?

GRPCRoute has been stable since v1.1 of the standard channel. TCPRoute, UDPRoute and TLSRoute remain in v1alpha2 and require the experimental channel, with support varying by implementation. To expose PostgreSQL or Redis outside the cluster, check the Gateway API project's conformance matrix first.

In summary

  • ingress-nginx is no longer maintained after March 2026: no more security fixes for an exposed component that can read every Secret in the cluster.
  • The successor is the Gateway API (v1.3.0), installed via its CRDs, with a conformant controller: Kong (API gateway), Traefik (simplicity), NGINX Gateway Fabric (NGINX continuity).
  • Migration translates each Ingress into a Gateway + HTTPRoute: annotations become typed filters, and ingress2gateway produces a first draft.
  • Both controllers can coexist: test with curl --resolve, cut over through DNS with a reduced TTL, keep the old one for a week in case of rollback.
  • Keep an eye on load balancer costs during coexistence, then consolidate onto a single Gateway via allowedRoutes.
  • Want an outside view on your entry point or your cluster? Meet the team and let's talk about your project.