Skip to content

Routing Rules Reference ​

Routes belong to sites. A request first selects a site from the incoming listener and hostname, then evaluates that site's shared route list.

Configure routes from Proxy → Sites. Open or create a site and use its route section to add, edit, clone, order, enable, disable, or delete routes. New routes are bound to the open site automatically.

Existing standalone listener routes may remain active after an upgrade until they complete the revision-bound migration described in Sites. They cannot be created through the normal site workflow.

Route fields and defaults ​

FieldRule
site_idRequired for site route creation. It identifies the owning site.
priorityLower numbers evaluate first. Route ID breaks a tie.
path_prefixMust start with / when set. An empty path matches within the owning site.
path_security_modeDefaults to strict. Use allow_encoded_separators only for an upstream requiring encoded separators, repeated slashes, or semicolon path parameters, accepting the parser differences described below.
access_policy_idOptional reusable identity policy. Requests fail closed when its provider or policy is unavailable.
actionforward or redirect; defaults to forward when unspecified.
target_load_balancingDefaults to round-robin for forward target pools.
is_defaultHandles path misses inside the owning site. A Site can have one default route.
redirect_status_code301, 302, 307, or 308; defaults to 302 when unset.
redirect_preserve_path_suffixDefaults enabled.
redirect_preserve_queryDefaults enabled.

Hostname matching belongs to the site. A Site-owned route does not define a separate host pattern or listener scope. All Serve listener assignments use the same route paths and targets.

Target fields ​

Forward routes require at least one enabled target.

FieldRule
target_typeproxy or static.
urlRequired for proxy targets. Must be an HTTP or HTTPS origin.
transportdirect or agent for proxy targets.
agent_selector.match_labelsRequired for agent targets. All labels must match the same enabled agent.
priority_groupLowest available group is selected; higher groups are failover.
weight1 to 1000000; defaults to 100.
agent_load_balancingAgent selection policy for agent targets.
tls_skip_verifyDefaults off. Disables certificate-chain, hostname, and expiry checks, allowing origin impersonation even on a private network. Use only temporarily for diagnosis; restore verification with a trusted certificate.
upstream_response_header_timeout_millisDefaults to 60000.
upstream_request_headersOrdered headers added to upstream requests. Sensitive values are write-only and returned as saved-state metadata.
upstream_basic_authOptional upstream username and write-only password.
health_checkOptional method, path, interval, timeout, thresholds, and expected status range.
static_status_codeLocal status returned by a static target.
static_response_headersHeaders returned by a static target.
static_response_body_modeInline body or generic response template.
static_response_template_idGeneric template selected in template mode.

Static targets use static_status_code, static_response_headers, and either inline body text or a generic response template.

Agent labels are configured in the Edit agent drawer. Labels under p2pstream.io/ are system-owned. Use p2pstream.io/agent-id=<agent public ID> for exact-agent targeting. Empty selector values match only agents with the same empty label value.

Sensitive header values and basic-auth passwords remain write-only. An ordinary saved edit preserves their saved state. Cloning cannot copy masked values: the clone retains enabled basic authentication but blocks saving until its password is re-entered, and each masked sensitive header needs a new value. A cloned route also starts with its default-route flag cleared so it cannot silently replace the site's current default.

Path matching and defaults ​

Routes are sorted by priority ascending, then route ID ascending. p2pstream selects the first enabled non-default route whose path prefix matches. If none matches, it selects the site's enabled default route. With no match, the selected site returns 404; it does not try another site.

PriorityPathResult
10/apiChecked first inside the selected site.
20/Broad path fallback inside the same site.
defaultemptyUsed only when no non-default path route matches.

A listener's default site handles an unmatched hostname. A Site's default route handles an unmatched path. These are independent settings.

Path security ​

Every route has a path security mode:

ModeBehavior
strictRejects encoded path separators such as %2F or %5C, duplicate slashes, and semicolon path parameters (including encoded forms), before WAF, rate limits, access control, traffic shaping, cache, or forwarding.
allow_encoded_separatorsAllows singly encoded separators, duplicate slashes, and semicolon path parameters for upstream compatibility. Requests with encoded separators bypass shared cache. The global checks below still apply.

Public listeners reject NUL bytes (literal or percent encoded), raw literal backslashes, and . or .. segments using either slash or backslash separators in both modes. Traversal remains rejected when a segment has a semicolon parameter suffix, such as /files/..;session=x/admin, or when decoding an encoded separator exposes traversal. For example, /files/a%5C..%5Cadmin and /files/a%00b return 400 in either mode. NUL checks inspect the request-target path, Go's decoded path, raw escaped path, and each inspected decoding layer; query bytes are outside these path checks.

Strict routes return 400 for /app;jsessionid=X/page, /app%3Bjsessionid=X/page, /app//page, //app/page, and /%2Fapp/page. An explicitly selected compatibility route accepts these legitimate upstream forms and forwards their escaped path bytes unchanged.

Nested percent encodings are also rejected in both modes: Go's once-decoded path must not contain another valid %HH escape. This includes nested separators (%252F), dots (%252e), and ordinary prefix characters (/%2561dmin). Literal percent filenames such as /files/100%25.txt, spaces such as /files/a%20b.txt, and dots inside ordinary names such as /files/v1%2e2/readme remain allowed. A literal filename containing a percent followed by two hexadecimal digits must use another naming scheme. Inspection bounds each path representation to eight decoding passes and rejects unresolved nesting, bounding work even for deeply wrapped escapes.

Accepted paths retain their original escaped bytes when forwarded; validation does not normalize one path for matching and send a different path upstream. Routing, WAF, rate-limit, and traffic-shaper path matching use Go's once-decoded request path: an encoded slash becomes a route boundary, while an encoded backslash remains a literal character. A selected strict route rejects a prohibited form immediately; it never retries a compatibility fallback. A prohibited form with no matching route also returns 400.

Compatibility mode explicitly accepts parser differences: an origin that collapses slashes, strips semicolon parameters, or treats encoded backslashes as boundaries can interpret //admin, /admin;session=x, or /admin%5Cpanel as protected resources even though p2pstream selects a compatibility catchall. A strict /admin route beside an explicit compatibility catchall does not protect those aliases. Avoid route-specific authentication, authorization, or cache eligibility differences on compatibility catchalls sharing that origin; enforce equivalent policy for every alias, preferably at the origin as well. Only requests containing encoded separators automatically bypass shared cache; literal duplicate-slash and semicolon compatibility requests do not get that automatic bypass.

These checks are byte-based and do not guarantee agreement with every origin's Unicode handling. Legitimate Unicode filenames remain supported. Origins that fold Unicode slash/dot lookalikes or accept invalid or overlong UTF-8 as ASCII separators can introduce additional aliases even in strict mode. Configure the origin to reject invalid UTF-8 and avoid such normalization, or apply equivalent authorization and cache policy across the resulting aliases.

Runtime effects ​

p2pstream performs a lightweight site and route match before WAF, rate limits, access control, and traffic shapers to determine path security and access policy. This pass does not select a target or advance load-balancer state. Target selection still happens after those policy layers.

An assigned access policy applies to forward, static, and redirect routes. Protected routes bypass shared cache. See Identity-Aware Access.

At request time, disabled targets, unhealthy targets, invalid target configurations, and unavailable agent selector matches are skipped. p2pstream selects from the lowest available priority group. If no target is usable, the response is 503.

When health checks are enabled, connection and timeout failures mark the selected target or target-agent path temporarily unhealthy for later requests. The original request is not replayed to another target.

After a route and target are selected, cache rules may serve eligible proxy GET or HEAD requests. Redirect routes and static targets are not cached.

Operations documentation for self-hosted p2pstream deployments.