Path & Header-Based Routing
The API gateway’s primary job is to translate an incoming HTTP request into a deterministic upstream dispatch — and that translation starts with path and header matching. These two mechanisms cover the majority of real-world routing decisions: which service handles /api/v2/orders, whether a request bearing X-Tenant-ID: acme goes to a dedicated pool, and how an Accept: application/vnd.api+json header selects a content-negotiated backend. Understanding how each gateway evaluates these predicates, in what order, and with what precedence is foundational to Advanced Routing & API Versioning.
Architectural Baseline
Before writing a single route rule, engineers need a clear mental model of the routing pipeline:
- TLS termination — the gateway decrypts the request and exposes plaintext headers.
- Header normalisation — header names are canonicalised (typically lowercased), invalid or oversized values are rejected.
- Route lookup — the gateway walks a compiled route table, evaluating path and header predicates until one matches.
- Policy execution — the matched route triggers authentication, rate limiting, and transformation plugins in the middleware chain.
- Upstream dispatch — the gateway forwards the request to the resolved upstream endpoint, injecting trace headers.
Path matching and header matching operate at step 3, but their interaction with step 4 is critical: misrouted traffic that reaches the wrong authentication plugin will generate spurious 401 errors that are hard to distinguish from real security boundary failures. Get routing right first; policy composition follows.
Route table data structures matter. Kong (OpenResty/nginx core) compiles its route table at reload time into an internal trie for prefix lookups. Envoy (1.32+) constructs per-virtual-host route matchers evaluated top-to-bottom. NGINX uses a specificity hierarchy (= exact > ^~ prefix > ~ regex > / catch-all). Each model has different implications for how you express priority — covered in depth below.
Path-Based Routing: Prefix, Exact, and Regex
Path matching is the most common routing predicate. The three modes — prefix, exact, and regex — differ in how they balance specificity, performance, and maintenance overhead.
Prefix matching
Prefix matching routes all URIs that begin with a given string. It is the workhorse of service decomposition: /api/v1/orders routes to the order service, /api/v1/customers to the customer service. Most gateways apply a longest-prefix-wins rule so that /api/v1/orders/returns does not accidentally resolve to a catch-all /api/ route.
Kong 3.x — prefix route:
# Kong 3.x declarative (deck) — prefix route
services:
- name: order-service
url: http://order-svc.internal:8080
routes:
- name: orders-v1
paths:
- /api/v1/orders
strip_path: false
protocols: [http, https]
strip_path: false preserves the full URI when forwarding upstream. Set it to true only when the upstream service expects requests at / rather than the full path.
Exact matching
Exact matching is appropriate for sentinel endpoints such as /healthz, /readyz, or versioned contract endpoints where any path variation signals a client bug. Kong uses ~ prefix on a path to switch to regex mode; without it, all paths are treated as prefixes. To force exact semantics in Kong you can anchor a regex path: ~/api/v1/status$.
Envoy 1.32+ — exact and prefix in the same virtual host:
# Envoy 1.32+ route_config — exact match takes precedence by declaration order
virtual_hosts:
- name: api_gateway
domains: ["api.example.com"]
routes:
- match:
path: "/api/v1/status" # exact match
route:
cluster: health_check_cluster
timeout: 2s
- match:
prefix: "/api/v1/orders" # prefix match — evaluated second
route:
cluster: order_service_v1
timeout: 5s
- match:
prefix: "/" # catch-all
route:
cluster: default_backend
In Envoy, route evaluation is strictly top-to-bottom within a virtual host. Always list more-specific rules before less-specific ones.
Regex matching
Regex routes handle dynamic segments such as /api/v1/orders/{uuid}/items where the UUID must conform to a specific format. Use sparingly on hot paths. Envoy uses RE2 (linear time, no catastrophic backtracking). NGINX compiles PCRE patterns at startup. Kong (via ~ prefix) delegates to the underlying LuaJIT regex engine.
NGINX 1.26+ — regex location with named capture for UUID validation:
# NGINX 1.26+ — regex location, more specific than prefix, less specific than exact
server {
listen 443 ssl;
server_name api.example.com;
# Exact match takes priority
location = /healthz {
proxy_pass http://health_backend;
}
# Regex: validate UUID format in path segment
location ~ ^/api/v1/orders/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/items$ {
proxy_pass http://order_items_backend;
proxy_set_header X-Order-ID $1;
}
# Prefix fallback
location /api/v1/ {
proxy_pass http://api_v1_backend;
}
}
Header-Based Routing: Versioning and Tenant Dispatch
Header matching evaluates HTTP metadata — standard or custom — to influence upstream selection without altering the URI. This is the mechanism behind Accept-driven content negotiation, X-API-Version versioning strategies, and multi-tenant routing where each tenant maps to a dedicated upstream pool.
Custom version headers
The pattern: clients send X-API-Version: v2 (or API-Version: 2024-01-01 for date-based versioning). The gateway matches on this header and routes to the appropriate service version, leaving the URI path stable across versions.
Kong 3.x — header predicate on a route:
# Kong 3.x — route matching on X-API-Version header
services:
- name: order-service-v2
url: http://order-svc-v2.internal:8080
routes:
- name: orders-v2-header
paths:
- /api/orders
headers:
x-api-version:
- "v2"
protocols: [http, https]
- name: order-service-v1
url: http://order-svc-v1.internal:8080
routes:
- name: orders-v1-fallback
paths:
- /api/orders
protocols: [http, https]
Kong evaluates the header-constrained route first because its specificity score is higher (path + header vs path alone). The fallback route captures all /api/orders traffic without an x-api-version: v2 header.
Regex header matching
Some versioning schemes use semver-like values (v2.1, v2.3-beta) or feature-flag strings. Envoy’s safe_regex matcher and NGINX’s ~* header conditions handle these.
Envoy 1.32+ — combined path prefix + regex header match:
# Envoy 1.32+ — route matching path + header regex
routes:
- match:
prefix: "/api/orders"
headers:
- name: "x-feature-flag"
string_match:
safe_regex:
regex: "^beta-[a-z0-9]+$"
route:
cluster: orders_beta_pool
timeout: 10s
- match:
prefix: "/api/orders"
headers:
- name: "x-api-version"
string_match:
exact: "v2"
route:
cluster: orders_v2_cluster
timeout: 5s
- match:
prefix: "/api/orders"
route:
cluster: orders_v1_cluster
timeout: 5s
The beta-flag route must appear first: it is the most restrictive, and Envoy stops at the first match.
Standard headers: Accept and Content-Type
Content-type negotiation uses the Accept header to select between JSON and Protobuf upstreams, or between REST and a gRPC-to-REST translated backend. This is more complex than custom headers because Accept can carry quality factors (application/json;q=0.9). Most gateways match on substring or exact value; if your clients send quality-weighted Accept headers, normalise them in a request transformation plugin before routing.
The middleware chain that wraps the routing step is where this normalisation belongs — transformations run before or after routing depending on gateway configuration, so verify execution order in your gateway’s documentation.
Compound Matching: Path + Header + Method
Production routes rarely use a single predicate in isolation. Combining path prefix, header constraints, and HTTP method gives you precise traffic segmentation with minimal rule count. The diagram below shows how Envoy evaluates compound predicates top-to-bottom until the first match is found.
Kong 3.x — three-predicate compound route:
# Kong 3.x — compound: path + header + method
services:
- name: payments-v2
url: http://payments-svc-v2.internal:9090
routes:
- name: payments-post-v2
paths:
- /api/v1/payments
methods:
- POST
headers:
x-api-version:
- "v2"
protocols: [https]
strip_path: false
Only POST /api/v1/payments requests carrying x-api-version: v2 over HTTPS match this route. Any other combination falls through to the next route in Kong’s evaluation.
Comparative Implementation Table
| Gateway | Path match types | Header match types | Precedence model | Regex engine |
|---|---|---|---|---|
| Kong 3.x | Prefix, regex (~ prefix), strip_path |
Exact list per header name | Specificity score (path length + header count + method) | LuaJIT PCRE |
| Envoy 1.32+ | path (exact), prefix, safe_regex |
Exact, prefix, suffix, safe_regex, present/absent |
Declaration order within virtual host | RE2 (linear time) |
| NGINX 1.26+ | = exact, ^~ prefix, ~ regex, / catch-all |
Via if ($http_<name>) or map |
Specificity hierarchy, then file order for same type | PCRE2 |
| Tyk 5.x | listen_path prefix + strip_listen_path |
Via middleware RequestHeader |
Path prefix first, then middleware chain order | Go regexp |
Trade-off notes:
- Kong — high-level route object model makes compound predicates easy to declare; specificity scoring removes need to manually order rules, but scoring algorithm must be understood to avoid surprises.
- Envoy — declaration order gives maximum control; easy to place a catch-all above a specific rule accidentally during config edits — enforce order with policy-as-code linting.
- NGINX —
locationblock specificity is well-known but header-based branching viaifblocks is fragile at scale; better suited for simpler topologies or as a TLS offload layer in front of a feature-rich gateway. - Tyk — route config is simpler but header-based dispatch requires custom middleware; better for teams already invested in the Tyk plugin ecosystem. See the Kong vs Tyk vs Envoy comparison for a full capability matrix.
Advanced Configuration: Precedence Rules and Edge Cases
The path normalisation trap
Gateways differ on whether they normalise percent-encoded characters, double slashes, or trailing slashes before matching. Envoy normalises paths by default (configurable via HttpConnectionManager.normalize_path). NGINX does not collapse double slashes unless merge_slashes on is set. Kong normalises via the OpenResty layer. A request to /api//v1/orders may match on one gateway and return 404 on another. Enforce path normalisation consistently across your fleet — and test with edge-case URIs in CI.
Header injection and SSRF risk
Never use unvalidated header values directly in routing decisions without an allowlist. A client sending X-Tenant-ID: ../admin cannot affect path resolution, but if your gateway uses that header value to construct an upstream URL (e.g., http://{X-Tenant-ID}.internal/), the gateway itself becomes an SSRF vector. The security boundaries and zero-trust layer must validate and allowlist header values before routing logic consumes them, and implementing mTLS at the gateway edge adds a transport-level identity check that limits which clients can supply sensitive routing headers at all.
Rate-limiting scope and routing interaction
When you scope rate limiting and throttling buckets by route, a misconfigured route that catches more traffic than intended will exhaust rate-limit counters for unrelated clients. Always verify that the routing table produces the intended traffic distribution before attaching rate-limit policies — instrument with per-route request counters first.
Versioned routes and the deprecation signal
When a versioned route (x-api-version: v1) is scheduled for removal, inject a Deprecation response header and a Sunset date at the gateway level. This gives clients programmatic notice without requiring upstream code changes. Kong’s response-transformer plugin handles this; Envoy’s response_headers_to_add at the route level does the same.
# Envoy 1.32+ — inject deprecation headers on v1 route
routes:
- match:
prefix: "/api/orders"
headers:
- name: "x-api-version"
string_match:
exact: "v1"
route:
cluster: orders_v1_cluster
response_headers_to_add:
- header:
key: "Deprecation"
value: "true"
- header:
key: "Sunset"
value: "Sat, 01 Nov 2026 00:00:00 GMT"
- header:
key: "Link"
value: "</api/orders>; rel=\"successor-version\""
Observability: routing decision telemetry
The routing decision context must be captured in every request’s structured log and trace. Minimum fields:
route_id— the matched route identifiermatched_path_type—exact | prefix | regexmatched_headers— list of header predicates that contributed to the matchupstream_cluster— resolved backend targetroute_lookup_latency_ns— time spent in route table evaluation
Inject traceparent (W3C Trace Context) and the matched route_id as a span attribute at the routing step. This lets you correlate latency spikes with specific route rules in your APM tool without redeploying any service.
Operational Gotchas
1. Catch-all route placed too early. In Envoy and NGINX, a prefix: / or location / rule before specific routes will absorb all traffic. Add a CI lint step (e.g., envoy-config-validator, or nginx -t with a test config) that flags when a catch-all appears before non-catch-all routes.
2. Kong route ordering confusion. Engineers new to Kong sometimes assume it evaluates routes top-to-bottom in the declarative config file. Kong uses a scoring algorithm, not declaration order. A route with a short path and no headers will beat a route with a long path and no headers only if its path is longer. Understand the Kong route priority rules before relying on implicit ordering.
3. Case-sensitive header values. Header names are case-insensitive (RFC 9110), but header values are case-sensitive by default. X-API-Version: V2 will not match an exact rule for v2. Normalise header values in a pre-routing request transformer, or use case-insensitive regex matchers where supported.
4. Path stripping and upstream 404s. When strip_path: true (Kong) or prefix_rewrite: / (Envoy) is set, the matched path prefix is removed before forwarding. If the upstream service expects the full path, this causes a 404 at the service layer that looks identical to a routing miss. Always log the upstream request URI separately from the inbound URI.
5. Hot-reload propagation lag. After updating route config, most gateways apply changes asynchronously. Envoy xDS updates propagate via streaming; Kong’s in-memory router may take a few seconds to rebuild. During this window, stale routes may still match. Implement a GET /route-health endpoint that returns the active route table version and poll it in deployment pipelines before promoting traffic.
6. Regex route ReDoS exposure. Even with RE2’s linear guarantee, complex patterns on long URI strings increase per-request CPU. Benchmark regex routes under load with representative URI distributions. Set a compiled-pattern timeout in NGINX (pcre_jit on; plus proxy_read_timeout) and prefer anchored patterns.
Production Configuration Checklist
- Exact-match routes are declared (or scored) above prefix routes; prefix routes are above catch-all routes in every gateway config.
- Path normalisation is enabled and consistent across all gateway instances (
normalize_path: truein Envoy;merge_slashes on;in NGINX). - Header name keys in routing config are lowercase; header values are validated against an allowlist or normalised by a pre-routing transformer.
- Catch-all fallback route exists and routes to a
default_backendthat returns a structured 404 withX-Routing-Fallback: true. - Per-route request counters are exposed as Prometheus metrics (
gateway_route_hits_total{route_id="..."}) and dashboarded before rate-limit policies are attached. - Structured logs emit
route_id,matched_path_type,upstream_cluster, androute_lookup_latency_nsfor every request. - W3C
traceparentheader is injected at the routing hop; matchedroute_idis attached as a span attribute. - Deprecation response headers (
Deprecation,Sunset,Link) are configured on any route scheduled for removal. - Route config diffs are reviewed in the GitOps pipeline; a lint step rejects configs where a catch-all precedes specific routes.
- Regex patterns are anchored (
^...$), RE2-compatible, and load-tested with representative URI lengths.
FAQ
Does path matching or header matching take priority at the API gateway?
The answer depends on the gateway. Kong evaluates routes by a composite score: more-specific paths win, and header predicates add specificity. Envoy matches routes in declaration order within a virtual host, so a header-constrained route must be listed above the catch-all path rule. NGINX location blocks follow their own specificity hierarchy (exact > regex > prefix). Always test precedence explicitly — never assume the gateway infers intent from config order alone.
How should I handle case-insensitive header matching?
HTTP/1.1 headers are case-insensitive per RFC 9110, but many gateways store them in a canonical lowercased form internally. Envoy lowercases header names before matching. Kong normalises to lowercase as well. Always write header match keys in lowercase in your config and test with mixed-case clients to confirm the gateway normalises correctly before routing.
Can I combine path and header predicates in the same routing rule?
Yes, and doing so is a best practice for versioned APIs. Kong supports multiple match predicates (paths + headers + methods) on a single Route object — all must be satisfied for the route to match. Envoy combines a prefix/path/safe_regex matcher with headers conditions in the same route entry. This compound matching lets you route /api/orders differently depending on whether the request carries X-API-Version: v2 or not.
What is the performance impact of regex-based path matching?
Regex evaluation is O(n) per route entry in the worst case and can backtrack catastrophically on pathological patterns. Production gateways mitigate this with compiled finite automata (Envoy uses RE2, which guarantees linear time), radix-tree prefix lookups that discard non-matching branches before reaching regex routes, and route table limits. Prefer exact or prefix matches for hot paths, and confine regex to low-traffic or internal routes where compile-time safety guarantees still apply.
Related
- Routing by API Key vs JWT Claims — how credential type changes the routing predicate strategy
- Regex vs Prefix Route Matching Performance — the latency cost of regex predicates versus radix-tree prefix matching
- API Versioning Strategies — URI, header, and content-negotiation approaches to routing versioned traffic
- Multi-Tenant Routing Strategies — mapping tenant identifiers to isolated upstream pools
- Authentication Proxying & Token Validation — how routing interacts with the auth middleware layer
- Protocol Translation Patterns — translating between gRPC, REST, and other protocols at the gateway
- High Availability Topologies — route table consistency across multi-instance gateway deployments