The Ingress API served Kubernetes well for years, but its limitations — reliance on unportable annotations, no standard for traffic splitting, header matching, or delegation — led the community to build its successor: the Gateway API. It's now the officially recommended way to handle ingress traffic in Kubernetes.

The Kubernetes project has officially designated Gateway API as the successor to Ingress. The ingress-nginx controller project has announced deprecation, with the NGINX Gateway Fabric (implementing Gateway API) as its replacement. New projects should use Gateway API. Existing Ingress setups continue to work but won't receive new features.

1. Why Gateway API Replaces Ingress

Ingress LimitationGateway API Solution
Features via annotations (not portable)First-class typed fields in the spec
No role separation (one resource, one owner)Layered resources: infra team owns Gateway, app teams own Routes
HTTP onlyHTTP, gRPC, TCP, UDP, TLS passthrough — all supported
No traffic splitting standardBuilt-in weighted backends for canary/blue-green
No header/query matchingRich matching: headers, query params, methods
No request/response manipulationBuilt-in filters: rewrite, redirect, add headers, mirror
Single implementation per IngressMultiple Gateways, different implementations in same cluster

The Resource Model

GatewayClass Defines the controller implementation Managed by: cluster admin / vendor Gateway Listeners (ports, TLS, hostnames) Managed by: platform / infra team HTTPRoute Managed by: app team A HTTPRoute Managed by: app team B "What kind of proxy?" "Where to listen?" "How to route?" Role Separation (Key Innovation) Infrastructure: GatewayClass + Gateway (ports, certs, IP) → Platform team Application: HTTPRoute (paths, backends, traffic rules) → App developers No one needs cluster-admin to add a route. Self-service with guardrails.
The three-layer model enables self-service: The platform team sets up GatewayClass + Gateway (TLS, listeners, allowed namespaces). App teams create HTTPRoutes in their own namespaces that attach to the Gateway. No annotations to learn, no cluster-admin needed for routing changes. The Gateway controls what's allowed via allowedRoutes.

2. GatewayClass & Gateway

GatewayClass — "What implementation?"

# Installed by the controller vendor (like IngressClass):
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
  name: nginx                      # or: cilium, envoy, istio, etc.
spec:
  controllerName: gateway.nginx.org/nginx-gateway-controller

Common GatewayClass controllers:

ImplementationcontrollerNameNotes
NGINX Gateway Fabricgateway.nginx.org/nginx-gateway-controllerOfficial successor to ingress-nginx
Ciliumio.cilium/gateway-controllereBPF-based, high performance
Istioistio.io/gateway-controllerService mesh integration
Envoy Gatewaygateway.envoyproxy.io/gatewayclass-controllerStandalone Envoy for Gateway API
Traefiktraefik.io/gateway-controllerTraefik v3+
AWS Gateway API Controllerapplication-networking.k8s.aws/gateway-api-controllerMaps to AWS VPC Lattice
GKEnetworking.gke.io/gatewayNative GCP load balancers

Gateway — "Where to listen?"

apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: production-gateway
  namespace: infra
spec:
  gatewayClassName: nginx             # Reference to GatewayClass
  listeners:
    - name: http
      protocol: HTTP
      port: 80
      allowedRoutes:
        namespaces:
          from: All                   # Any namespace can attach routes
    - name: https
      protocol: HTTPS
      port: 443
      tls:
        mode: Terminate
        certificateRefs:
          - name: wildcard-tls        # TLS Secret
            namespace: infra
      allowedRoutes:
        namespaces:
          from: Selector
          selector:
            matchLabels:
              gateway-access: "true"  # Only labeled namespaces can use HTTPS

allowedRoutes — The Guardrail

SettingMeaning
from: AllAny namespace can attach Routes to this listener
from: SameOnly Routes in the same namespace as the Gateway
from: SelectorOnly namespaces matching the label selector
The allowedRoutes field is how platform teams implement multi-tenancy for ingress. The Gateway is in a shared infra namespace; app teams attach HTTPRoutes from their own namespaces. The Gateway controls which namespaces are allowed — preventing unauthorized teams from hijacking traffic.

3. HTTPRoute — "How to route?"

Basic Routing

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: web-routes
  namespace: app-team              # App team's namespace
spec:
  parentRefs:
    - name: production-gateway     # Attach to which Gateway
      namespace: infra
  hostnames:
    - "app.example.com"
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /api
      backendRefs:
        - name: api-svc
          port: 80
    - matches:
        - path:
            type: PathPrefix
            value: /
      backendRefs:
        - name: web-svc
          port: 80

Rich Matching (Beyond Ingress Capabilities)

rules:
  - matches:
      - path:
          type: PathPrefix
          value: /api
        headers:                       # ← Match by HTTP header
          - name: X-Version
            value: "beta"
        method: GET                     # ← Match by HTTP method
        queryParams:                    # ← Match by query parameter
          - name: debug
            value: "true"
    backendRefs:
      - name: api-beta-svc
        port: 80

Traffic Splitting (Canary/Blue-Green)

rules:
  - matches:
      - path:
          type: PathPrefix
          value: /
    backendRefs:
      - name: web-v1                   # 90% of traffic
        port: 80
        weight: 90
      - name: web-v2                   # 10% canary
        port: 80
        weight: 10
Traffic splitting is a first-class feature. In Ingress, you needed controller-specific annotations (e.g., nginx.ingress.kubernetes.io/canary-weight: "10") that only some controllers supported. In Gateway API, weighted backends are standard — every conformant implementation must support them.

Filters — Request/Response Manipulation

rules:
  - matches:
      - path:
          type: PathPrefix
          value: /old-path
    filters:
      - type: RequestRedirect          # 301/302 redirect
        requestRedirect:
          scheme: https
          hostname: new.example.com
          statusCode: 301
  - matches:
      - path:
          type: PathPrefix
          value: /api/v1
    filters:
      - type: URLRewrite               # Rewrite before forwarding
        urlRewrite:
          path:
            type: ReplacePrefixMatch
            replacePrefixMatch: /v1
      - type: RequestHeaderModifier    # Add/set/remove headers
        requestHeaderModifier:
          add:
            - name: X-Forwarded-By
              value: gateway
          remove:
            - X-Internal-Only
    backendRefs:
      - name: api-svc
        port: 80

Available Filter Types

FilterPurposeExample
RequestHeaderModifierAdd/set/remove request headersAdd auth headers, strip internal headers
ResponseHeaderModifierAdd/set/remove response headersAdd CORS headers, security headers
RequestRedirectHTTP redirect (301/302)HTTP→HTTPS, domain migration
URLRewriteRewrite path/host before forwardingStrip prefix, change hostname
RequestMirrorCopy traffic to another backend (no response)Shadow testing new versions
ExtensionRefController-specific custom filtersRate limiting, auth (via CRDs)

4. Beyond HTTP — Other Route Types

Route TypeProtocolUse CaseStatus
HTTPRouteHTTP/HTTPSWeb apps, APIsGA (v1)
GRPCRoutegRPCgRPC services with method-level routingGA (v1)
TLSRouteTLS (passthrough)Pass encrypted traffic without terminationExperimental
TCPRouteRaw TCPDatabases, Redis, custom protocolsExperimental
UDPRouteRaw UDPDNS, game serversExperimental
# GRPCRoute example — route by gRPC service/method:
apiVersion: gateway.networking.k8s.io/v1
kind: GRPCRoute
metadata:
  name: grpc-routes
spec:
  parentRefs:
    - name: production-gateway
  rules:
    - matches:
        - method:
            service: myapp.UserService
            method: GetUser
      backendRefs:
        - name: user-svc
          port: 50051

5. Migrating from Ingress to Gateway API

Comparison: Same routing, different specs

# OLD — Ingress:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: web
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  ingressClassName: nginx
  rules:
    - host: app.example.com
      http:
        paths:
          - path: /api
            pathType: Prefix
            backend:
              service:
                name: api-svc
                port:
                  number: 80
# NEW — Gateway API:
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: web
spec:
  parentRefs:
    - name: production-gateway
  hostnames: ["app.example.com"]
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /api
      filters:
        - type: URLRewrite
          urlRewrite:
            path:
              type: ReplacePrefixMatch
              replacePrefixMatch: /
      backendRefs:
        - name: api-svc
          port: 80

Migration Strategy

  1. Install a Gateway API controller (e.g., NGINX Gateway Fabric, Cilium, Envoy Gateway)
  2. Create GatewayClass + Gateway (platform team)
  3. Run both in parallel — Ingress and Gateway API can coexist
  4. Migrate routes one by one — convert Ingress → HTTPRoute
  5. Validate traffic — confirm identical behavior
  6. Remove old Ingress resources and controller
Tools like ingress2gateway (from the Gateway API project) can auto-convert Ingress resources to HTTPRoutes. It handles common patterns but may need manual adjustment for complex annotations. Run both systems in parallel during migration — don't big-bang switch.

6. NGINX Gateway Fabric — The Official Successor

NGINX Gateway Fabric is the official replacement for ingress-nginx. It implements the Gateway API spec using NGINX as the data plane.

# Installation:
kubectl apply -f https://github.com/nginxinc/nginx-gateway-fabric/releases/latest/download/crds.yaml
kubectl apply -f https://github.com/nginxinc/nginx-gateway-fabric/releases/latest/download/nginx-gateway.yaml

# Creates GatewayClass "nginx" automatically
Featureingress-nginx (deprecated)NGINX Gateway Fabric
APIIngress + annotationsGateway API (typed resources)
Traffic splittingCanary annotation (limited)Native weighted backends
Header matchingCustom snippets (unsafe)First-class matches.headers
URL rewriteAnnotation + regexTyped URLRewrite filter
Role separationNone (one Ingress per route)Gateway (infra) + HTTPRoute (app)
Multi-tenancyWeak (namespace annotations)Strong (allowedRoutes with selectors)
The ecosystem is converging on Gateway API. Cilium, Istio, Envoy Gateway, Traefik, NGINX, AWS, GCP — all implement it. Learning Gateway API means your knowledge is portable across controllers. Annotations are dead; typed specs are the future.

Summary

ConceptKey Point
Gateway APIOfficial successor to Ingress — richer, portable, role-based
GatewayClass"What controller?" — installed by vendor (like IngressClass)
Gateway"Where to listen?" — ports, TLS, allowed namespaces (platform team)
HTTPRoute"How to route?" — path, headers, methods, backends (app team)
Traffic splittingWeighted backendRefs — built-in canary/blue-green
FiltersRedirect, rewrite, header modification, mirror — no annotations
Role separationInfra owns Gateway, apps own Routes — self-service with guardrails
NGINX Gateway FabricOfficial replacement for ingress-nginx
MigrationRun in parallel, use ingress2gateway tool, migrate incrementally

📝 Quiz: Gateway API

Q1: Who creates the Gateway and who creates the HTTPRoute in a typical multi-team setup?

Platform/infra team creates the GatewayClass + Gateway (defines ports, TLS, which namespaces can attach). App teams create HTTPRoutes in their own namespaces and attach them to the Gateway via parentRefs. This separation means app teams don't need cluster-admin access to manage routing.

Q2: You want to send 5% of traffic to a canary deployment. How do you do this in Gateway API?

Use weighted backendRefs in the HTTPRoute:
backendRefs:
  - name: web-stable
    port: 80
    weight: 95
  - name: web-canary
    port: 80
    weight: 5
No annotations needed — this is a standard, portable feature that all conformant implementations must support.

Q3: How does a Gateway control which namespaces can attach HTTPRoutes to it?

Via listeners[].allowedRoutes.namespaces. Options:
from: All — any namespace
from: Same — only the Gateway's namespace
from: Selector — only namespaces with matching labels
This prevents unauthorized teams from routing traffic through the Gateway.

Q4: What can Gateway API HTTPRoute do that Ingress cannot (without annotations)?

Without annotations, Ingress can only do host + path routing and TLS. Gateway API natively supports:
Header matching
Query parameter matching
HTTP method matching
Weighted traffic splitting
URL rewriting
Request/response header modification
Traffic mirroring
Redirects

Q5: Can Ingress and Gateway API coexist in the same cluster during migration?

Yes. They use completely separate CRDs and can run simultaneously. You can even have both an Ingress controller and a Gateway API controller running at the same time. Migrate routes one by one: create an HTTPRoute, validate traffic, then delete the corresponding Ingress. No big-bang cutover needed.

Q6: What replaces ingress-nginx for new projects?

NGINX Gateway Fabric — the official successor from the NGINX/F5 team. It implements the Gateway API standard using NGINX as the data plane. Alternatives: Envoy Gateway (standalone Envoy), Cilium (eBPF-based), Istio (if you want service mesh), or cloud-native options (AWS Gateway API Controller, GKE Gateway).