Kubernetes Gateway API Setup

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
  • kubectl configured 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-IP field 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 sectionName field 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 BackendTLSPolicy for 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