backendRefs is a list, but not the kind of list you think — the Gateway API trap that fails silently
If you're writing an HTTPRoute and need two paths served by different services, backendRefs looks like the obvious place to add a second entry.
That's a super easy mistake to make, and it fails silently.
Here's the baseline shape from the official docs:
rules:
- matches:
- path:
type: PathPrefix
value: /api
backendRefs:
- name: video-svc
port: 8080
Now you need /api/video and /api/eat handled by two different services. Under time pressure, you just append a second entry:
backendRefs:
- name: video-svc
port: 8080
- name: eat-svc
port: 8080
This does not route paths independently. It creates a weighted traffic split. One request goes to exactly one backend, chosen via round-robin. If you curl that endpoint 20 times, video-svc gets about 10 requests and eat-svc gets the other 10.
What backendRefs list is actually for
Traffic splitting: canaries, blue/green cutovers, and progressive rollouts.
backendRefs:
- name: api-v1
port: 8080
weight: 90
- name: api-v2
port: 8080
weight: 10
Changing those numbers handles your rollout without touching pods:
100 / 0-> all traffic to v1, v2 idle90 / 10-> canary testing50 / 50-> halfway through cutover0 / 100-> v2 live, v1 kept as your instant rollback switch
Two quirks worth knowing about weights:
- They're relative, not percentages. They don't need to sum to 100.
weight: 3andweight: 1means a 75/25 split. - The split is per-request, not sticky per-user. The same client can hit v1 then v2 on the very next call. Fine for canary, but weights alone aren't real A/B testing (which needs header matching for sticky sessions).
Why this bug bites so hard
Three things stack up to make this brutal to debug:
- Nothing in the YAML signals a split. Two entries under
backendRefslooks normal whether you intended path routing or traffic splitting. weightdefaults to 1. Omittingweightdoesn't opt you out of a split, it creates an even 50/50 split.- Nothing errors out. No
CrashLoopBackOff, no503, no warning inkubectl describe. Route status literally reportsAccepted: TrueandResolvedRefs: Truebecause both backends actually exist. Requests just land on the wrong service half the time. That's the exact profile of a bug you spend a whole day blaming on DNS or caching.
The correct way to handle multiple paths
Use separate rules, not multiple backendRefs inside one rule:
rules:
- matches:
- path: {type: PathPrefix, value: /api/video}
backendRefs:
- name: video-svc
port: 8080
- matches:
- path: {type: PathPrefix, value: /api/eat}
backendRefs:
- name: eat-svc
port: 8080
Reusing a Service across rules is totally fine video-svc can back both /api/video and /see/video.
Two match behaviors worth remembering:
rulesare not evaluated top-to-bottom. Ingress was first-match-wins, so people order them defensively. Gateway API scores matches by specificity: exact matches beat prefixes, and longer prefixes beat shorter ones. A bare/apirule sitting above/api/videowon't shadow it.matcheslogic: Multiple entries undermatchesact as an OR condition. But multiple conditions inside a single match entry (e.g., path + method + headers) act as an AND. One dash is the difference.
CKA / Exam Takeaways
- Separate paths mean separate
rules**:** CKA path-routing tasks will ask you to direct distinct paths to distinct services (e.g.,/apitosvc-aand/webtosvc-b). Never add multiplebackendRefsunder a single rule for this — that creates a traffic split instead of path routing. - Don't quick-copy individual refs: Under time pressure, appending a second
backendRefto an existing block is a fast way to lose points. Always copy and adjust the fullruleblock. - Rule order doesn't matter: Unlike classic
Ingress(where first match wins), Gateway API matches by specificity. Exact matches beat prefixes, and longer prefixes beat shorter ones. Placing a/rule at the top won't shadow/apibelow it. Don't waste time reordering YAML entries. - Verify Service ports:
portin abackendRefmust reference theServiceport — not thetargetPort, container port, or Gateway listener port. Always verify withkubectl get svc. - Verify with
curl**, not**kubectl**:** Route status will showAccepted: Trueeven if your path logic sends traffic to the wrong service. Run a quickcurlloop to confirm requests land where they belong.