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.

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:
# 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.yamlCheck what's included:
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:
helm install eg oci://docker.io/envoyproxy/gateway-helm \
--version v1.3.0 \
--namespace envoy-gateway-system \
--create-namespaceThe install creates a GatewayClass automatically:
kubectl get gatewayclass
# NAME CONTROLLER ACCEPTED
# eg gateway.envoyproxy.io/gatewaycontroller TrueGatewayClass and Gateway
The platform team creates the Gateway — this is the load balancer:
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:
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: 8080Traffic Splitting
Canary deployments without Argo Rollouts — native traffic splitting:
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 canaryURL Rewriting
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: 8080Header-Based Routing
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: 8080ReferenceGrant: 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:
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' namespaceThis 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:
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: 9090AWS ALB Integration
The AWS Load Balancer Controller (v2.8+) implements Gateway API with ALB as the backing load balancer:
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=true1# 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: AllHTTP → HTTPS Redirect
A permanent redirect from HTTP to HTTPS is a standalone HTTPRoute attached to the HTTP listener — no annotations needed:
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: 301This 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:
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 attachLabel the namespaces you want to grant access:
kubectl label namespace payments gateway-access=true
kubectl label namespace orders gateway-access=trueNamespaces without the label cannot attach HTTPRoute resources to this listener — their routes will show Accepted: False in status. Verify:
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):
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: 8080Gateway API (after):
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: 8080The 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
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# 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 5mFrequently 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.


