RegularExpression Path Matching in HTTPRoute
Kubernetes Gateway API and HTTPRoute
Kubernetes Gateway API is a collection of resources that model service networking in Kubernetes. Among them, HTTPRoute is the resource that defines HTTP routing rules for matching requests and forwarding them to backend services.
One of the most important things when working with HTTPRoute is understanding how multiple HTTPRoute rules get merged when attached to a single Gateway.
When multiple HTTPRoutes are attached to a single Gateway, the Proxy or LoadBalancer must assign priority in the following order:
- Exact path match
- Prefix path match (longer character count wins)
PathPrefix: /coldoes not route to/color
- RegularExpression path match (Istio-specific)
- Istio uses RE2 syntax (e.g.
.*matches any character)
- Istio uses RE2 syntax (e.g.
- Method match
- Largest number of header matches
- Largest number of query param matches
Istio Source Code: How Path Match Priority Works
Istio processes Gateway API-based HTTPRoute path matching rules with the following priority logic in its source code.
createURIMatch()
This function converts Gateway API path match types into Istio’s internal StringMatch representation:
func createURIMatch(match k8s.HTTPRouteMatch) (*istio.StringMatch, *ConfigError) {
tp := k8s.PathMatchPathPrefix
if match.Path.Type != nil {
tp = *match.Path.Type
}
dest := "/"
if match.Path.Value != nil {
dest = *match.Path.Value
}
switch tp {
case k8s.PathMatchPathPrefix:
if dest != "/" {
dest = strings.TrimSuffix(dest, "/")
}
return &istio.StringMatch{
MatchType: &istio.StringMatch_Prefix{Prefix: dest},
}, nil
case k8s.PathMatchExact:
return &istio.StringMatch{
MatchType: &istio.StringMatch_Exact{Exact: dest},
}, nil
case k8s.PathMatchRegularExpression:
return &istio.StringMatch{
MatchType: &istio.StringMatch_Regex{Regex: dest},
}, nil
default:
return nil, &ConfigError{...}
}
}
getURIRank()
This function assigns a numeric rank to each match type. The higher the return value, the higher the priority:
// getURIRank ranks a URI match type. Exact > Prefix > Regex
func getURIRank(match *istio.HTTPMatchRequest) int {
if match.Uri == nil {
return -1
}
switch match.Uri.MatchType.(type) {
case *istio.StringMatch_Exact:
return 3
case *istio.StringMatch_Prefix:
return 2
case *istio.StringMatch_Regex:
return 1
}
return -1
}
sortHTTPRoutes()
The sorting function uses getURIRank to order routes by priority:
func sortHTTPRoutes(routes []*istio.HTTPRoute) {
sort.SliceStable(routes, func(i, j int) bool {
// ...
r1, r2 := getURIRank(m1), getURIRank(m2)
len1, len2 := getURILength(m1), getURILength(m2)
switch {
case r1 != r2:
return r1 > r2 // higher priority first
case len1 != len2:
return len1 > len2
case (m1.Method == nil) != (m2.Method == nil):
return m1.Method != nil
case len(m1.Headers) != len(m2.Headers):
return len(m1.Headers) > len(m2.Headers)
default:
return len(m1.QueryParams) > len(m2.QueryParams)
}
})
}
The key takeaway: Exact(3) > Prefix(2) > Regex(1). If a PathPrefix that satisfies the route is declared, RegularExpression will never be matched.
The Kubernetes Gateway API spec itself does not define the priority of “RegularExpression” path matches — it is left up to each implementation to decide. This means users have no choice but to follow how their specific implementation handles it. In Istio’s case,
getURIRankenforces the URI match type priority as Exact > Prefix > Regex. Therefore, no matter how complex your Regex is, if a matching PathPrefix exists, PathPrefix match takes precedence.
For example, given:
PathPrefix: /api/v1/-> backend-serviceRegularExpression: /api/v1/hooks/.*/callback-> webhook-handler
A request to /api/v1/hooks/provider/callback will always be matched by PathPrefix first, and the RegularExpression rule will never apply.
If you need to use RegularExpression, you should use RegularExpression for all routes within the same hostname group in the gateway to prevent unexpected behavior (e.g. RegularExpression: /.*).
Practical Example: Gateway/Waypoint Separation
(1) Gateway: Broad routing with PathPrefix
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: public-ingress-route
spec:
parentRefs:
- name: istio-public-ingress
namespace: istio-gateway
hostnames:
- "api.example.com"
rules:
- matches:
- path:
type: PathPrefix
value: "/"
backendRefs:
- name: backend-svc
port: 8080
(2) Waypoint: Fine-grained routing with RegularExpression
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: backend-svc-waypoint
spec:
parentRefs:
- kind: Service
name: backend-svc
rules:
- matches:
- path:
type: RegularExpression
value: "/.*"
backendRefs:
- name: backend-svc
port: 8080
- matches:
- path:
type: RegularExpression
value: "/api/v1/hooks/.*/callback"
backendRefs:
- name: webhook-handler
port: 8080
Tips on Waypoint Rules and Envoy Config
- When
waypoint.rulesis blank (empty), default rules are populated to forward traffic to the backend-svc Envoy cluster. - If only the
/api/v1/hooks/.*/callbackRegularExpression rule exists, only requests matching that condition will be routed — all other requests will return 404 NOT FOUND. - Both
"/.*"and"/api/v1/hooks/.*/callback"must exist to achieve the expected behavior. Since Waypoint and Gateway are bound to separate Envoy instances, Waypoint rules are not affected by Gateway’s PathPrefix rules.
Conclusion
- Istio enforces PathPrefix > RegularExpression priority at the source code level.
- In practice, it is recommended to clearly separate Gateway and Waypoint for different routing needs.
- If you mix PathPrefix and Regex within the same hostname in an HTTPRoute without understanding this structure, PathPrefix will silently consume all matching traffic — a common pitfall.