The Kubernetes Gateway API is the official successor to the networking.k8s.io/v1 Ingress API. It became generally available in Kubernetes 1.28 and is now the community standard for managing external traffic. Unlike Ingress, it replaces vendor-specific annotations with first-class API fields and splits responsibilities across three role-oriented resources.
The Ingress API is not being removed, but it is frozen. No new features will be added to it. The Gateway API is where all future development happens.
Why the Gateway API replaces Ingress?
| Ingress | Gateway API |
|---|---|
| Single monolithic resource | Three role-oriented resources |
| Features via vendor annotations | Features as typed API fields |
| No cross-namespace routing | Native cross-namespace support via ReferenceGrant |
| Limited to HTTP/HTTPS | Supports HTTP, HTTPS, TCP, TLS, gRPC |
| Implementation-specific behaviour | Conformance tests ensure portable behaviour |
Core concepts
Three resources make up the Gateway API.
GatewayClass is a cluster-scoped resource that maps to a controller implementation, similar to StorageClass for persistent volumes. Cluster operators manage this resource.
Gateway is a namespace-scoped resource that declares a listener (port, protocol, TLS certificate). Infrastructure teams or platform engineers manage this resource.
HTTPRoute is a namespace-scoped resource that attaches routing rules (hosts, paths, headers) to a Gateway. Application developers manage this resource.
graph LR
GatewayClass --> Gateway
Gateway --> HTTPRoute
HTTPRoute --> Service
This separation means a developer can deploy routing rules without touching cluster-level networking configuration.
Prerequisites
Before starting, ensure you have:
- Kubernetes 1.28 or later
kubectlconfigured against your cluster- Helm 3 installed
- Services already deployed that you want to expose
Step 1: Install the Gateway API CRDs
The Gateway API ships as a separate CRD bundle maintained by the Kubernetes SIG-Network team. Install the standard channel, which includes GatewayClass, Gateway, HTTPRoute, ReferenceGrant, and GRPCRoute:
1
kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.2.1/standard-install.yaml
Verify the CRDs are installed:
1
kubectl get crd | grep gateway.networking.k8s.io
You should see gatewayclasses, gateways, httproutes, and referencegrants.
Always install the CRDs before the controller. The controller watches for these CRDs at startup and will fail if they are missing.
Step 2: Install Nginx Gateway Fabric
Nginx Gateway Fabric is the NGINX-maintained implementation of the Gateway API. It replaces the older Nginx Ingress Controller and is built from the ground up for the Gateway API standard.
1
2
3
4
helm install ngf oci://ghcr.io/nginxinc/charts/nginx-gateway-fabric \
--namespace nginx-gateway \
--create-namespace \
--set service.type=LoadBalancer
Wait for the controller pod to be ready:
1
kubectl -n nginx-gateway rollout status deployment/ngf-nginx-gateway-fabric
Retrieve the external IP assigned to the Gateway controller service:
1
kubectl -n nginx-gateway get service ngf-nginx-gateway-fabric
On cloud providers the
EXTERNAL-IPfield is populated automatically. On bare-metal clusters use MetalLB or a NodePort service type instead.
Step 3: Create a GatewayClass
A GatewayClass tells Kubernetes which controller will handle Gateways that reference it. Create one that points to Nginx Gateway Fabric:
1
2
3
4
5
6
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
name: nginx
spec:
controllerName: gateway.nginx.org/nginx-gateway-controller
Apply it:
1
kubectl apply -f gatewayclass.yaml
Confirm the class is accepted:
1
kubectl get gatewayclass nginx
The ACCEPTED column should show True.
Step 4: Create a Gateway
A Gateway declares which ports and protocols are open on the controller. Create one in the default namespace:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: main-gateway
namespace: default
spec:
gatewayClassName: nginx
listeners:
- name: http
port: 80
protocol: HTTP
allowedRoutes:
namespaces:
from: Same
The allowedRoutes.namespaces.from: Same field restricts which namespaces may attach routes to this listener. Change to All to allow routes from any namespace (cross-namespace routing requires a ReferenceGrant, see below).
Apply and verify:
1
2
kubectl apply -f gateway.yaml
kubectl get gateway main-gateway
The PROGRAMMED column should show True once the controller has configured the underlying proxy.
Step 5: Create an HTTPRoute
An HTTPRoute attaches routing rules to a Gateway. The following example routes all traffic for myapp.example.com to a service named myapp-service:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: myapp-route
namespace: default
spec:
parentRefs:
- name: main-gateway
namespace: default
hostnames:
- myapp.example.com
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: myapp-service
port: 8080
Path-based routing
Route /api and /app to separate services on the same hostname:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: split-route
namespace: default
spec:
parentRefs:
- name: main-gateway
namespace: default
hostnames:
- myapp.example.com
rules:
- matches:
- path:
type: PathPrefix
value: /api
backendRefs:
- name: api-service
port: 8080
- matches:
- path:
type: PathPrefix
value: /app
backendRefs:
- name: frontend-service
port: 80
Header-based routing
Route traffic based on a request header, for example to support canary deployments:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: canary-route
namespace: default
spec:
parentRefs:
- name: main-gateway
namespace: default
hostnames:
- myapp.example.com
rules:
- matches:
- headers:
- name: x-canary
value: "true"
backendRefs:
- name: myapp-canary-service
port: 8080
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: myapp-service
port: 8080
Step 6: Add TLS termination
First, add an HTTPS listener to the Gateway:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: main-gateway
namespace: default
spec:
gatewayClassName: nginx
listeners:
- name: http
port: 80
protocol: HTTP
allowedRoutes:
namespaces:
from: Same
- name: https
port: 443
protocol: HTTPS
tls:
mode: Terminate
certificateRefs:
- name: myapp-tls-secret
kind: Secret
allowedRoutes:
namespaces:
from: Same
Create the TLS secret from your certificate files:
1
2
3
4
kubectl create secret tls myapp-tls-secret \
--cert=path/to/cert.pem \
--key=path/to/key.pem \
-n default
Then update your HTTPRoute to attach to the https listener by adding a sectionName:
1
2
3
4
5
spec:
parentRefs:
- name: main-gateway
namespace: default
sectionName: https
The
sectionNamefield selects which listener the route attaches to when a Gateway has multiple listeners. Omitting it attaches the route to all matching listeners.
Step 7: Cross-namespace routing with ReferenceGrant
When a Gateway lives in one namespace and the target Service lives in another, a ReferenceGrant must explicitly permit the cross-namespace reference. Without it, the controller ignores the route.
1
2
3
4
5
6
7
8
9
10
11
12
13
apiVersion: gateway.networking.k8s.io/v1beta1
kind: ReferenceGrant
metadata:
name: allow-default-to-myapp
namespace: myapp-namespace
spec:
from:
- group: gateway.networking.k8s.io
kind: HTTPRoute
namespace: default
to:
- group: ""
kind: Service
This grants routes in the default namespace the ability to reference services in myapp-namespace.
Verifying the configuration
Check all Gateway API resources at once:
1
kubectl get gatewayclass,gateway,httproute -A
Inspect route attachment status:
1
kubectl describe httproute myapp-route
Look for Accepted: True and ResolvedRefs: True in the status conditions. A False condition includes a human-readable reason.
Test routing with curl:
1
curl -H "Host: myapp.example.com" http://<gateway-external-ip>/
Migration reference from Nginx Ingress
| Ingress concept | Gateway API equivalent |
|---|---|
kubernetes.io/ingress.class annotation | spec.gatewayClassName on Gateway |
spec.rules[].host | spec.hostnames[] on HTTPRoute |
spec.rules[].http.paths[] | spec.rules[].matches[].path on HTTPRoute |
spec.tls[].secretName | spec.listeners[].tls.certificateRefs[].name on Gateway |
nginx.ingress.kubernetes.io/* annotations | Native HTTPRoute filter fields or BackendTLSPolicy |
| No cross-namespace routing | ReferenceGrant + allowedRoutes.namespaces |
What to avoid?
Do not mix Ingress and HTTPRoute for the same service. Both the Nginx Ingress Controller and Nginx Gateway Fabric may be installed on the same cluster during migration, but routing the same hostname through both will produce unpredictable behaviour. Migrate one service at a time and remove the old Ingress resource before creating the equivalent HTTPRoute.
Do not use vendor annotations on HTTPRoute. The Gateway API is designed to be portable. If a feature you need is not in the standard API, check for a provider-specific policy CRD (such as NginxProxy for Nginx Gateway Fabric) rather than falling back to annotations.
Do not omit ReferenceGrant for cross-namespace references. The controller silently ignores routes that reference services in other namespaces without a grant in place. Always check route status conditions when troubleshooting missing traffic.
Do not skip CRD version pinning. The standard-install.yaml URL above pins to v1.2.1. Always pin to a specific release and align it with your controller’s supported Gateway API version before upgrading.
Next steps
- Set up cert-manager to automate TLS certificate provisioning for Gateway listeners
- Explore
BackendTLSPolicyfor end-to-end TLS between the Gateway and backend services - Review the Gateway API conformance report for your chosen implementation to understand which optional features are supported