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:

  1. TLS termination — the gateway decrypts the request and exposes plaintext headers.
  2. Header normalisation — header names are canonicalised (typically lowercased), invalid or oversized values are rejected.
  3. Route lookup — the gateway walks a compiled route table, evaluating path and header predicates until one matches.
  4. Policy execution — the matched route triggers authentication, rate limiting, and transformation plugins in the middleware chain.
  5. 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 & Header Routing Decision Pipeline An incoming HTTP request passes through TLS termination and header normalisation, then route-table lookup. A matched route triggers policy execution before upstream dispatch. Unmatched requests receive a 404 response. Incoming Request TLS Termination + Header Norm. Route Table Lookup Match? Policy Execution Upstream Dispatch 404 Response Yes No

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.

Compound Route Matching — Path + Header + Method Decision tree showing how Envoy evaluates compound route predicates in declaration order. A request to POST /api/orders with x-api-version: v2 and x-feature-flag: beta-x traverses three decision nodes before matching the beta pool route. POST /api/orders x-api-version: v2 x-feature-flag: beta-x prefix /api/orders + x-feature-flag: beta-*? Yes orders_beta_pool timeout 10s No prefix /api/orders + x-api-version: v2? Yes orders_v2_cluster timeout 5s No prefix /api/orders (no header constraint) Match orders_v1_cluster timeout 5s No 404 / no route matched

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.
  • NGINXlocation block specificity is well-known but header-based branching via if blocks 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 identifier
  • matched_path_typeexact | prefix | regex
  • matched_headers — list of header predicates that contributed to the match
  • upstream_cluster — resolved backend target
  • route_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: true in 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_backend that returns a structured 404 with X-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, and route_lookup_latency_ns for every request.
  • W3C traceparent header is injected at the routing hop; matched route_id is 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.


Up: Advanced Routing & API Versioning