Orchestration

Gateway API HTTPRoute: Advanced Traffic Routing

July 3, 2026 Kubezilla Team 14 min read

Introduction

In the evolving landscape of Kubernetes, managing incoming traffic efficiently and securely is paramount for modern applications. For years, Kubernetes Ingress has been the de-facto standard for HTTP and HTTPS routing, but its limitations, especially in advanced traffic management scenarios, have become increasingly apparent. Enter the Kubernetes Gateway API – a robust, extensible, and role-oriented API designed to overcome these challenges and provide a more powerful way to expose services to the outside world.

The Gateway API introduces a new set of resources, including GatewayClass, Gateway, and HTTPRoute, to provide granular control over traffic routing. Among these, HTTPRoute stands out as the workhorse for defining advanced HTTP routing patterns. It allows developers and operators to specify complex matching rules, manipulate requests and responses, and implement sophisticated traffic management strategies like canary deployments, A/B testing, and blue/green rollouts with unparalleled flexibility. This guide will dive deep into the HTTPRoute resource, exploring its capabilities and demonstrating how to leverage it for advanced traffic routing patterns in your Kubernetes clusters.

If you’re still navigating the transition from Ingress to Gateway API, we highly recommend checking out our comprehensive guide: Kubernetes Gateway API vs Ingress: The Complete Migration Guide. This resource will provide you with the foundational knowledge needed to fully appreciate the power and flexibility of HTTPRoute as we explore its advanced features.

TL;DR: Advanced HTTPRoute Patterns

The Kubernetes Gateway API’s HTTPRoute resource offers powerful, fine-grained control over HTTP traffic routing. It surpasses Ingress by enabling advanced patterns like path, header, and query parameter matching, request/response modification, and sophisticated traffic splitting for canary deployments or A/B testing. This guide covers setting up a Gateway API controller, defining a Gateway, and then creating various HTTPRoute configurations to manage traffic effectively.

Key Commands:

# Install a Gateway API controller (e.g., Nginx Gateway)
helm upgrade --install ingress-nginx ingress-nginx \
  --repo https://kubernetes.github.io/ingress-nginx \
  --namespace ingress-nginx --create-namespace \
  --set controller.service.type=LoadBalancer \
  --set-string controller.config.enable-gateway-api=true

# Deploy a sample application
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/docs/examples/gateway/httproute/echoserver.yaml

# Define a Gateway
kubectl apply -f - <<EOF
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: my-gateway
  namespace: default
spec:
  gatewayClassName: nginx
  listeners:
  - name: http
    protocol: HTTP
    port: 80
EOF

# Example: Basic HTTPRoute
kubectl apply -f - <<EOF
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: basic-http-route
  namespace: default
spec:
  parentRefs:
  - name: my-gateway
  hostnames:
  - "example.com"
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /
    backendRefs:
    - name: echoserver
      port: 80
EOF

# Example: PathPrefix and Header Matching
kubectl apply -f - <<EOF
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: advanced-http-route
  namespace: default
spec:
  parentRefs:
  - name: my-gateway
  hostnames:
  - "example.com"
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /v2
      headers:
      - name: X-Version
        value: "v2"
    backendRefs:
    - name: echoserver-v2
      port: 80
  - matches:
    - path:
        type: PathPrefix
        value: /
    backendRefs:
    - name: echoserver
      port: 80
EOF

# Get Gateway IP
kubectl get gateway my-gateway -o jsonpath='{.status.addresses[0].value}'

Prerequisites

Before we embark on our journey into advanced HTTPRoute patterns, ensure you have the following:

  • Kubernetes Cluster: A running Kubernetes cluster (v1.20+ is recommended for Gateway API, though v1.22+ is where it became generally available). You can use Minikube, Kind, a cloud-managed cluster (EKS, GKE, AKS), or any other Kubernetes distribution.
  • kubectl: The Kubernetes command-line tool, configured to connect to your cluster. Refer to the official Kubernetes documentation for installation instructions.
  • Helm: The package manager for Kubernetes, used to install the Gateway API controller. Install instructions can be found on the Helm website.
  • Gateway API CRDs: The Custom Resource Definitions (CRDs) for the Gateway API must be installed in your cluster. Most Gateway API controllers will install these automatically, but it’s good to be aware.
  • A Gateway API Controller: You need an implementation of the Gateway API. Popular choices include Nginx, Istio, Cilium, or Envoy Gateway. For this tutorial, we will use the Nginx Gateway API controller as it’s straightforward to set up for demonstration purposes. For a more sophisticated service mesh implementation with Gateway API support, consider exploring our Istio Ambient Mesh Production Guide.
  • Basic Kubernetes Knowledge: Familiarity with Kubernetes concepts like Pods, Deployments, Services, and Namespaces is assumed.

Step-by-Step Guide: Advanced HTTPRoute Patterns

Step 1: Install a Gateway API Controller

The first step is to install a Gateway API controller in your cluster. This controller watches for Gateway API resources (GatewayClass, Gateway, HTTPRoute, etc.) and translates them into the underlying infrastructure configuration (e.g., Nginx configuration, Envoy configuration). For this guide, we’ll use the Nginx Gateway controller, which can be easily installed via Helm.

We’ll install the Nginx Ingress Controller, configured to also act as a Gateway API controller. This setup is common as many Ingress controllers are evolving to support Gateway API. We deploy it into its own namespace, ingress-nginx, and expose it via a LoadBalancer service to make it accessible from outside the cluster.

# Add the Nginx Ingress Helm repository
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update

# Install the Nginx Ingress Controller with Gateway API support enabled
helm upgrade --install ingress-nginx ingress-nginx \
  --repo https://kubernetes.github.io/ingress-nginx \
  --namespace ingress-nginx --create-namespace \
  --set controller.service.type=LoadBalancer \
  --set-string controller.config.enable-gateway-api=true

Verify:

Confirm that the Nginx controller pods are running and the LoadBalancer service has an external IP or hostname assigned.

kubectl get pods -n ingress-nginx

Expected Output (may vary slightly):

NAME                                       READY   STATUS    RESTARTS   AGE
ingress-nginx-controller-5c9c9b46c-abcde   1/1     Running   0          2m
kubectl get svc -n ingress-nginx ingress-nginx-controller

Expected Output (EXTERNAL-IP will be pending for a short while on some cloud providers):

NAME                       TYPE           CLUSTER-IP     EXTERNAL-IP     PORT(S)                      AGE
ingress-nginx-controller   LoadBalancer   10.96.100.123  34.123.45.67    80:30000/TCP,443:30001/TCP   2m

Make a note of the EXTERNAL-IP; this will be your Gateway’s public address.

Step 2: Deploy Sample Applications

To demonstrate various routing patterns, we need some sample applications. We’ll deploy a simple echoserver application which returns details about the request it received. We’ll deploy two versions to simulate different services or versions of a service.

First, deploy a standard echoserver. This will be our default backend.

kubectl apply -f - <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
  name: echoserver
  labels:
    app: echoserver
spec:
  replicas: 1
  selector:
    matchLabels:
      app: echoserver
  template:
    metadata:
      labels:
        app: echoserver
    spec:
      containers:
      - name: echoserver
        image: k8s.gcr.io/echoserver:1.4
        ports:
        - containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: echoserver
spec:
  selector:
    app: echoserver
  ports:
  - port: 80
    targetPort: 8080
EOF

Next, let’s deploy a “v2” version of the echoserver, which we’ll use for advanced routing scenarios like header matching or traffic splitting. We’ll label it differently so we can target it specifically.

kubectl apply -f - <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
  name: echoserver-v2
  labels:
    app: echoserver-v2
spec:
  replicas: 1
  selector:
    matchLabels:
      app: echoserver-v2
  template:
    metadata:
      labels:
        app: echoserver-v2
    spec:
      containers:
      - name: echoserver
        image: k8s.gcr.io/echoserver:1.4 # Using the same image, but we'll differentiate via routing
        ports:
        - containerPort: 8080
        env:
        - name: NODE_NAME
          value: "echoserver-v2-pod"
EOF

Verify:

Ensure both deployments and services are running:

kubectl get deploy,svc

Expected Output:

NAME                           READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/echoserver     1/1     1            1           1m
deployment.apps/echoserver-v2  1/1     1            1           1m

NAME                 TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
service/echoserver   ClusterIP   10.96.10.100     <none>        80/TCP     1m
service/echoserver-v2 ClusterIP   10.96.10.101     <none>        80/TCP     1m

Step 3: Define a Gateway

A Gateway resource represents a load balancer or proxy that handles incoming traffic. It binds to a GatewayClass (which is provided by the controller) and defines listeners for specific ports and protocols.

We’ll create a simple HTTP Gateway listening on port 80. The gatewayClassName must match the name of the GatewayClass provided by your controller (e.g., nginx for the Nginx Gateway controller).

apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: my-gateway
  namespace: default
spec:
  gatewayClassName: nginx
  listeners:
  - name: http
    protocol: HTTP
    port: 80
    allowedRoutes:
      namespaces:
        from: All
---
apiVersion: v1
kind: Service
metadata:
  name: my-gateway-lb # This is optional, but some controllers create a Service for the Gateway.
  # For Nginx Gateway, the LoadBalancer service created in Step 1 is often used directly.
  # However, defining a Gateway explicitly tells the controller which LoadBalancer service to manage/associate.
spec:
  type: LoadBalancer # For Nginx, this might not be strictly needed if controller manages Service directly
  selector:
    gateway.networking.k8s.io/gateway-name: my-gateway # Match the gateway controller's pods
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80
EOF

Note: With the Nginx Gateway controller installed in Step 1, the ingress-nginx-controller service already acts as the LoadBalancer. The Gateway resource primarily configures the controller’s behavior. Some Gateway API implementations might create a dedicated LoadBalancer service for each Gateway resource. The Nginx Gateway API controller typically uses the service created during its installation. We’ve added a placeholder Service definition for clarity, but the actual LoadBalancer IP will come from the controller’s service.

kubectl apply -f - <<EOF
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: my-gateway
  namespace: default
spec:
  gatewayClassName: nginx
  listeners:
  - name: http
    protocol: HTTP
    port: 80
    allowedRoutes:
      namespaces:
        from: All # Allow HTTPRoutes from any namespace to attach to this Gateway
EOF

Verify:

Check the status of your Gateway. It should eventually show an address.

kubectl get gateway my-gateway -o yaml

Expected Output (look for status.addresses):

apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: my-gateway
  namespace: default
spec:
  # ... (spec details) ...
status:
  addresses:
  - type: IPAddress
    value: 34.123.45.67 # Your LoadBalancer IP
  conditions:
  - lastTransitionTime: "2023-10-27T10:00:00Z"
    message: Gateway is accepted by controller
    observedGeneration: 1
    reason: Accepted
    status: "True"
    type: Accepted
  - lastTransitionTime: "2023-10-27T10:00:00Z"
    message: Listener Ready
    observedGeneration: 1
    reason: Ready
    status: "True"
    type: Ready
  listeners:
  - attachedRoutes: 0
    conditions:
    - lastTransitionTime: "2023-10-27T10:00:00Z"
      message: Listener is Running
      observedGeneration: 1
      reason: Ready
      status: "True"
      type: Ready
    name: http
    supportedKinds:
    - group: gateway.networking.k8s.io
      kind: HTTPRoute

Retrieve the external IP for testing:

export GATEWAY_IP=$(kubectl get gateway my-gateway -o jsonpath='{.status.addresses[0].value}')
echo "Gateway IP: $GATEWAY_IP"

Step 4: Basic HTTPRoute – Hostname and Path Matching

Let’s start with a foundational HTTPRoute that routes all traffic for a specific hostname to our echoserver service. This is analogous to a basic Ingress rule. The parentRefs link the HTTPRoute to our Gateway, and hostnames specify which domain this route applies to.

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: basic-http-route
  namespace: default
spec:
  parentRefs:
  - name: my-gateway
  hostnames:
  - "example.com" # Use a domain you control or map via /etc/hosts
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: / # Match all paths
    backendRefs:
    - name: echoserver
      port: 80
EOF

Apply this route:

kubectl apply -f - <<EOF
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: basic-http-route
  namespace: default
spec:
  parentRefs:
  - name: my-gateway
  hostnames:
  - "example.com"
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /
    backendRefs:
    - name: echoserver
      port: 80
EOF

Verify:

Update your local /etc/hosts file (or DNS) to point example.com to your GATEWAY_IP. Then, make a request.

# Add to /etc/hosts (replace with your actual IP)
echo "$GATEWAY_IP example.com" | sudo tee -a /etc/hosts

curl -H "Host: example.com" http://$GATEWAY_IP/

Expected Output (showing details from the echoserver):

Hostname: echoserver-7fbc6c478c-abcde
Pod Information:
    - no pod information available -
Server values:
    server_version=nginx/1.10.0
Request Information:
    client_address=10.0.0.1
    method=GET
    path=/
    headers:
        Host: example.com
        User-Agent: curl/7.81.0
        X-Forwarded-For: 10.0.0.1, 10.1.2.3
        # ... other headers ...

Step 5: Advanced Matching – Path, Header, and Query Parameter

HTTPRoute allows for highly granular traffic matching. You can combine multiple match types within a single rule. Let’s create a rule that routes traffic to echoserver-v2 if the path starts with /v2 AND a specific header X-Version: v2 is present. Otherwise, it falls back to the default echoserver.

This is extremely powerful for A/B testing or rolling out new APIs to specific clients. For more advanced network security and isolation, consider how these routing rules can complement Kubernetes Network Policies to secure your microservices.

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: advanced-match-route
  namespace: default
spec:
  parentRefs:
  - name: my-gateway
  hostnames:
  - "example.com"
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /v2
      headers:
      - name: X-Version
        value: "v2"
      queryParams:
      - name: debug
        value: "true"
    backendRefs:
    - name: echoserver-v2
      port: 80
  - matches: # Default rule for anything else
    - path:
        type: PathPrefix
        value: /
    backendRefs:
    - name: echoserver
      port: 80
EOF

Apply this route:

kubectl apply -f - <<EOF
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: advanced-match-route
  namespace: default
spec:
  parentRefs:
  - name: my-gateway
  hostnames:
  - "example.com"
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /v2
      headers:
      - name: X-Version
        value: "v2"
      queryParams:
      - name: debug
        value: "true"
    backendRefs:
    - name: echoserver-v2
      port: 80
  - matches:
    - path:
        type: PathPrefix
        value: / # Default rule
    backendRefs:
    - name: echoserver
      port: 80
EOF

Verify:

Test with different requests:

# Should go to echoserver (default)
curl -H "Host: example.com" http://$GATEWAY_IP/
curl -H "Host: example.com" http://$GATEWAY_IP/v2

# Should go to echoserver-v2 (matching all conditions)
curl -H "Host: example.com" -H "X-Version: v2" http://$GATEWAY_IP/v2?debug=true

Expected Output for default:

Hostname: echoserver-7fbc6c478c-abcde
# ... (no mention of echoserver-v2-pod) ...

Expected Output for v2 route:

Hostname: echoserver-v2-deployment-abcde # Or similar pod name
Pod Information:
    NODE_NAME: echoserver-v2-pod # This is our custom env var
# ...
Request Information:
    headers:
        Host: example.com
        X-Version: v2
    uri: /v2?debug=true

Step 6: Traffic Splitting (Canary Deployments)

One of the most powerful features of HTTPRoute is its ability to split traffic between multiple backends based on weights. This is crucial for implementing canary deployments, A/B testing, or gradual rollouts. We’ll split traffic for the /canary path, sending 90% to echoserver and 10% to echoserver-v2.

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: traffic-split-route
  namespace: default
spec:
  parentRefs:
  - name: my-gateway
  hostnames:
  - "canary.example.com" # A new hostname for canary testing
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /
    backendRefs:
    - name: echoserver
      port: 80
      weight: 90 # 90% of traffic
    - name: echoserver-v2
      port: 80
      weight: 10 # 10% of traffic
EOF

Apply this route:

kubectl apply -f - <<EOF
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: traffic-split-route
  namespace: default
spec:
  parentRefs:
  - name: my-gateway
  hostnames:
  - "canary.example.com"
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /
    backendRefs:
    - name: echoserver
      port: 80
      weight: 90
    - name: echoserver-v2
      port: 80
      weight: 10
EOF

Verify:

Add canary.example.com to your /etc/hosts file, pointing to the same GATEWAY_IP.

echo "$GATEWAY_IP canary.example.com" | sudo tee -a /etc/hosts

Now, repeatedly hit the endpoint and observe the responses. You should see approximately 1 out of 10 requests hitting echoserver-v2.

for i in $(seq 1 20); do curl -s -H "Host: canary.example.com" http://$GATEWAY_IP/ | grep "Hostname\|NODE_NAME"; done

Expected Output (mix of both):

Hostname: echoserver-7fbc6c478c-abcde
Hostname: echoserver-7fbc6c478c-abcde
Hostname: echoserver-v2-deployment-abcde
    NODE_NAME: echoserver-v2-pod
Hostname: echoserver-7fbc6c478c-abcde
# ... and so on ...

Step 7: Request and Response Header Modification

HTTPRoute also allows you to modify HTTP headers both on the request before it reaches the backend and on the response before it’s sent back to the client. This is useful for injecting correlation IDs, security tokens, or removing sensitive information.

Let’s add a custom request header X-Request-ID to all requests and remove the Server header from responses for a specific path.

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: header-mod-route
  namespace: default
spec:
  parentRefs:
  - name: my-gateway
  hostnames:
  - "headers.example.com"
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /
    filters:
    - type: RequestHeaderModifier
      requestHeaderModifier:
        add:
        - name: X-Request-ID
          value: "generated-by-gateway"
    - type: ResponseHeaderModifier
      responseHeaderModifier:
        remove:
        - "Server" # The echoserver usually adds a Server header
    backendRefs:
    - name: echoserver
      port: 80
EOF

Apply this route:

kubectl apply -f - <<EOF
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: header-mod-route
  namespace: default
spec:
  parentRefs:
  - name: my-gateway
  hostnames:
  - "headers.example.com"
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /
    filters:
    - type: RequestHeaderModifier
      requestHeaderModifier:
        add:
        - name: X-Request-ID
          value: "generated-by-gateway"
    - type: ResponseHeaderModifier
      responseHeaderModifier:
        remove:
        - "Server"
    backendRefs:
    - name: echoserver
      port: 80
EOF

Verify:

Add headers.example.com to your /etc/hosts.

echo "$GATEWAY_IP headers.example.com" | sudo tee -a /etc/hosts

Make a request and inspect the request headers received by the backend (echoserver output) and the response headers returned to your client.

curl -v -H "Host: headers.example.com" http://$GATEWAY_IP/

Expected Output (partially, look for X-Request-ID in the echoserver output and absence of Server in curl’s response headers):

# In the echoserver output (body of the curl response):
Request Information:
    headers:
        Host: headers.example.com
        X-Request-Id: generated-by-gateway
        # ...

# In curl's verbose output (headers received by client):
< HTTP/1.1 200 OK
< Content-Type: text/plain
< Date: Fri, 27 Oct 2023 11:30:00 GMT
# ... (NO 'Server' header) ...

Step 8: URL Rewrite

URL rewriting allows you to change the path or hostname of a request before it’s forwarded to the backend service. This is useful for exposing simpler URLs to clients while maintaining complex internal service structures, or for migrating APIs.

Let’s rewrite requests to /old-path to /new-path before sending them to the echoserver.

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: rewrite-route
  namespace: default
spec:
  parentRefs:
  - name: my-gateway
  hostnames:
  - "rewrite.example.com"
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /old-path
    filters:
    - type: URLRewrite
      urlRewrite:
        path:
          type: ReplacePrefixMatch
          replacePrefixMatch: /new-path
    backendRefs:
    - name: echoserver
      port: 80
  - matches: # Default rule for other paths on this host
    - path:
        type: PathPrefix
        value: /
    backendRefs:
    - name: echoserver
      port: 80
EOF

Apply this route:

kubectl apply -f - <<EOF
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: rewrite-route
  namespace: default
spec:
  parentRefs:
  - name: my-gateway
  hostnames:
  - "rewrite.example.com"
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /old-path
    filters:
    - type: URLRewrite
      urlRewrite:
        path:
          type: ReplacePrefixMatch
          replacePrefixMatch: /new-path
    backendRefs:
    - name: echoserver
      port: 80
  - matches:
    - path:
        type: PathPrefix
        value: /
    backendRefs:
    - name: echoserver
      port: 80
EOF

Verify:

Add rewrite.example.com to your /etc/hosts.

echo "$GATEWAY_IP rewrite.example.com" | sudo tee -a /etc/hosts

Request the /old-path and observe that the backend receives /new-path.

curl -H "Host: rewrite.example.com" http://$GATEWAY_IP/old-path/some-resource

Expected Output (look for path in the echoserver output):

Request Information:
    method=GET
    path=/new-path/some-resource # The path was rewritten!
# ...

Production Considerations

Leave a comment