Kubernetes Gateway API vs Ingress: Differences & Migration Guide (2026)

Gateway API vs Ingress in Kubernetes: Migration Guide (2026)

In November 2025 the Kubernetes project announced the retirement of Ingress NGINX, the community ingress controller running in a large share of clusters. Best-effort maintenance ended in March 2026, the repository was archived, and there are no further releases or security fixes. Existing deployments keep working, but nobody will patch the next vulnerability.

That makes this the right time to understand Gateway API, the upstream-backed successor to the Ingress resource. This guide explains what changed, compares the two APIs with real YAML, helps you pick a controller, and gives a migration plan using the official ingress2gateway tool.

Table of Contents

  1. What happened to Ingress NGINX
  2. Ingress vs Gateway API at a glance
  3. The Gateway API model
  4. Side-by-side YAML
  5. Choosing a Gateway API implementation
  6. Migration plan
  7. Using ingress2gateway
  8. Annotation mapping
  9. Bare-metal and homelab notes
  10. Troubleshooting
  11. FAQ

1. What happened to Ingress NGINX

DateEvent
11 Nov 2025Kubernetes SIG Network and the Security Response Committee announce the retirement of Ingress NGINX
March 2026Best-effort maintenance ends; no more releases, bug fixes, or security patches
20 Mar 2026ingress2gateway 1.0 released as the official migration tool
24 Mar 2026The kubernetes/ingress-nginx repository is archived (read-only)

Nothing breaks overnight: running controllers continue to work and the installation artifacts remain available. The risk is cumulative, because any new vulnerability stays unpatched. Plan a migration rather than waiting for an incident to force one.

Check which NGINX controller you actually run. There are two different projects:

kubectl get pods -A -o jsonpath='{range .items[*]}{.metadata.namespace}{"\t"}{.metadata.name}{"\t"}{.spec.containers[*].image}{"\n"}{end}' | grep -i nginx
Image containsProjectStatus
registry.k8s.io/ingress-nginx/controllerCommunity ingress-nginx (Kubernetes project)Retired
nginx/nginx-ingress or nginxincF5 NGINX Ingress ControllerSeparate project, not covered by this retirement

2. Ingress vs Gateway API at a glance

IngressGateway API
API groupnetworking.k8s.io/v1gateway.networking.k8s.io/v1
Core resourcesIngress, IngressClassGatewayClass, Gateway, HTTPRoute, GRPCRoute, and more
Protocols in the standard channelHTTP/HTTPSHTTP, HTTPS, gRPC, TLS; TCP and UDP routes are available in the experimental channel
Traffic splitting, header matching, redirects, rewritesController-specific annotationsBuilt into the API
Role separationOne object, mixed concernsInfra team owns Gateway; app teams own HTTPRoute
Cross-namespace routingAwkward or unsupportedFirst-class, controlled by ReferenceGrant
PortabilityAnnotations differ per controllerConformance tests across implementations
StatusFrozen (stable, no new features)Actively developed

The Ingress API is not being removed, but it is feature-frozen. New networking capabilities land in Gateway API.

3. The Gateway API model

   Infrastructure provider        Cluster operator             Application developer
   -----------------------        ----------------             ---------------------
   +-------------------+          +----------------+           +--------------------+
   |   GatewayClass    |  <------ |    Gateway     | <-------- |     HTTPRoute      |
   | (which controller)|          | (listeners:    |           | (hostnames, paths, |
   |                   |          |  ports, TLS)   |           |  headers, weights) |
   +-------------------+          +--------+-------+           +---------+----------+
                                           |                             |
                                  data plane (Envoy, NGINX, ...)         v
                                  gets a LoadBalancer / NodePort   +-----------+
                                           |                       |  Service  |
   Client ---> cloud LB / MetalLB ---------+---------------------> |  + Pods   |
                                                                   +-----------+
ResourceOwned byPurpose
GatewayClassInfrastructure providerNames the controller implementation
GatewayCluster operatorDefines listeners (ports, protocols, TLS certificates) and who may attach routes
HTTPRoute / GRPCRouteApplication teamMaps hosts, paths, and headers to backend Services
ReferenceGrantTarget namespace ownerPermits cross-namespace references (for example, a route using a Service or Secret in another namespace)

4. Side-by-side YAML

Today, with Ingress and annotations:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: shop
  namespace: shop
  annotations:
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    nginx.ingress.kubernetes.io/canary: "true"           # needs a second Ingress object
    nginx.ingress.kubernetes.io/canary-weight: "10"
spec:
  ingressClassName: nginx
  tls:
    - hosts: [shop.example.com]
      secretName: shop-tls
  rules:
    - host: shop.example.com
      http:
        paths:
          - path: /api
            pathType: Prefix
            backend:
              service:
                name: shop-api
                port:
                  number: 80

With Gateway API:

# 1. Owned by the platform team
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
  name: envoy
spec:
  controllerName: gateway.envoyproxy.io/gatewayclass-controller
---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: public-gw
  namespace: infra
spec:
  gatewayClassName: envoy
  listeners:
    - name: http
      protocol: HTTP
      port: 80
      allowedRoutes:
        namespaces:
          from: All
    - name: https
      protocol: HTTPS
      port: 443
      hostname: "shop.example.com"
      tls:
        mode: Terminate
        certificateRefs:
          - kind: Secret
            name: shop-tls
      allowedRoutes:
        namespaces:
          from: All
# 2. Owned by the application team: redirect HTTP to HTTPS
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: shop-redirect
  namespace: shop
spec:
  parentRefs:
    - name: public-gw
      namespace: infra
      sectionName: http
  hostnames: ["shop.example.com"]
  rules:
    - filters:
        - type: RequestRedirect
          requestRedirect:
            scheme: https
            statusCode: 301
---
# 3. Routing with a built-in 90/10 canary split
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: shop-api
  namespace: shop
spec:
  parentRefs:
    - name: public-gw
      namespace: infra
      sectionName: https
  hostnames: ["shop.example.com"]
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /api
      backendRefs:
        - name: shop-api-v1
          port: 80
          weight: 90
        - name: shop-api-v2
          port: 80
          weight: 10

Notice what disappeared: no vendor annotations for the redirect, and the canary is two weighted backendRefs on one route rather than a second Ingress object with magic annotations.

The TLS Secret shop-tls in this example lives in the same namespace as the Gateway (infra). If it lives elsewhere, the Secret’s namespace must allow it with a ReferenceGrant:

apiVersion: gateway.networking.k8s.io/v1beta1
kind: ReferenceGrant
metadata:
  name: allow-gateway-to-read-certs
  namespace: shop                      # namespace that owns the Secret
spec:
  from:
    - group: gateway.networking.k8s.io
      kind: Gateway
      namespace: infra
  to:
    - group: ""
      kind: Secret

5. Choosing a Gateway API implementation

Gateway API itself is only the specification and CRDs. You still need a controller that implements it.

ImplementationData planeGood fitNotes
Envoy GatewayEnvoyDirect replacement for a shared cluster edge; rich policy extensionsCNCF-hosted, popular for teams leaving Ingress NGINX
NGINX Gateway FabricNGINXTeams that want to stay on NGINX semanticsFrom F5/NGINX, implements Gateway API
TraefikTraefikExisting Traefik users; can also read NGINX Ingress annotations as a stop-gapUseful for phased migrations
CiliumeBPF + EnvoyClusters already using Cilium CNIIntegrated with the network layer
IstioEnvoyService mesh usersGateway API is Istio’s recommended API
Cloud load balancer controllersManaged LBEKS, GKE, AKSCheck provider support for your features

Check each project’s conformance page for the Gateway API version and features it supports. Vendor-specific extras (rate limiting, external auth, timeouts) are exposed through policy resources that are not portable between implementations.

6. Migration plan

  1. Inventory  --->  2. Install CRDs   --->  3. Deploy new      --->  4. Convert
  kubectl get          + chosen controller     Gateway beside          with ingress2gateway
  ingress -A                                   old Ingress                   |
                                                                             v
  7. Retire  <---  6. Cut over DNS /   <---  5. Test with Host header,  review output
  ingress-nginx    LB gradually              staging hostnames          by hand
  1. Inventory. List every Ingress and its annotations.kubectl get ingress -A kubectl get ingress -A -o yaml | grep 'nginx.ingress.kubernetes.io' | sort | uniq -c | sort -rnThe annotation counts show how much custom behavior you must re-create.
  2. Install the Gateway API CRDs and a controller. Use the standard channel unless you need experimental routes.# Check the latest release at github.com/kubernetes-sigs/gateway-api/releases GATEWAY_API_VERSION=v1.6.0 kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/${GATEWAY_API_VERSION}/standard-install.yaml kubectl get crd | grep gateway.networking.k8s.ioThen install your chosen controller, typically with Helm. See Managing Kubernetes Applications with Helm Charts.
  3. Run in parallel. Deploy the new Gateway with its own load balancer address next to the old controller. Do not touch production DNS yet.
  4. Convert with ingress2gateway and review every generated manifest.
  5. Test using a Host header against the new address:curl -s -H "Host: shop.example.com" http://<new-gateway-ip>/api curl -sk --resolve shop.example.com:443:<new-gateway-ip> https://shop.example.com/api -I
  6. Cut over gradually. Lower DNS TTLs in advance, move a small share of traffic first (weighted DNS or load balancer rules), watch error rates, then shift the rest.
  7. Retire the old controller only after a full business cycle without incident. Keep manifests in Git so you can roll back. A GitOps workflow helps; see Argo CD on Kubernetes.

7. Using ingress2gateway

ingress2gateway reads existing Ingress resources and prints Gateway API equivalents. Version 1.0 arrived on 20 March 2026 and widened support for Ingress NGINX annotations considerably compared with the early 0.x releases.

# Install: download the binary from the project's GitHub releases page,
# or use your package manager (for example: brew install ingress2gateway)
chmod +x ingress2gateway
sudo mv ingress2gateway /usr/local/bin/

# Convert everything in the cluster (dry run: output only)
ingress2gateway print --providers=ingress-nginx --all-namespaces

# One namespace
ingress2gateway print --providers=ingress-nginx --namespace shop

# From a file, saved for review
ingress2gateway print --providers=ingress-nginx --input-file=legacy-ingress.yaml -o yaml > converted.yaml

Use --providers=ingress-nginx for the retired community controller. The tool also has a provider for F5’s separate NGINX controller; picking the wrong one produces misleading output.

The tool is a starting point, not an autopilot. It cannot translate everything, and it flags or skips annotations that have no Gateway API equivalent. Always read the generated routes, check parentRefs, hostnames, and filters, and add the policy resources your chosen controller needs.

8. Annotation mapping

Ingress NGINX annotation (concept)Gateway API equivalent
ssl-redirect / force-ssl-redirectRequestRedirect filter (scheme https) on an HTTP listener route
rewrite-targetURLRewrite filter
Canary weight (canary, canary-weight)Weighted backendRefs in one HTTPRoute
Canary by header or cookieHeader matches on separate rules
proxy-set-headers, response header changesRequestHeaderModifier / ResponseHeaderModifier filters
enable-corsCORS filter where the implementation supports it, otherwise a vendor policy
Timeouts, body size, bufferingVendor-specific policy resources (not portable)
Rate limiting (limit-rps, limit-connections)Vendor-specific policy; check your controller
External auth (auth-url, auth-signin)Vendor-specific policy or a different mechanism
Basic authVendor-specific policy
configuration-snippet, server-snippetNo equivalent. Re-express the behavior with standard filters or policies, or rethink the requirement
TCP/UDP services ConfigMapTCPRoute / UDPRoute (experimental channel), if your controller supports them

Roughly speaking, routing logic maps well; operational tuning (timeouts, limits, auth) lands in implementation-specific policies, so budget time for it.

9. Bare-metal and homelab notes

On a kubeadm cluster such as the one in our Kubernetes cluster on Rocky Linux 10 guide, the Gateway’s data-plane Service of type LoadBalancer stays in <pending> unless something assigns an address. Use MetalLB (the same fix described in our NGINX Ingress guide for Rocky Linux 10), or expose the proxy with hostNetwork/NodePort and put your own load balancer in front.

On Rocky Linux nodes, open the listener ports in firewalld on the nodes that receive traffic, and keep SELinux enforcing. Review the hardening advice in Kubernetes Security Best Practices (2026 Edition) when exposing a new edge proxy, including NetworkPolicies and TLS Secret handling. For managing certificates and Secrets at scale, see Kubernetes Secrets Management.

10. Troubleshooting

SymptomLikely causeFix
no matches for kind "HTTPRoute"Gateway API CRDs not installedApply the standard-install manifest
Gateway PROGRAMMED: False or no addressController not running, wrong gatewayClassName, or no LoadBalancer providerkubectl describe gateway; check the controller pods; install MetalLB on bare metal
Route status Accepted: FalseparentRefs name/namespace/sectionName wrong, or allowedRoutes blocks the namespacekubectl describe httproute; fix parentRefs or listener allowedRoutes
Route status ResolvedRefs: FalseBackend Service missing, wrong port, or cross-namespace without ReferenceGrantCheck the Service name and port; add a ReferenceGrant
TLS handshake fails or default certificate servedSecret missing, wrong namespace, or hostname mismatch on the listenerVerify the Secret type is kubernetes.io/tls and the listener hostname
404 on every requestNo route matches host or pathCompare hostnames and matches; test with the correct Host header
Converted route behaves differently from the old IngressAnnotation had no equivalentReview the tool’s warnings; add a policy resource or change the app
Works from inside the cluster, not outsideFirewall, NodePort, or load balancer pathCheck firewalld on nodes, MetalLB pool, and security groups
HTTP to HTTPS redirect loopsRedirect route attached to the HTTPS listenerAttach the redirect route to the HTTP listener only (sectionName: http)
Wrong provider flag outputUsed the F5 provider for community ingress-nginxUse --providers=ingress-nginx

Useful commands:

kubectl get gatewayclass,gateway,httproute -A
kubectl describe gateway -n infra public-gw
kubectl describe httproute -n shop shop-api

11. FAQ

Is the Ingress API deprecated?
It is not being removed, but it is frozen. New capabilities go to Gateway API, and the retirement of the most popular controller pushes most teams toward the new API.

Do I have to migrate to Gateway API, or can I just switch to another Ingress controller?
You can swap to another Ingress controller and keep your manifests, which buys time. Gateway API is the longer-term direction, and moving once is cheaper than moving twice.

Which controller is the easiest replacement for Ingress NGINX?
Envoy Gateway is a common choice for a shared cluster edge. NGINX Gateway Fabric suits teams that prefer NGINX, and Traefik can ease the transition by reading NGINX annotations. Evaluate feature coverage (auth, rate limiting, timeouts) before deciding.

Does ingress2gateway migrate everything automatically?
No. It converts routing and many common annotations, but you must review the output and re-create unsupported behavior, such as snippets, with standard filters or vendor policies.

What is the difference between a Gateway and an HTTPRoute?
A Gateway defines listeners (ports, protocols, TLS) and is owned by the platform team. An HTTPRoute defines how requests for specific hosts and paths reach Services and is owned by application teams.

Can Gateway API handle TCP and gRPC?
gRPC has its own GRPCRoute. TCP and UDP routes exist in the experimental channel; confirm that your controller supports them before relying on them.

How do I do canary releases?
Use weighted backendRefs in an HTTPRoute, as in the example above, and shift the weights gradually.

(Visited 3 times, 3 visits today)

You may also like