Kubernetes
13 min readMay 2, 2026

Kubernetes Gateway API: The Modern Replacement for Ingress

The Kubernetes Gateway API (GA in 1.0, stable in Kubernetes 1.28) replaces Ingress with a role-oriented, expressive API for HTTP, HTTPS, TCP, and gRPC routing. HTTPRoute separates infrastructure configuration (Gateway, managed by platform teams) from application routing (HTTPRoute, owned by application teams) — the problem Ingress never solved with its provider-specific annotation soup.

AJ
Ajeet Yadav
Platform & Cloud Engineer
Kubernetes Gateway API: The Modern Replacement for Ingress

Ingress has two problems that annotations never solved: it's not expressive enough (path rewrites, header manipulation, traffic splitting all require provider-specific annotations), and it conflates infrastructure concerns (what load balancer to use, TLS certificate management) with application concerns (which service gets which path). The result: every team needs to understand both layers, and platform teams can't standardize without locking to a specific ingress controller.

Gateway API separates these concerns with three resource types across two personas:

  • Infrastructure provider / platform team: GatewayClass (cluster-scoped, defines the controller), Gateway (namespace-scoped, defines listener/TLS config)
  • Application team: HTTPRoute (namespace-scoped, defines routing rules — attached to a Gateway)

Installing Gateway API CRDs

Gateway API is not bundled with Kubernetes — install the CRDs separately:

bash
# Standard channel (recommended for production — includes GA resources)
kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.2.1/standard-install.yaml

# Experimental channel (includes TCPRoute, TLSRoute, UDPRoute — not GA; GRPCRoute is in the standard channel)
kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.2.1/experimental-install.yaml

Check what's included:

bash
1kubectl get crd | grep gateway.networking.k8s.io
2# gatewayclasses.gateway.networking.k8s.io
3# gateways.gateway.networking.k8s.io
4# httproutes.gateway.networking.k8s.io
5# grpcroutes.gateway.networking.k8s.io         (GA since v1.1 — standard channel)
6# referencegrants.gateway.networking.k8s.io
7# tcproutes.gateway.networking.k8s.io           (experimental channel only)

Controller: Envoy Gateway

Envoy Gateway (CNCF project, envoyproxy/gateway) is the reference implementation of Gateway API. It's operationally simpler than full Istio if you only need north-south traffic management:

bash
helm install eg oci://docker.io/envoyproxy/gateway-helm \
  --version v1.3.0 \
  --namespace envoy-gateway-system \
  --create-namespace

The install creates a GatewayClass automatically:

bash
kubectl get gatewayclass
# NAME            CONTROLLER                        ACCEPTED
# eg              gateway.envoyproxy.io/gatewaycontroller   True

GatewayClass and Gateway

The platform team creates the Gateway — this is the load balancer:

yaml
1# platform team creates this in the infra namespace
2apiVersion: gateway.networking.k8s.io/v1
3kind: Gateway
4metadata:
5  name: prod-gateway
6  namespace: infra
7  annotations:
8    cert-manager.io/cluster-issuer: "letsencrypt-prod"
9spec:
10  gatewayClassName: eg    # References the GatewayClass installed by Envoy Gateway
11  listeners:
12    - name: http
13      port: 80
14      protocol: HTTP
15      allowedRoutes:
16        namespaces:
17          from: All    # Allow HTTPRoutes from any namespace to attach
18
19    - name: https
20      port: 443
21      protocol: HTTPS
22      tls:
23        mode: Terminate
24        certificateRefs:
25          - kind: Secret
26            name: wildcard-tls
27            namespace: infra
28      allowedRoutes:
29        namespaces:
30          from: Selector
31          selector:
32            matchLabels:
33              gateway.networking.k8s.io/allowed: "true"

allowedRoutes controls which namespaces can attach routes to this listener — this is the access control model that Ingress lacked.


HTTPRoute

Application teams create HTTPRoute resources in their own namespaces:

yaml
1# payments team creates this in their namespace
2apiVersion: gateway.networking.k8s.io/v1
3kind: HTTPRoute
4metadata:
5  name: payments-api
6  namespace: payments
7spec:
8  parentRefs:
9    - name: prod-gateway
10      namespace: infra
11      sectionName: https    # Attach to the HTTPS listener specifically
12
13  hostnames:
14    - "api.codingprotocols.com"
15
16  rules:
17    - matches:
18        - path:
19            type: PathPrefix
20            value: /payments
21
22      # Header-based filtering
23      filters:
24        - type: RequestHeaderModifier
25          requestHeaderModifier:
26            add:
27              - name: X-Team
28                value: payments
29            remove:
30              - X-Internal-Token
31
32      backendRefs:
33        - name: payments-api
34          port: 8080
35          weight: 100
36
37    - matches:
38        - path:
39            type: Exact
40            value: /payments/health
41
42      backendRefs:
43        - name: payments-api
44          port: 8080

Traffic Splitting

Canary deployments without Argo Rollouts — native traffic splitting:

yaml
1apiVersion: gateway.networking.k8s.io/v1
2kind: HTTPRoute
3metadata:
4  name: payments-canary
5  namespace: payments
6spec:
7  parentRefs:
8    - name: prod-gateway
9      namespace: infra
10
11  hostnames:
12    - "api.codingprotocols.com"
13
14  rules:
15    - matches:
16        - path:
17            type: PathPrefix
18            value: /payments
19
20      backendRefs:
21        - name: payments-api-stable
22          port: 8080
23          weight: 90
24        - name: payments-api-canary
25          port: 8080
26          weight: 10    # 10% of traffic to canary

URL Rewriting

yaml
1rules:
2  - matches:
3      - path:
4          type: PathPrefix
5          value: /api/v1
6
7    filters:
8      - type: URLRewrite
9        urlRewrite:
10          path:
11            type: ReplacePrefixMatch
12            replacePrefixMatch: /v1    # /api/v1/users → /v1/users
13
14    backendRefs:
15      - name: api-service
16        port: 8080

Header-Based Routing

yaml
1rules:
2  - matches:
3      - headers:
4          - type: Exact
5            name: X-Beta-User
6            value: "true"
7
8    backendRefs:
9      - name: api-service-beta
10        port: 8080
11
12  - backendRefs:    # Default (no match header)
13      - name: api-service-stable
14        port: 8080

ReferenceGrant: Cross-Namespace Backends

By default, HTTPRoute can only reference backends (Services) in the same namespace. ReferenceGrant allows cross-namespace references — the target namespace grants permission:

yaml
1# Created in the 'payments' namespace (the target)
2# Grants permission for HTTPRoutes in 'infra' to reference Services here
3apiVersion: gateway.networking.k8s.io/v1beta1
4kind: ReferenceGrant
5metadata:
6  name: allow-infra-routes
7  namespace: payments
8spec:
9  from:
10    - group: gateway.networking.k8s.io
11      kind: HTTPRoute
12      namespace: infra    # Routes in this namespace can reference...
13  to:
14    - group: ""
15      kind: Service       # ...Services in the 'payments' namespace

This is especially useful when the platform team owns a shared gateway in infra and application namespaces host the backends.


GRPCRoute

GRPCRoute (GA in standard channel since Gateway API v1.1, included in the standard install) provides native gRPC routing with method-level granularity:

yaml
1apiVersion: gateway.networking.k8s.io/v1
2kind: GRPCRoute
3metadata:
4  name: grpc-payments
5  namespace: payments
6spec:
7  parentRefs:
8    - name: prod-gateway
9      namespace: infra
10
11  hostnames:
12    - "grpc.codingprotocols.com"
13
14  rules:
15    - matches:
16        - method:
17            service: payments.v1.PaymentsService
18            method: ProcessPayment     # Route specific gRPC method
19
20      backendRefs:
21        - name: payments-grpc-service
22          port: 9090
23
24    - matches:
25        - method:
26            service: payments.v1.PaymentsService
27            # No method specified = matches all methods in service
28
29      backendRefs:
30        - name: payments-grpc-service
31          port: 9090

AWS ALB Integration

The AWS Load Balancer Controller (v2.8+) implements Gateway API with ALB as the backing load balancer:

bash
1# LBC must be installed first (see aws-load-balancer-controller-eks post)
2# Enable Gateway API support (disabled by default in LBC)
3helm upgrade aws-load-balancer-controller eks/aws-load-balancer-controller \
4  --namespace kube-system \
5  --set clusterName=my-cluster \
6  --set serviceAccount.create=false \
7  --set serviceAccount.name=aws-load-balancer-controller \
8  --set enableGatewayAPI=true
yaml
1# GatewayClass for ALB
2apiVersion: gateway.networking.k8s.io/v1
3kind: GatewayClass
4metadata:
5  name: alb
6spec:
7  controllerName: ingress.k8s.aws/alb
8
9---
10# Gateway creates an ALB
11apiVersion: gateway.networking.k8s.io/v1
12kind: Gateway
13metadata:
14  name: prod-alb
15  namespace: infra
16  annotations:
17    alb.ingress.kubernetes.io/scheme: internet-facing
18    alb.ingress.kubernetes.io/target-type: ip
19spec:
20  gatewayClassName: alb
21  listeners:
22    - name: https
23      port: 443
24      protocol: HTTPS
25      tls:
26        mode: Terminate
27        certificateRefs:
28          - kind: Secret
29            name: alb-tls
30            namespace: infra
31      allowedRoutes:
32        namespaces:
33          from: All

HTTP → HTTPS Redirect

A permanent redirect from HTTP to HTTPS is a standalone HTTPRoute attached to the HTTP listener — no annotations needed:

yaml
1apiVersion: gateway.networking.k8s.io/v1
2kind: HTTPRoute
3metadata:
4  name: http-to-https-redirect
5  namespace: infra
6spec:
7  parentRefs:
8    - name: prod-gateway
9      namespace: infra
10      sectionName: http    # Attaches to the HTTP:80 listener only
11
12  hostnames:
13    - api.codingprotocols.com
14
15  rules:
16    - filters:
17        - type: RequestRedirect
18          requestRedirect:
19            scheme: https
20            statusCode: 301

This replaces the nginx.ingress.kubernetes.io/ssl-redirect: "true" annotation — same behavior, portable across any conformant Gateway API implementation.


Namespace Isolation: Controlling Who Can Attach

The Gateway's allowedRoutes field governs which namespaces can attach HTTPRoute resources to a listener. This is the RBAC-complementary access control that Ingress never had:

yaml
1# Restrict: only allow routes from namespaces labeled gateway-access: "true"
2listeners:
3  - name: https
4    protocol: HTTPS
5    port: 443
6    tls:
7      mode: Terminate
8      certificateRefs:
9        - kind: Secret
10          name: wildcard-tls
11          namespace: infra
12    allowedRoutes:
13      namespaces:
14        from: Selector
15        selector:
16          matchLabels:
17            gateway-access: "true"    # Only namespaces with this label can attach

Label the namespaces you want to grant access:

bash
kubectl label namespace payments gateway-access=true
kubectl label namespace orders gateway-access=true

Namespaces without the label cannot attach HTTPRoute resources to this listener — their routes will show Accepted: False in status. Verify:

bash
kubectl get httproute -n payments -o jsonpath='{.items[*].status.parents[*].conditions[?(@.type=="Accepted")].status}'

Migrating from Ingress

Side-by-side comparison — same routing expressed in both APIs:

Ingress (before):

yaml
1apiVersion: networking.k8s.io/v1
2kind: Ingress
3metadata:
4  name: payments-ingress
5  annotations:
6    nginx.ingress.kubernetes.io/rewrite-target: /v1/$2
7    nginx.ingress.kubernetes.io/use-regex: "true"
8    nginx.ingress.kubernetes.io/proxy-read-timeout: "30"
9spec:
10  ingressClassName: nginx
11  tls:
12    - hosts: [api.codingprotocols.com]
13      secretName: api-tls
14  rules:
15    - host: api.codingprotocols.com
16      http:
17        paths:
18          - path: /api/v1(/|$)(.*)
19            pathType: ImplementationSpecific
20            backend:
21              service:
22                name: payments-api
23                port:
24                  number: 8080

Gateway API (after):

yaml
1# Gateway (platform team, reused across services)
2apiVersion: gateway.networking.k8s.io/v1
3kind: Gateway
4metadata:
5  name: prod-gateway
6  namespace: infra
7spec:
8  gatewayClassName: eg
9  listeners:
10    - name: https
11      port: 443
12      protocol: HTTPS
13      tls:
14        mode: Terminate
15        certificateRefs:
16          - kind: Secret
17            name: api-tls
18            namespace: infra
19      allowedRoutes:
20        namespaces:
21          from: All
22
23---
24# HTTPRoute (application team, own namespace)
25apiVersion: gateway.networking.k8s.io/v1
26kind: HTTPRoute
27metadata:
28  name: payments-route
29  namespace: payments
30spec:
31  parentRefs:
32    - name: prod-gateway
33      namespace: infra
34  hostnames:
35    - "api.codingprotocols.com"
36  rules:
37    - matches:
38        - path:
39            type: PathPrefix
40            value: /api/v1
41      filters:
42        - type: URLRewrite
43          urlRewrite:
44            path:
45              type: ReplacePrefixMatch
46              replacePrefixMatch: /v1
47      backendRefs:
48        - name: payments-api
49          port: 8080

The annotation nginx.ingress.kubernetes.io/rewrite-target: /v1/$2 becomes the explicit URLRewrite filter — portable across any Gateway API implementation, no annotation memorization needed.


Checking Route Status

bash
1# Check if the route was accepted
2kubectl get httproute payments-api -n payments -o yaml | grep -A 20 status:
3
4# The status shows which parents (Gateways) accepted the route
5# status:
6#   parents:
7#     - conditions:
8#         - type: Accepted
9#           status: "True"
10#       parentRef:
11#         name: prod-gateway
12#         namespace: infra
bash
# Check Gateway status (shows assigned address)
kubectl get gateway prod-gateway -n infra
# NAME           CLASS   ADDRESS          PROGRAMMED   AGE
# prod-gateway   eg      203.0.113.100    True         5m

Frequently Asked Questions

Is Gateway API a drop-in replacement for Ingress?

No — they're separate APIs that coexist. Most ingress controllers (nginx, Traefik, AWS LBC) support both. You migrate incrementally: new services use HTTPRoute, existing Ingress resources keep working. Both can attach to different Gateway or IngressClass resources simultaneously on the same cluster.

What happens to Ingress?

Ingress is not being removed from Kubernetes — it's still valid. The API is GA and won't disappear. Gateway API is the strategic direction, but Ingress will remain supported indefinitely. Migrate when it offers real benefit (you need traffic splitting, header manipulation without annotations, or the role-separation model).

Does Gateway API work with cert-manager?

Yes. cert-manager supports Gateway API in its experimental channel. Annotate the Gateway with cert-manager.io/cluster-issuer: letsencrypt-prod and cert-manager provisions and attaches the certificate. See the cert-manager Gateway API docs for details.


For the AWS Load Balancer Controller that implements Gateway API on EKS, see AWS Load Balancer Controller for EKS. For Istio's implementation of Gateway API as a replacement for Istio's own Gateway/VirtualService CRDs, see Istio Service Mesh for Kubernetes. For a comparison of Ingress vs Gateway API, the operational reasons to migrate, and step-by-step migration instructions, see Kubernetes Ingress vs Gateway API: When to Migrate and How. For production ingress-nginx configuration (the incumbent before Gateway API) including WAF, rate limiting, and TLS, see Kubernetes Ingress-NGINX in Production.

Standardizing ingress across multiple teams on a shared cluster? Talk to us at Coding Protocols — we help platform teams adopt Gateway API in a way that gives application teams self-service routing without exposing infrastructure concerns.

Related Topics

Gateway API
Kubernetes
Ingress
HTTPRoute
Networking
Platform Engineering
EKS
AWS

Read Next