Routes & Domains
Routes are nested inside domain entries ([[domains]]). A domain groups a TLS certificate, per-domain headers and security policy, and one or more path-prefix routes. Dynamic: hot-reloadable.
Domains
Section titled “Domains”Each [[domains]] entry handles one hostname (exact or wildcard). Omit host for a catch-all that matches any host, including plain-HTTP traffic, IP literals, and localhost.
Named domain (HTTPS)
Section titled “Named domain (HTTPS)”domains: - host: "api.example.com" cert_path: "/config/certs/api.crt" key_path: "/config/certs/api.key" routes: - prefix: "/api" backend: "backend-a:9000" - prefix: "/" backend: "backend-b:9000"[[domains]]host = "api.example.com"cert_path = "/config/certs/api.crt"key_path = "/config/certs/api.key"
[[domains.routes]] prefix = "/api" backend = "backend-a:9000"
[[domains.routes]] prefix = "/" backend = "backend-b:9000"Wildcard domain
Section titled “Wildcard domain”domains: - host: "*.example.com" cert_path: "/config/certs/wildcard.crt" key_path: "/config/certs/wildcard.key" routes: - prefix: "/" backend: "backend-a:9000"[[domains]]host = "*.example.com"cert_path = "/config/certs/wildcard.crt"key_path = "/config/certs/wildcard.key"
[[domains.routes]] prefix = "/" backend = "backend-a:9000"Wildcard matching is one level only: *.example.com matches api.example.com but not a.b.example.com.
Catch-all domain (no host)
Section titled “Catch-all domain (no host)”Matches any host that has no explicit domain entry. Its certificate (if set) becomes the TLS default certificate (the fallback for connections that send no SNI or an unrecognized SNI). Omit cert/key for plain-HTTP only.
domains: - routes: - prefix: "/" backend: "localhost:3000"[[domains]] # no host = catch-all
[[domains.routes]] prefix = "/" backend = "localhost:3000"Host matching
Section titled “Host matching”The request host is resolved from the HTTP-layer authority for all protocol versions:
:authority (HTTP/2) or the Host header (HTTP/1.1). TLS SNI is not used for routing: it only selects the certificate.
Matching order:
- Exact:
"api.example.com" - Wildcard:
"*.example.com"(one level only) - Catch-all: the entry with no
hostkey, if present - No match → 421 Misdirected Request
Host values are lowercased at load and compared case-insensitively. The config is rejected if two domains share the same effective host or if more than one catch-all is defined.
Prefix matching
Section titled “Prefix matching”Within a domain, routes are matched by URL prefix. The most specific (longest) prefix wins: sorted 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. Identical prefixes are treated as load-balance candidates (round-robin).
domains: - host: "api.example.com" routes: - prefix: "/api/v2" backend: "backend-a:9000" - prefix: "/api" backend: "backend-b:9000" - prefix: "/" backend: "backend-b:9000"[[domains]]host = "api.example.com"
[[domains.routes]] prefix = "/api/v2" backend = "backend-a:9000"
[[domains.routes]] prefix = "/api" backend = "backend-b:9000"
[[domains.routes]] prefix = "/" backend = "backend-b:9000"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 value (
replace_path = "/new/...")
Query strings are preserved. There is no regular-expression routing, only prefix matching.
Strip: /strip/users → backend receives /users:
domains: - host: "api.example.com" routes: - prefix: "/strip" backend: "backend-a:9000" replace_path: ""[[domains]]host = "api.example.com"
[[domains.routes]] prefix = "/strip" backend = "backend-a:9000" replace_path = ""Rewrite: /old/data → backend receives /new/data:
domains: - host: "api.example.com" routes: - prefix: "/old" backend: "backend-b:9000" replace_path: "/new"[[domains]]host = "api.example.com"
[[domains.routes]] prefix = "/old" backend = "backend-b:9000" replace_path = "/new"Fingerprinting gate
Section titled “Fingerprinting gate”The fingerprinting field controls header injection for fingerprint data. Capture is always driven by the global [fingerprint] static config.
Resolution order: route → domain → global default (true).
domains: - host: "api.example.com" fingerprinting: true # domain-level default routes: - prefix: "/internal" backend: "backend-a:9000" fingerprinting: false # override for this route[[domains]]host = "api.example.com"fingerprinting = true
[[domains.routes]] prefix = "/internal" backend = "backend-a:9000" fingerprinting = falsePreserve host
Section titled “Preserve host”Top-level boolean. When true, the original client Host header is forwarded to the upstream. When false (default), the request is forwarded with the backend address as its authority. Dynamic.
preserve_host: falsepreserve_host = falseConnection reuse
Section titled “Connection reuse”force_new_connection = true bypasses the connection pool and opens a fresh TCP+TLS connection per request to the upstream. Use it when you need a new TLS handshake for each request. Adds latency; avoid in high-throughput routes.
It does not affect client fingerprint capture: TLS JA4 and HTTP/2 signatures come from the client→proxy connection, and TCP SYN is captured via eBPF at the client→proxy edge.
domains: - host: "api.example.com" routes: - prefix: "/isolated" backend: "backend-a:9000" force_new_connection: true - prefix: "/api" backend: "backend-a:9000" force_new_connection: false[[domains]]host = "api.example.com"
[[domains.routes]] prefix = "/isolated" backend = "backend-a:9000" force_new_connection = true
[[domains.routes]] prefix = "/api" backend = "backend-a:9000" force_new_connection = falsePool settings are under [backend_pool].
Route fields (summary)
Section titled “Route fields (summary)”prefix,backend,replace_path,fingerprinting,force_new_connection- Optional
security: per-routeip_filter,rate_limit,headers(whole-block replaces). See Security and Rate limiting - Optional
[domains.routes.headers]: same shape as global[headers]. See Headers
Domain fields (summary)
Section titled “Domain fields (summary)”host(exact,*.wildcard, or absent for catch-all)cert_path,key_path: TLS certificate for this domain (omit for plain HTTP)fingerprinting: domain-level gate (resolved against route-levelfingerprinting)headers: domain-level header manipulation (same shape as global[headers])security: per-domain overrides forip_filter,rate_limit,headers(whole-block replaces)routes: array of route entries (see above)
See Configuration overview for the full config layout.