Envoy Gateway HTTP Routing in Practice: Host, Path, Header, and Traffic Splitting
The first two posts gave you the "what is this thing" foundation. This one gets into what you'll actually be editing day-to-day: HTTPRoute.
Think of HTTPRoute as a routing script for incoming traffic — except instead of writing Nginx rules or memorizing controller annotations, you're using Kubernetes-native resources.
One thing to get clear first: this post only covers HTTPRoute, because it handles HTTP/HTTPS semantics. For gRPC, you'd lean toward GRPCRoute; for raw TCP, TCPRoute; for TLS passthrough, TLSRoute. Don't force everything into HTTPRoute — the YAML starts giving off a "making do" vibe.
The Most Common Routing: Host and Path
Here's the most typical example:
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: app-route
spec:
parentRefs:
- name: eg
hostnames:
- "app.example.com"
rules:
- matches:
- path:
type: PathPrefix
value: /api
backendRefs:
- name: api-service
port: 8080What this means:
- Only
Host: app.example.comwill match this route - Path must start with
/api - Traffic is forwarded to
api-service:8080
If you remove hostnames, the route becomes "path-only, any domain."
What matches Can Match
matches is one of the most central concepts in HTTPRoute.
Think of it as "what conditions trigger this rule."
Common match conditions:
pathheadersqueryParamsmethod
Example:
matches:
- method: GET
path:
type: PathPrefix
value: /api
headers:
- name: version
value: v2Meaning:
- Only
GETrequests - Path starts with
/api - Request must include header
version: v2
This pattern is ideal for API version trials or staged rollouts to specific users.
One Route, Multiple Rules
A single HTTPRoute can route different paths to different services:
rules:
- matches:
- path:
type: PathPrefix
value: /api
backendRefs:
- name: api-service
port: 8080
- matches:
- path:
type: PathPrefix
value: /admin
backendRefs:
- name: admin-service
port: 8080This is great for splitting sub-paths under one domain — for example:
/apigoes to the API service/admingoes to the admin backend
The routing logic is immediately visible. Unlike some legacy config files where you read halfway through and start doubting your own existence.
Header, Method, and Query as Conditions
HTTPRoute isn't limited to path matching — it can also match on request headers, methods, and query params.
For example, only route requests with version: v2 to the new service:
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: versioned-route
spec:
parentRefs:
- name: eg
rules:
- matches:
- path:
type: PathPrefix
value: /api
headers:
- name: version
value: v2
backendRefs:
- name: api-v2
port: 8080
- matches:
- path:
type: PathPrefix
value: /api
backendRefs:
- name: api-v1
port: 8080This pattern works well for:
- Canary releases
- Letting specific customers try new versions early
- Manual validation of new features
Compared to cutting over an entire new domain, header-based routing is more granular and easier to experiment with.
filters: Process Requests Before Forwarding
HTTPRoute doesn't just "match and forward" — it can also apply transformations before or after forwarding. That's what filters are for.
A common use case is adding headers:
rules:
- filters:
- type: RequestHeaderModifier
requestHeaderModifier:
add:
- name: x-env
value: prod
backendRefs:
- name: api-service
port: 8080This is useful for:
- Injecting tracing metadata before forwarding to the backend
- Adding fixed headers at the ingress layer
- Request/response transformations
That said, filters are powerful but not a free pass to make the ingress layer a giant middleware blob. Too much logic piled onto routes will make debugging feel like it needs its own therapy session.
Weighted Traffic Splitting: The Most Common Canary Pattern
To send 90% of traffic to the old version and 10% to the new, use weight:
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: canary-route
spec:
parentRefs:
- name: eg
hostnames:
- "app.example.com"
rules:
- backendRefs:
- name: api-v1
port: 8080
weight: 90
- name: api-v2
port: 8080
weight: 10This is basic canary / traffic splitting.
You don't need a full service mesh just to run a traffic experiment. For many API services, this is more than enough — and it's a lot cleaner to configure.
💡
weightis relative, not required to sum to 100.9and1express the same ratio as90and10.
timeouts: Don't Let Requests Hang Forever
HTTPRoute supports timeout config at the rule level. This matters — if the ingress layer has no limits, bad requests sit around indefinitely like a rude customer who won't leave.
rules:
- matches:
- path:
type: PathPrefix
value: /reports
timeouts:
request: 10s
backendRequest: 2s
backendRefs:
- name: report-service
port: 8080Understanding the two fields:
request: Maximum total wait time for the client requestbackendRequest: Maximum wait for a single request to the backend
Note that backendRequest shouldn't exceed request — the logic would be like saying "the whole trip takes 10 minutes, but one bus leg can wait 20 minutes." The universe gets confused.
Two Common Nuances
1. parentRefs Can Target a Specific Listener
If a Gateway has multiple listeners, you can bind to just one:
parentRefs:
- name: eg
sectionName: httpThis means the route only attaches to the http listener.
In multi-listener setups, this is far more predictable than attaching to everything.
2. No matches = Match Everything
If a rule has no matches, it defaults to matching / — essentially a catch-all.
This is convenient, but it can silently swallow what you thought were more specific rules. Don't skip it out of laziness and then spend an hour wondering why your precise rules aren't firing.
Verifying That Routing Works
After configuring routes, validate with curl:
curl -H "Host: app.example.com" http://$GATEWAY_HOST/api/users
curl -H "Host: app.example.com" -H "version: v2" http://$GATEWAY_HOST/api/usersTo check HTTPRoute status:
kubectl get httproute app-route -o yamlPay particular attention to:
status.parents- The
Acceptedcondition
Sometimes the YAML looks correct, but the Gateway rejected it. That's like submitting a job application and assuming you got the job because you heard nothing back.
One-Line Summary
The core of HTTPRoute is "match first, then forward." Once you have host, path, header, and weight covered, you can handle about 80% of day-to-day HTTP ingress requirements.
Next Step
Next post tackles something you'll hit in every production environment: HTTPS, certificates, TLS termination, and security: 👉 TLS and Security