Skip to content

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.

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: false

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"

A request to /api/v2/health matches /api/v2, not /api.

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: ""

Rewrite: request /old/data → backend receives /new/data:

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"
  • 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

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

force_new_connection = true adds latency; use it only when you need per-request upstream connections.

See Configuration overview for the full config layout.