Routes
Each route maps a URL prefix to a backend address (defined under backends). Optional fields control fingerprint headers, rate limits, path rewrites, and connection pooling. Dynamic — hot-reloadable.
Preserve host
Section titled “Preserve host”Top-level boolean (not inside [[routes]]). When true, the original client Host header is forwarded to the upstream; when false, the proxy substitutes Host with the backend address.
preserve_host: falsepreserve_host = falsePrefix matching
Section titled “Prefix matching”Routes are matched by URL prefix. The most specific (longest) prefix wins — routes are sorted by prefix length at config load time, so declaration order does not affect which route matches.
/api/v2 beats /api beats / for a request to /api/v2/users. Declaration order only matters for routes with identical prefixes, which are treated as load-balance candidates for round-robin selection.
Example:
routes: - prefix: "/api/v2" backend: "backend-a:9000" - prefix: "/api" backend: "backend-b:9000" - prefix: "/" backend: "backend-b:9000"[[routes]]prefix = "/api/v2"backend = "backend-a:9000"
[[routes]]prefix = "/api"backend = "backend-b:9000"
[[routes]]prefix = "/"backend = "backend-b:9000"A request to /api/v2/health matches /api/v2, not /api.
Path manipulation
Section titled “Path manipulation”Per route you can:
- Forward the path unchanged (omit
replace_path) - Strip the matched prefix so the backend sees a shorter path (
replace_path = "") - Rewrite the prefix to a different prefix (
replace_path = "/new/..."). Query strings are preserved.
There is no regular-expression routing, only prefix matching.
Strip: request /strip/users → backend receives /users:
routes: - prefix: "/strip" backend: "backend-a:9000" replace_path: ""[[routes]]prefix = "/strip"backend = "backend-a:9000"replace_path = ""Rewrite: request /old/data → backend receives /new/data:
routes: - prefix: "/old" backend: "backend-b:9000" replace_path: "/new"[[routes]]prefix = "/old"backend = "backend-b:9000"replace_path = "/new"Rewrite to a versioned API: request /v1/endpoint → backend receives /api/v1/endpoint:
routes: - prefix: "/v1" backend: "backend-a:9000" replace_path: "/api/v1"[[routes]]prefix = "/v1"backend = "backend-a:9000"replace_path = "/api/v1"Route fields (summary)
Section titled “Route fields (summary)”prefix,backend,fingerprinting,force_new_connection,replace_path- Optional
[routes.rate_limit]: see Rate limiting - Optional per-route
[routes.headers]: same shape as global[headers]— see Headers - Top-level
preserve_host: see above
Connection reuse
Section titled “Connection reuse”HTTP/1.1 and HTTP/2 reuse connections to backends where possible; pool settings are under [backend_pool] on the Backends page. force_new_connection = true bypasses that pool and opens a new TCP connection to the upstream for each request (and a new TLS session if the backend speaks TLS). That is only about the proxy→backend leg.
It does not change whether client fingerprints are available: TLS JA4 and HTTP/2 signatures come from the client→proxy connection; TCP SYN (x-tcp-p0f) is captured on the client→proxy path via eBPF and is unrelated to backend pooling. Forcing new backend connections does not re-capture the client’s SYN.
routes: - prefix: "/isolated" backend: "backend-a:9000" force_new_connection: true - prefix: "/api" backend: "backend-a:9000" force_new_connection: false[[routes]]prefix = "/isolated"backend = "backend-a:9000"force_new_connection = true
[[routes]]prefix = "/api"backend = "backend-a:9000"force_new_connection = falseforce_new_connection = true adds latency; use it only when you need per-request upstream connections.
See Configuration overview for the full config layout.