DevOps

Migrate from Ingress NGINX to Kubernetes Gateway API

The ingress-nginx controller that roughly half of all Kubernetes clusters depend on is entering retirement. Best-effort maintenance from 1-2 volunteer maintainers ended in March 2026. No more releases, no bugfixes, no security patches. Existing deployments keep running, but you are on your own if something breaks.

Original content from computingforgeeks.com - post 160843

The replacement is the Kubernetes Gateway API, a set of CRDs designed by SIG-Network that fixes the fundamental problems with Ingress: vendor-specific annotations, no role separation, and no support for anything beyond HTTP. This guide covers what Gateway API changes, how the resource model works, which implementation to pick, how to convert your existing Ingress manifests with ingress2gateway, and the step-by-step migration process. If you are running Kubernetes in production, migration planning should start now.

Updated March 2026 for Gateway API v1.5.0, NGINX Gateway Fabric 2.4.2, ingress2gateway 1.0.0

Why Ingress Is Being Replaced

The Kubernetes Ingress resource was designed in the early days of the project. It handled basic HTTP routing well enough, but teams quickly outgrew it. Every advanced feature (rewrites, rate limiting, CORS, header manipulation) required controller-specific annotations. An Ingress manifest written for ingress-nginx would not work on Traefik or HAProxy without rewriting those annotations.

The specific problems that Gateway API solves:

  • Annotation sprawl: ingress-nginx accumulated 100+ annotations. Some, like configuration-snippet, inject raw NGINX config and are an active security risk
  • No role separation: application developers and platform engineers edit the same Ingress object, leading to accidental changes to shared infrastructure
  • HTTP only: Ingress has no native support for TCP, UDP, or gRPC. Every controller handles these differently (or not at all)
  • No portability: switching controllers means rewriting every Ingress manifest because annotations are not standardized
  • Frozen spec: the Ingress API has been frozen since Kubernetes 1.19. All new networking features go exclusively to Gateway API

How Gateway API Works

Gateway API introduces a role-oriented resource model with three layers. Each layer maps to a different team in the organization:

RoleResourceResponsibility
Infrastructure providerGatewayClassDefines which controller handles traffic (like StorageClass for networking). Cluster-scoped.
Cluster operatorGatewayConfigures listeners (ports, protocols, TLS certificates). Equivalent to provisioning a load balancer.
Application developerHTTPRoute, GRPCRoute, TLSRouteDefines routing rules per application. Attaches to a Gateway via parentRefs.

The separation means application developers can create and modify routes without touching the Gateway configuration, and operators can update TLS certificates or add listeners without disrupting application routing. With Ingress, both concerns lived in the same resource.

The Resource Flow

Traffic flows through three resources before reaching your application:

GatewayClass → Gateway (Listeners: ports, TLS) ← HTTPRoute → Service → Pods

A GatewayClass is created by the controller installation (one per implementation). A Gateway declares which ports and protocols to listen on, plus TLS configuration. HTTPRoute objects attach to a Gateway and define the actual routing rules: hostnames, path matching, header matching, and backend references.

Routes attach to Gateways via parentRefs. Both conditions must be met: the Route must reference the Gateway, and the Gateway’s listener must accept the Route (based on hostname filters, namespace selectors, and allowed route kinds). This two-way binding prevents unauthorized route attachment.

What’s GA in Gateway API v1.5

As of Gateway API v1.5.0 (February 2025, with installation YAML updated February 2026), these resources are stable and production-ready:

ResourceChannelDescription
GatewayClassStandard (GA)Cluster-scoped controller selector
GatewayStandard (GA)Load balancer with listeners
HTTPRouteStandard (GA)HTTP/HTTPS routing with path, header, query matching
GRPCRouteStandard (GA)Native gRPC routing (ensures HTTP/2)
TLSRouteStandard (GA, new in v1.5)SNI-based TLS routing (passthrough or terminated)
ReferenceGrantStandard (GA, new in v1.5)Cross-namespace reference authorization
BackendTLSPolicyStandard (GA)TLS between gateway and backend
CORS filterStandard (GA, new in v1.5)Native CORS configuration on HTTPRoute
TCPRoute / UDPRouteExperimentalLayer 4 routing
BackendTrafficPolicyExperimentalFine-grained traffic management

Ingress Annotations vs Gateway API: Side-by-Side

The table below maps common ingress-nginx annotations to their Gateway API equivalents. This is the reference you will keep coming back to during migration:

FeatureIngress (annotation)Gateway API (native)
TLS terminationspec.tls[].secretNameGateway.spec.listeners[].tls.certificateRefs
SSL redirectnginx.ingress.kubernetes.io/ssl-redirect: "true"HTTPRoute with RequestRedirect filter (scheme: https)
Path rewritenginx.ingress.kubernetes.io/rewrite-target: /$1HTTPRoute URLRewrite filter with replacePrefixMatch
Traffic splittingnginx.ingress.kubernetes.io/canary-weight: "20"Multiple backendRefs with weight field
Header routingnginx.ingress.kubernetes.io/canary-header: X-TestHTTPRoute matches[].headers
Rate limitingnginx.ingress.kubernetes.io/limit-rps: "10"BackendTrafficPolicy or implementation-specific Policy
CORS6 separate annotationsHTTPRoute CORS filter (GA in v1.5)
Request headersconfiguration-snippet (security risk)HTTPRoute RequestHeaderModifier filter
Response headersconfiguration-snippetHTTPRoute ResponseHeaderModifier filter
Backend TLSnginx.ingress.kubernetes.io/backend-protocol: "HTTPS"BackendTLSPolicy resource
Default backendspec.defaultBackendCatch-all HTTPRoute with path /

The key difference: Ingress features are opaque annotation strings that vary per controller. Gateway API makes them first-class typed fields with built-in validation, making configurations portable across implementations.

Choose a Gateway API Implementation

Gateway API is a specification, not a product. You need a controller that implements it. The choice depends on your current setup and requirements:

ImplementationProxyBest for
NGINX Gateway Fabric (v2.4.2)NGINXTeams already running NGINX. Smoothest migration path from ingress-nginx. Supports all GA routes plus TCP/UDP.
Envoy GatewayEnvoyTeams wanting Istio-like features without a full service mesh. Lightweight, CNCF-backed.
Istio GatewayEnvoyTeams already running or planning to adopt Istio service mesh.
Cilium Gateway APIeBPFHigh-performance environments using Cilium CNI. Kernel-level routing.
TraefikTraefikSimple setups, dynamic configuration, auto-discovery.
KongKongAPI gateway features (authentication, rate limiting, plugins).
Cloud-managed (GKE, AKS)VariesManaged Kubernetes with cloud load balancers.

For teams migrating from ingress-nginx, NGINX Gateway Fabric is the most natural choice. It uses the same proxy technology and has dedicated support in the ingress2gateway conversion tool. The rest of this guide uses NGINX Gateway Fabric, but the Gateway API resources are identical regardless of implementation.

Concrete Examples: Ingress vs Gateway API

Seeing the actual YAML makes the differences concrete. These examples show common patterns converted from Ingress to Gateway API.

Basic HTTP Routing

Ingress (one resource does everything):

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-app
  annotations:
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
  ingressClassName: nginx
  tls:
  - hosts:
    - app.example.com
    secretName: app-tls
  rules:
  - host: app.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: my-app
            port:
              number: 80

Gateway API (separated into Gateway + HTTPRoute):

apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: main-gateway
spec:
  gatewayClassName: nginx
  listeners:
  - name: https
    port: 443
    protocol: HTTPS
    tls:
      mode: Terminate
      certificateRefs:
      - kind: Secret
        name: app-tls
  - name: http
    port: 80
    protocol: HTTP
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: my-app
spec:
  parentRefs:
  - name: main-gateway
    sectionName: https
  hostnames:
  - app.example.com
  rules:
  - backendRefs:
    - name: my-app
      port: 80

The Gateway is created once by the platform team and shared across applications. Each application team creates their own HTTPRoute that attaches to the shared Gateway.

TLS Redirect (HTTP to HTTPS)

With Ingress, a single annotation handled this. With Gateway API, you create a separate HTTPRoute on the HTTP listener:

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: tls-redirect
spec:
  parentRefs:
  - name: main-gateway
    sectionName: http
  rules:
  - filters:
    - type: RequestRedirect
      requestRedirect:
        scheme: https
        port: 443

Traffic Splitting (Canary Deployments)

Where Ingress required a separate canary Ingress resource with annotations, Gateway API handles this natively with weighted backends:

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: my-app
spec:
  parentRefs:
  - name: main-gateway
  hostnames:
  - app.example.com
  rules:
  - backendRefs:
    - name: my-app-v1
      port: 80
      weight: 80
    - name: my-app-v2
      port: 80
      weight: 20

80% of traffic goes to v1, 20% to v2. Adjust the weights to gradually shift traffic during a rollout. No annotations, no separate Ingress objects.

Path Rewrite

Ingress rewrites with regex capture groups (rewrite-target: /$1) become Gateway API URL rewrite filters:

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: api-rewrite
spec:
  parentRefs:
  - name: main-gateway
  hostnames:
  - app.example.com
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /api/v1
    filters:
    - type: URLRewrite
      urlRewrite:
        path:
          type: ReplacePrefixMatch
          replacePrefixMatch: /v1
    backendRefs:
    - name: api-service
      port: 8080

Requests to /api/v1/users are rewritten to /v1/users before reaching the backend.

Convert Existing Ingress Manifests with ingress2gateway

The ingress2gateway tool (v1.0.0, released March 2026) automates the conversion of Ingress resources to Gateway API resources. It supports 30+ common ingress-nginx annotations and produces Gateway + HTTPRoute YAML.

Install it:

go install github.com/kubernetes-sigs/ingress2gateway@latest

Or download a prebuilt binary from the releases page. Homebrew is also supported:

brew install ingress2gateway

Convert all Ingress resources in the current namespace using the ingress-nginx provider:

ingress2gateway print --providers=ingress-nginx

For all namespaces:

ingress2gateway print --providers=ingress-nginx -A

The tool reads your cluster’s live Ingress resources and outputs equivalent Gateway API manifests. Save the output to a file for review:

ingress2gateway print --providers=ingress-nginx -A > gateway-resources.yaml

The NGINX-specific provider converts annotations like rewrite-target, ssl-redirect, backend-protocol, and use-regex into native Gateway API fields. Annotations without a Gateway API equivalent are listed as comments in the output so you know what needs manual attention.

Critical: always review the output before applying it. The tool handles common cases well, but complex regex rewrites, custom configuration snippets, and Lua-based plugins will need manual conversion. Start with the converted output as a baseline, then adjust.

Step-by-Step Migration Process

The safest approach runs the old and new controllers in parallel, migrates traffic gradually, and keeps rollback capability throughout.

1. Audit Your Current Ingress Resources

Before changing anything, inventory what you have:

kubectl get ingress -A -o wide

List all annotations in use (the kubectl cheat sheet covers more query patterns) to understand what features need mapping:

kubectl get ingress -A -o jsonpath='{range .items[*]}{.metadata.namespace}/{.metadata.name}: {.metadata.annotations}{"\n"}{end}'

This shows exactly which annotations each Ingress uses. Group them by complexity: simple host/path routing (easy to convert), TLS with redirect (straightforward), and custom snippets or regex rewrites (need manual work).

2. Install Gateway API CRDs

Install the Gateway API Custom Resource Definitions. Use the standard channel for production:

kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.5.0/standard-install.yaml

If you need experimental features (TCPRoute, UDPRoute, BackendTrafficPolicy), use the experimental channel instead:

kubectl apply --server-side -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.5.0/experimental-install.yaml

The --server-side flag is required for experimental CRDs because they exceed the size limit for client-side apply.

3. Install Your Gateway API Controller

For NGINX Gateway Fabric, install via Helm (the same tool used for Prometheus and Grafana on Kubernetes):

helm install ngf oci://ghcr.io/nginx/charts/nginx-gateway-fabric \
  --create-namespace -n nginx-gateway \
  --set service.type=LoadBalancer

Verify the GatewayClass was created:

kubectl get gatewayclass

You should see a GatewayClass named nginx with status Accepted: True. This confirms the controller is ready to handle Gateway resources.

4. Create the Gateway

Define a Gateway with HTTP and HTTPS listeners. This replaces the load balancer that ingress-nginx managed:

apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: main-gateway
  namespace: nginx-gateway
spec:
  gatewayClassName: nginx
  listeners:
  - name: http
    port: 80
    protocol: HTTP
    allowedRoutes:
      namespaces:
        from: All
  - name: https
    port: 443
    protocol: HTTPS
    tls:
      mode: Terminate
      certificateRefs:
      - kind: Secret
        name: wildcard-tls
    allowedRoutes:
      namespaces:
        from: All

The allowedRoutes.namespaces.from: All setting lets HTTPRoutes from any namespace attach to this Gateway. For tighter control, use from: Selector with a label match.

5. Convert and Apply HTTPRoutes

Use ingress2gateway to convert your Ingress resources, review the output, and apply:

ingress2gateway print --providers=ingress-nginx -A > gateway-routes.yaml
kubectl apply -f gateway-routes.yaml

Verify the routes are attached and accepted:

kubectl get httproute -A

Check that each route shows Accepted: True in its status. If a route shows Accepted: False, the most common causes are a hostname mismatch with the Gateway listener or a missing ReferenceGrant for cross-namespace routing.

6. Test Before Switching Traffic

Both controllers are now running in parallel. Test the Gateway API path without touching production traffic:

GATEWAY_IP=$(kubectl get svc -n nginx-gateway ngf-nginx-gateway-fabric -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
curl -H "Host: app.example.com" https://$GATEWAY_IP/healthz -k

If the response matches what you expect from the old Ingress path, the routes are configured correctly.

7. Switch DNS and Remove Ingress

Reduce your DNS TTL to 60 seconds 24 hours before the cutover. Then update DNS records to point to the Gateway’s external IP. Keep the old ingress-nginx controller running for 24-48 hours as a rollback option.

Once traffic is flowing through the Gateway API path and everything is verified, remove the old Ingress resources and uninstall ingress-nginx:

kubectl delete ingress -A --all
helm uninstall ingress-nginx -n ingress-nginx

Cross-Namespace Routing with ReferenceGrant

When an HTTPRoute in one namespace needs to reference a Gateway or Service in another namespace, you need a ReferenceGrant. Without it, the reference is silently rejected. This is a common gotcha during migration because Ingress had no concept of cross-namespace restrictions.

apiVersion: gateway.networking.k8s.io/v1
kind: ReferenceGrant
metadata:
  name: allow-routes-from-apps
  namespace: nginx-gateway
spec:
  from:
  - group: gateway.networking.k8s.io
    kind: HTTPRoute
    namespace: my-app-namespace
  to:
  - group: ""
    kind: Service

This ReferenceGrant, placed in the nginx-gateway namespace, allows HTTPRoutes from my-app-namespace to reference Services in the nginx-gateway namespace.

Migration Gotchas

These are real issues that catch teams during migration:

  • Silent route rejection: if an HTTPRoute hostname does not match any Gateway listener hostname filter, the route is silently ignored. No error, no log. Always check kubectl get httproute -o yaml for the status conditions
  • No implicit entry points: with Ingress, a single resource creates both the entry point and the routing. With Gateway API, forgetting to create the Gateway means routes have nothing to attach to
  • TLSRoute requires Kubernetes 1.31+: the v1 TLSRoute uses CEL validation that requires Kubernetes 1.31 or later. On older clusters, use the v1alpha2 version
  • No defaultBackend equivalent: Ingress had spec.defaultBackend for catch-all routing. In Gateway API, create an HTTPRoute with a path prefix of / instead
  • configuration-snippet has no replacement: if you relied on raw NGINX config injection, you will need to find a native Gateway API filter or an implementation-specific extension policy
  • RBAC updates needed: application developers now create HTTPRoute resources instead of Ingress. Update ClusterRoles and RoleBindings accordingly

ingress-nginx Retirement Timeline

DateEvent
November 2025Retirement announced. Best-effort maintenance by 1-2 volunteers.
March 2026End of all maintenance. No releases, no bugfixes, no security patches.
Post-March 2026Repositories moved to kubernetes-retired/ and made read-only.

Existing Helm charts and container images remain available. Deployments continue to function. But with no security patches, running ingress-nginx in production becomes an increasing liability over time.

What Next?

  • cert-manager integration: cert-manager v1.16+ supports Gateway API natively. Annotate your Gateway with cert-manager.io/issuer and it handles certificate provisioning automatically
  • GRPCRoute for gRPC services: if you run gRPC backends, GRPCRoute ensures proper HTTP/2 negotiation without workarounds
  • Policy attachment: implementations like NGINX Gateway Fabric and Envoy Gateway support custom policies for rate limiting, authentication, and circuit breaking via the Gateway API policy attachment model
  • Multi-cluster Gateway: the Gateway API subproject is working on multi-cluster routing, which will allow a single Gateway to distribute traffic across clusters. If you already manage multiple Kubernetes clusters, this will simplify cross-cluster traffic management
  • Official migration guide: the Gateway API migration docs have additional examples and implementation-specific guides

Related Articles

Networking Install PowerDNS and PowerDNS-Admin on Ubuntu 24.04 Books Best CCNP Enterprise Certification Books for 2026 Cloud Scale up Worker Nodes in OpenStack Magnum Kubernetes Cluster Automation Install Ambassador Ingress Controller on Kubernetes

Press ESC to close