494 lines
34 KiB
Markdown
494 lines
34 KiB
Markdown
> 0.7 adds graphical locations, Basic Auth, cumulative IP ACL editing and a
|
|
> WebSocket toggle. See [host access and locations](VHOST_ACCESS.md).
|
|
|
|
# Virtual hosts, frontend bindings and backend pools
|
|
|
|
This is the source implementation contract. Rust compilation and runtime acceptance
|
|
were not performed in the packaging environment; see TEST_REPORT.md.
|
|
|
|
## Backend quick-view in Vhosts
|
|
|
|
Click the **Backends** count or the **Status** tile of a Vhost to open its
|
|
backend list. Each member displays the configured ID, origin host/IP and port,
|
|
HTTP/HTTPS scheme and HTTP mode, current reported health and `last_status`.
|
|
`last_status` is the most recent HTTP code from a proxied request; zero/no
|
|
member means no known HTTP response, not a failed probe. DNS origins show the
|
|
configured hostname, as the status API does not report the resolved IP.
|
|
The panel updates from the existing status feed without initiating requests,
|
|
and supports Escape and click-outside dismissal.
|
|
|
|
## One vhost, one backend pool
|
|
|
|
A `vhosts` entry owns an `id`, `enabled`, `frontend`, `source`, `backends`, `load_balancing`,
|
|
`routes`, optional `acl`, `headers` and `security_headers`. `source` defaults to Reverse Proxy
|
|
for older documents; it can instead reference an independently configured Web Service. See
|
|
[WEB_SERVER.md](WEB_SERVER.md). The pre-production top-level
|
|
routing and inline-listener formats are no longer accepted. Unknown JSON fields are
|
|
rejected. The examples are complete documents, not JSON merge patches:
|
|
|
|
- `examples/vhosts-private.json`: explicit loopback IP, two existing HTTP origins,
|
|
adaptive balancing, HTTP health checks, keepalive, no cache or cookie affinity.
|
|
- `examples/vhosts-balanced.json`: explicitly public HTTP 80 and TLS 443, cookie
|
|
affinity and a deliberately short HSTS value; requires a trusted, imported `app`
|
|
certificate/key and real origins. It does not create those services or files.
|
|
|
|
In the examples, both origins need `/health` to return a quick 2xx response. Adjust
|
|
that path/status range for the real service. New pools start quarantined until
|
|
`rise` successful checks; an unavailable or misconfigured check deliberately causes
|
|
503 instead of sending traffic to an unverified member. `mode: off` opts out.
|
|
|
|
The editor includes frontends, members, balancing, health, connection reuse, sticky
|
|
sessions and response protection. Custom locations have graphical match/action/rewrite/ACL/header
|
|
controls plus synchronized Advanced JSON for automation. Unsaved drafts survive translation/SSE refreshes. A stale draft must be
|
|
reloaded before saving: the panel does not silently merge competing revisions.
|
|
|
|
Existing vhost IDs can be renamed in the dashboard as long as the new ID is valid and
|
|
unique. The rename keeps the configured frontend hostnames, routes and backend IDs, but it
|
|
intentionally starts a new vhost runtime identity for metrics/log attribution and current
|
|
health/traffic history. Because the vhost configuration participates in cache compatibility,
|
|
a rename also rotates that compatibility identity and invalidates incompatible cached objects.
|
|
The editor warns before applying the rename.
|
|
|
|
## Internal error routing
|
|
|
|
The main vhost `source` may define `error_fallback`, and each Custom location may do the same. A fallback references the `id` of an enabled `internal` Custom location in the same vhost. Supported statuses are `403`, `404`, `500`, `502`, `503` and `504`; omitted status lists default to `404`. Internal targets are not reachable by ordinary path matching.
|
|
|
|
Configured upstream/Web Server failures are intercepted before final response/cache publication. The original request method, URI/query and context are internally dispatched to the target, which then follows normal vhost/location processing. Validation rejects fallback cycles and runtime dispatch is depth-limited. See [Custom Locations](CUSTOM_LOCATIONS.md) for the active internal-fallback behavior.
|
|
|
|
## Per-backend health monitoring (0.29.7)
|
|
|
|
Each backend has an independent `health_check_enabled` switch (default `true`).
|
|
Setting it to `false` excludes that member from both active health probes and
|
|
passive health ejection, **without** disabling the member for a named location.
|
|
This is useful for an internal 404 handler configured with `pool_eligible: false`
|
|
and a location pinned through `backend_id`: the 404 handler never receives
|
|
ordinary balanced traffic and does not need health monitoring.
|
|
|
|
A backend may also define `health_check` with the same fields as
|
|
`load_balancing.health_check` to use its own TCP/HTTP/off probe policy, timing,
|
|
path, Host and status ranges. Omitting it inherits the vhost policy. A custom
|
|
HTTP health check without `host` inherits the vhost Host default even when the
|
|
vhost's main health check is TCP. When monitoring is disabled, the backend is
|
|
assumed available; connection errors during real requests are still counted and
|
|
logged, so monitoring off is not a guarantee of backend reachability.
|
|
|
|
## Frontend selection and private IP isolation
|
|
|
|
`frontend.hosts` is a normalized hostname-pattern list. Exact hostnames and a single
|
|
leading wildcard such as `*.example.com` are supported; regex and mid-label wildcards
|
|
are not. Routing tries the exact hostname first, then the longest matching wildcard.
|
|
This means `api.example.com` can have its own vhost while `*.example.com` handles other
|
|
subdomains on the same listener without stealing the exact route.
|
|
`frontend.listener_ids` references named `listeners.endpoints` configured under
|
|
Settings. A listener owns its IP:port, TLS flag and H2/H3 transport capabilities;
|
|
a vhost never stores a listening address directly. For a new dashboard vhost, the
|
|
editor preselects both an enabled HTTP listener on port 80 and an enabled TLS listener
|
|
on port 443 when they exist (public-scope listeners are preferred). Existing vhost
|
|
bindings are never replaced by that convenience default.
|
|
|
|
`force_ssl` defaults to false. When enabled, any cleartext request that matches this
|
|
vhost receives HTTP `308 Permanent Redirect` to the same normalized hostname and
|
|
original path/query. At least one selected plain HTTP listener and one selected TLS
|
|
listener are required so there is both an ingress to redirect and an HTTPS target. If
|
|
several TLS ports are selected, port 443 is preferred; otherwise the selected TLS target
|
|
port must be unique.
|
|
|
|
`www_redirect` defaults to false. When enabled, `www_redirect_target` selects an exact DNS hostname from `frontend.hosts` as canonical (default: the first hostname for backwards compatibility). RustProxy also claims its direct companion (`example.com` <-> `www.example.com`) on the same listeners and returns `308 Permanent Redirect` from that companion to the first hostname while preserving path/query. Wildcards and IP literals cannot be the canonical hostname for this feature. The companion also participates in vhost certificate assignment, but the selected certificate must still contain both DNS names in its SANs. When `force_ssl` is also enabled, an HTTP companion request redirects directly to the HTTPS canonical URL instead of creating two redirect hops.
|
|
|
|
`forward_client_ip` defaults to true. Normal vhosts therefore receive the sanitized
|
|
`Forwarded`, `X-Forwarded-For` and `X-Real-IP` client identity generated by RustProxy.
|
|
Set it to false for applications whose own trusted-proxy/bypass logic must see the
|
|
RustProxy TCP peer instead. Only the backend application request loses those three
|
|
client-IP headers; ACLs, GeoIP, rate limiting and Tinyauth still use the effective client
|
|
address, and origin metadata such as `X-Forwarded-Proto`, `X-Forwarded-Host`,
|
|
`X-Forwarded-Port` and `X-Forwarded-URI` remains available to the backend.
|
|
|
|
`websockets` enables WebSocket handling for the vhost. The dashboard defaults new WebSocket
|
|
vhosts to the nginx-compatible HTTP/1.1 Upgrade transport. `websocket_legacy_support: true` is
|
|
a separate per-vhost compatibility switch for older appliances and embedded servers. It affects
|
|
only WebSocket handshakes: RustProxy forces an HTTP/1.1 upstream WebSocket hop, uses the configured backend authority as upstream `Host`,
|
|
rewrites `Origin` to the matching upstream scheme + backend authority, and tolerates legacy `101 Switching Protocols` response
|
|
framing while keeping the `Sec-WebSocket-Accept` proof and offered subprotocol checks strict.
|
|
Ordinary HTTP requests are unchanged. `websocket_rfc8441: true` and `websocket_rfc9220: true` are
|
|
separate opt-ins for HTTP/2 and HTTP/3 Extended CONNECT and are valid only when WebSocket
|
|
support is enabled. They are presented together in the vhost editor.
|
|
RFC 8441 requires an H2-capable selected listener (TLS ALPN or cleartext h2c prior knowledge).
|
|
Because the RFC 9220 advertisement is QUIC-connection scoped, one opted-in vhost causes the
|
|
shared QUIC listener to advertise Extended CONNECT, while request routing still rejects RFC
|
|
9220 on non-opted-in vhosts.
|
|
|
|
`http2_enabled` and `http3_enabled` are per-vhost opt-outs (both default true). The
|
|
panel presents the two switches together and enables each only when at least one
|
|
selected listener provides that protocol. For TLS H2, ClientHello/SNI selects H2+HTTP/1.1 or HTTP/1.1-only ALPN for
|
|
the matched vhost. H3 remains a shared UDP listener capability; disabling it on a
|
|
vhost removes Alt-Svc and rejects H3 for that host.
|
|
|
|
Each vhost can optionally set `tls_policy` with `tls12`, `tls13` and a list of rustls
|
|
cipher-suite names. Omitting it inherits `tls.policy` from Settings. The override is
|
|
selected from ClientHello/SNI before the TCP TLS handshake, so a vhost can for example
|
|
run TLS 1.2 only while another frontend continues to use the global TLS 1.2/1.3 policy.
|
|
At least one protocol version and a compatible effective cipher suite are required.
|
|
Because HTTP/3 shares QUIC TLS on the UDP listener and requires TLS 1.3, a vhost with
|
|
HTTP/3 enabled must use the same TLS policy as the global setting. Disable HTTP/3 on
|
|
that vhost before using a differing per-vhost TLS policy.
|
|
|
|
Routing checks Host/HTTP authority, actual accepted local IP, local port and actual
|
|
TLS state. Neither `Forwarded` nor `X-Forwarded-*` can create frontend access. A
|
|
request for a private hostname on a different local IP is not routed to that host.
|
|
Unknown or unmatched hosts return 404.
|
|
|
|
Bindings must be local addresses assigned to the OS. The process does not add IPs,
|
|
manage routes, configure firewall rules, acquire virtual IPs or run Keepalived/VRRP.
|
|
A missing IP, occupied port or permission error aborts startup without wildcard
|
|
fallback. Low ports may need deployment capabilities; see `../deploy/README.md`.
|
|
|
|
Identical or same-family wildcard sockets are consolidated only when the named
|
|
listener transport capabilities agree. Vhost routing restrictions are not
|
|
consolidated: a wildcard TCP listener can accept on a public IP while still rejecting
|
|
a private vhost there. Per-vhost H2 is narrowed by SNI and therefore does not require
|
|
a second TCP socket. IPv4/IPv6 wildcard overlap is rejected; use explicit addresses.
|
|
Native IPv4 binds are required instead of mapped IPv6 literals. The administration
|
|
port is reserved even on a different IP.
|
|
|
|
IP isolation is not client authentication. A firewall, router, NAT or preceding
|
|
proxy can forward external clients to the private destination IP; RustProxy sees
|
|
that local destination and the connecting peer. Use network ACLs, firewall rules
|
|
and application authentication too. Trusted forwarding headers have a separate explicit CIDR policy (see
|
|
INTEGRATIONS_040.md); PROXY protocol is not implemented. Interface changes require config
|
|
reload/restart to refresh the upstream loop-guard's local-address enumeration.
|
|
|
|
The same literal hostname pattern may occur only where its listener bindings remain
|
|
unambiguous. Exact and wildcard patterns are distinct ownership keys, so an exact host
|
|
and its parent wildcard may coexist. Disabled vhosts reserve their patterns and are
|
|
still validated, but create no pool or additional active listener.
|
|
|
|
## Paths, policies and migration
|
|
|
|
`routes` contains per-vhost paths: `id`, match fields, `enabled`, `source`, nullable `cache`,
|
|
optional `acl_profile_id`, inline `acl`, `country_access`, `method_policy`, `headers` and `backend_id`. `source` selects Reverse Proxy or a reusable Web Service;
|
|
`backend_id` is valid only for Reverse Proxy. An empty array leaves the main vhost source as the fallback. Longest
|
|
segment-boundary prefix wins. Origins receive the original path/query; prefixes
|
|
are not stripped. IDs must use ASCII letters, digits, hyphens and underscores.
|
|
Host method policy, reusable ACL profiles, vhost ACL/country policy and route method/profile/inline
|
|
ACL/country policies are cumulative before pool selection/DNS/cache. A vhost and each route can
|
|
attach a reusable `acl_profile_id`; profiles may contain an IP ACL plus a country block/allow-only
|
|
rule. Inline controls remain independent and cannot be weakened by a profile. Method-policy denial
|
|
returns 405; allow-list mode also emits `Allow`. Their client identity is the
|
|
validated effective peer address, not an untrusted forwarding header.
|
|
|
|
There is no legacy routing UI/runtime or automatic importer in 0.8.0. Development
|
|
databases using the old top-level routes or inline frontend listeners must be reset
|
|
or migrated explicitly before startup. An omitted path backend ID selects from the
|
|
balanced pool; a specified ID must belong to the same vhost and does not fail over to a
|
|
different application's member. Imported health checks start OFF. Ambiguous names,
|
|
unreferenced old backends and invalid/oversized documents stop migration before
|
|
saving. See MIGRATION.md before updating an existing installation.
|
|
|
|
## Backend members and connection reuse
|
|
|
|
Each member has `id`, HTTP(S) origin `url`, `weight` (1..256), `enabled` (true),
|
|
`backup` (false), `drain` (false), `allow_private` (false),
|
|
`accept_local_listener_risk` (false), `preserve_host` (true), `protocol` (`http1`),
|
|
`address_allowlist`, optional `tls_server_name`, `tls_verify` (false) and optional
|
|
`tls_ca_pem`. `tls_server_name` is an HTTPS-only DNS hostname
|
|
used for SNI and, while verification is enabled, certificate hostname verification;
|
|
DNS/TCP still targets the configured `url`. `tls_verify` defaults to false. HTTPS connections remain encrypted, but backend certificate authentication is skipped until an administrator explicitly sets `tls_verify: true`; this includes compatibility with legacy certificate encodings that modern webpki validation rejects. Address/DNS policy remains enforced; local-listener self-targeting has only the explicit risk exception described below. `tls_ca_pem` is ignored while verification is disabled.
|
|
Backend IDs are unique inside a vhost. URLs cannot contain credentials, paths,
|
|
queries or fragments.
|
|
Every DNS answer and every configured literal must pass the same upstream policy
|
|
for health probes and forwarding. Active, pending and prior-snapshot frontend
|
|
addresses and local administration are guarded against self-proxying by default.
|
|
For an intentional local target such as `http://127.0.0.1:9090`, enable both
|
|
`allow_private: true` and `accept_local_listener_risk: true`. The latter is a narrow,
|
|
explicit acceptance of loop/control-plane exposure risk; metadata/link-local and other
|
|
special-address blocks are not bypassed. In the dashboard this risk checkbox is hidden
|
|
unless the entered backend URL directly targets an administration listener reported by
|
|
persisted configuration or live status. Startup-owned/locked administration sockets are
|
|
therefore handled too. On the same administration port, loopback spellings such as
|
|
`localhost`, `127.0.0.1` (and the rest of `127.0.0.0/8`) and `::1` are treated as
|
|
equivalent for showing the explicit risk control, so it is not presented as a normal
|
|
backend option.
|
|
|
|
A vhost can independently enable two proxy-owned response diagnostics:
|
|
|
|
- `backend_header: true` -> `X-RustProxy-Backend: <backend-id>`,
|
|
- `backend_timing_header: true` -> `X-RustProxy-Backend-Time: <milliseconds>ms`.
|
|
|
|
The timing value covers the origin request through receipt of the backend response
|
|
headers; it is not full response-body duration. Both proxy-owned names are protected
|
|
from custom header rules and any upstream values are removed before RustProxy optionally
|
|
writes its own value. The former `X-Rust-Proxy-Backend-Time` spelling is also stripped
|
|
as a legacy lookalike but is never emitted. Responses served without contacting an
|
|
origin do not expose a selected backend or stale origin timing.
|
|
`timing_headers` remains the separate opt-in `Server-Timing` diagnostic.
|
|
|
|
`keepalive` controls **connection pooling**, not an HA service:
|
|
|
|
| Field | Default | Accepted range |
|
|
|---|---|---|
|
|
| `enabled` | true | Boolean |
|
|
| `idle_timeout_seconds` | 30 | 1..3600 |
|
|
| `max_idle_per_host` | 4 | 1..64 |
|
|
| `tcp_keepalive_seconds` | 30 | 1..3600 |
|
|
| `pool_recycle_requests` | 25000 | 0 (off) or 1000..10000000 |
|
|
| `pool_recycle_seconds` | 3600 | 0 (off) or 60..604800 |
|
|
| `http1_max_buffer_bytes` | 131072 | 8192..1048576 |
|
|
|
|
These configure HTTP idle reuse and TCP keepalive; HTTP/2 also uses pings for active
|
|
connections, with a 10-second ping timeout and no pinging solely to keep idle
|
|
connections alive. `pool_recycle_requests` and `pool_recycle_seconds` bound the lifetime
|
|
of each backend Hyper client generation. Recycling swaps only that backend client/pool;
|
|
it does not reload the vhost, purge cache or intentionally abort active exchanges. The
|
|
request counter is per backend client generation, not per TCP connection. Setting either
|
|
recycle value to `0` disables that trigger independently. `http1_max_buffer_bytes` bounds
|
|
Hyper's adaptive upstream HTTP/1 read buffer per connection; it does not apply to HTTP/2.
|
|
Compatible backend clients are retained across unrelated runtime snapshot rebuilds, so
|
|
generation age, request count and rotation totals remain continuous. A changed backend
|
|
origin, address policy, keepalive settings or connect timeout creates a fresh client
|
|
generation state instead of reusing an incompatible pool.
|
|
Disabling reuse sets the idle pool limit to zero and disables keepalive probes. The idle
|
|
limit is not a separate per-backend active-request cap; global connection/exchange bounds
|
|
still apply. HTTP health probes use separate no-reuse HTTP/1.1 connections.
|
|
|
|
Backend upstream `protocol` has four modes:
|
|
|
|
| UI mode | Serialized value | Backend behavior |
|
|
|---|---|---|
|
|
| **HTTP/1.1** | `http1` | HTTP/1.1 only over HTTPS or HTTP; default for new backends |
|
|
| **Auto** | `auto` | HTTPS: prefer H2 and negotiate H1 fallback with ALPN before sending a request; HTTP: H1 only, without H2C probing |
|
|
| **HTTP/2 compatible** | `http2` | **Unchanged 0.32.2 behavior**: HTTPS offers H2/H1 via ALPN; plaintext HTTP uses H2C prior knowledge (no H1 downgrade on HTTP) |
|
|
| **H2 only** | `h2` | HTTPS: require H2 via ALPN; reject H1-only peers. HTTP: prior-knowledge H2C, no fallback |
|
|
|
|
**0.32.2 -> 0.32.3 compatibility:** saved `"protocol": "http2"` retains its existing
|
|
HTTPS ALPN fallback and cleartext H2C behavior. No conversion, migration or
|
|
administrator action is needed to preserve the old selection. Choose the new
|
|
`"protocol": "h2"` only when H2 must be enforced. The editor describes the
|
|
transport for the selected backend URL; a descriptive notice next to compatible
|
|
HTTPS `http2` is informational, not a migration warning.
|
|
|
|
HTTPS Auto negotiation selects the wire protocol **before sending the request**.
|
|
RustProxy does not replay an ambiguous failed POST/PATCH/DELETE through H1, and
|
|
does not interpret backend 4xx/5xx responses as a reason to switch protocols.
|
|
Existing pool reuse, keep-alive, client generation rotation and the independent
|
|
HTTP/1.1 WebSocket Upgrade / H2 Extended CONNECT paths are retained.
|
|
HTTP health probes continue to use HTTP/1.1 independently of ordinary requests;
|
|
H2C-only peers can use TCP probes. A health probe that needs a virtual-host
|
|
name must configure `health_check.host` separately; `preserve_host` affects
|
|
visitor requests, not independent health probes.
|
|
|
|
**Since 0.32.4:** all four upstream modes allow `preserve_host: true`.
|
|
With HTTP/2, `:authority` follows the selected HTTP Host instead of the backend
|
|
IP; with HTTP/1.1 it remains the regular Host. TCP/DNS always target the
|
|
configured backend URL and still enforce the address and local-listener
|
|
policies; `tls_server_name` independently controls HTTPS SNI and certificate
|
|
identity, not the visitor Host. HTTPS backend certificate verification defaults
|
|
to OFF when `tls_verify` is omitted. Turn on verification and configure
|
|
`tls_server_name` / `tls_ca_pem` for a private DNS certificate; a valid IP-SAN
|
|
certificate can instead authenticate an IP backend without a DNS name.
|
|
The form's HTTP/1.1 troubleshooting preset is optional; it is not required
|
|
for Auto/H2, private origins or Host preservation.
|
|
|
|
## Scheduling
|
|
|
|
| `algorithm` | Decision |
|
|
|---|---|
|
|
| `round_robin` | Smooth weighted round-robin |
|
|
| `least_connections` (default) | Lowest `(active exchanges + 1) / effective weight` |
|
|
| `least_traffic` | Lowest recent EWMA payload rate plus active-exchange allowance, divided by effective weight |
|
|
| `adaptive` | Combines active exchanges, EWMA time-to-headers and payload rate, divided by effective weight |
|
|
| `ip_hash` | Weighted rendezvous hash of vhost/member and canonical actual peer IP |
|
|
|
|
An active exchange spans upload, response body and upgraded tunnel, not just the
|
|
wait for response headers. Slow-start gradually increases effective weight after
|
|
initial success/recovery (`slow_start_seconds`: 10, allowed 0..300; 0 disables it).
|
|
IP hashing uses configured weights instead of slow-start to keep assignments stable.
|
|
These are explicit heuristics, not workload predictions or benchmark-backed claims.
|
|
No remote host CPU measurement is used by the scheduler.
|
|
|
|
Only enabled, healthy, non-draining primary members receive new assignments. If
|
|
none exists, eligible backups are used. All unavailable means 503 with Retry-After;
|
|
there is no fallback into another vhost. Balanced routes may retry a body-free `GET`,
|
|
`HEAD` or `OPTIONS` on another healthy member according to `load_balancing.retry`:
|
|
|
|
| Retry field | Default | Accepted range / meaning |
|
|
|---|---|---|
|
|
| `max_retries` | 1 | 0..3; zero disables failover retry |
|
|
| `retry_connect_errors` | true | Retry connection establishment failures |
|
|
| `retry_timeouts` | true | Retry the response-header timeout |
|
|
| `retry_statuses` | `[502,503,504]` | Up to 8 HTTP 5xx statuses |
|
|
|
|
A backend already attempted in the same exchange is excluded from later attempts.
|
|
Path-pinned routes and WebSocket handshakes are not retried. POST/PUT/PATCH and any
|
|
request with a body are never automatically replayed. Failed attempts still feed the
|
|
normal passive-health policy and retry counters/logs; successful failover updates sticky
|
|
affinity to the backend that actually answered.
|
|
|
|
## Health checks and recovery
|
|
|
|
| Field | Default | Accepted range / meaning |
|
|
|---|---|---|
|
|
| `mode` | `tcp` | `off`, `tcp`, `http` |
|
|
| `path` | `/` | Leading slash, up to 1024 bytes; no authority/fragment |
|
|
| `host` | null | Optional validated HTTP Host override; otherwise the first exact vhost DNS hostname is used |
|
|
| `interval_ms` | 5000 | 250..300000 |
|
|
| `timeout_ms` | 2000 | 50..60000; no greater than interval |
|
|
| `slow_response_ms` | 1500 | 0 disables; otherwise no greater than timeout |
|
|
| `fall` / `rise` | 3 / 2 | 1..20 consecutive failed/successful checks |
|
|
| `expected_status_min` / `max` | 200 / 399 | Inclusive, ordered HTTP status range within 100..599 |
|
|
|
|
TCP checks verify that an approved origin address accepts a connection. HTTP checks
|
|
perform GET on the configured path, without redirects, cookies or authorization.
|
|
They use HTTP/1.1, including verified TLS when appropriate, and measure time to
|
|
headers, not the full body. When `health_check.host` is empty, RustProxy automatically uses the first exact DNS hostname configured on the vhost so an IP-address backend can still route the probe through its name-based virtual host. If no exact DNS hostname exists, the existing backend-host fallback remains. Set `health_check.host` only when the probe must use a different Host value. For an h2c-only service, use TCP checks. Slow probes
|
|
count as active failures. Active TCP/HTTP health is independent from `passive_health`.
|
|
|
|
`passive_health.enabled` defaults to `false`. With that default, normal user traffic
|
|
never changes the shared backend healthy/unhealthy state: HTTP 5xx, connection errors,
|
|
timeouts, response-body I/O failures and slow responses are still counted/logged but
|
|
cannot quarantine the member. When explicitly enabled, the administrator can choose
|
|
`count_connect_errors`, `count_timeouts`, `count_io_errors`, `count_http_5xx` and
|
|
`count_slow_responses`, with optional passive `fall`/`rise` overrides. Missing overrides
|
|
inherit the active health thresholds. Active and passive streaks are separate, so a
|
|
successful user request cannot erase an active-probe failure streak and an active probe
|
|
cannot erase a passive failure streak. `mode: off` disables only active probes; an
|
|
explicitly enabled passive policy may still quarantine a backend.
|
|
|
|
One cancellation-aware supervisor schedules at most 16 probes, one per member,
|
|
with a rotating window for fairness. The tick is 250 ms; configured intervals are
|
|
best-effort under resource pressure, not precise service-level guarantees. DNS
|
|
workers have their own bounded slots. Probe bodies are not collected or cached.
|
|
|
|
## Sticky sessions and drain
|
|
|
|
`sticky.mode` is `off`, `cookie` or `ip_hash`. IP mode overrides the normal algorithm
|
|
using the actual peer IP. Users behind one NAT/proxy share that identity.
|
|
|
|
Cookie mode uses a random 32-character opaque member token, not a backend URL.
|
|
The default name is `RP_AFFINITY_<vhost-id>`; custom names accept 1..96 ASCII letters,
|
|
digits, underscores or hyphens and cannot use the admin session name. TTL defaults
|
|
to 3600 seconds (60..604800), with Path=/, HttpOnly, SameSite=Lax and Secure on TLS.
|
|
`__Host-`/`__Secure-` names require every frontend to use TLS. Cookies are host-only,
|
|
so aliases have independent browser cookies. They do not authenticate users.
|
|
|
|
Cookie-affine requests always bypass shared cache. The dedicated routing cookie
|
|
is stripped before forwarding; origins cannot override it via Set-Cookie. Unknown,
|
|
duplicate or unhealthy-target tokens are ignored/reassigned. Existing valid cookie
|
|
sessions may continue on a healthy draining/backup member; new sessions do not
|
|
choose a draining member. Disabling a member prevents all new assignments,
|
|
including existing cookie sessions, while already admitted exchanges finish.
|
|
|
|
Affinity tokens, health and traffic observations survive compatible hot edits.
|
|
A changed origin/health policy or re-enabled member is checked afresh. Process
|
|
restart resets tokens/counters; clients can be reassigned. Session data itself is
|
|
never replicated: applications needing durable sessions require a shared store.
|
|
|
|
## Response protection
|
|
|
|
`security_headers` supports `hsts` (`enabled`, `max_age_seconds`,
|
|
`include_subdomains`, `preload`), `nosniff`, `frame_options` (`preserve`, `deny`,
|
|
`sameorigin`), `referrer_policy`, `content_security_policy` and `permissions_policy`.
|
|
Header values are validated against splitting and bounded to 8192 bytes.
|
|
|
|
HSTS is configured only for TLS responses. For a matched vhost, plaintext responses
|
|
remove Strict-Transport-Security even if an origin/custom rule adds it. On TLS,
|
|
disabled HSTS means no forced replacement of an origin value; use a custom remove
|
|
rule to suppress that value. Enabled HSTS overrides generic rules. `max_age_seconds`
|
|
accepts 0..63072000 (default31536000); setting zero is available for explicit expiry.
|
|
Preload is opt-in and requires enabled HSTS, includeSubDomains and at least one year.
|
|
This only emits the directive; it does not register a hostname in a preload list.
|
|
Start with a short lifetime and verify every affected hostname before increasing it.
|
|
|
|
Request rules run global -> vhost -> path, then protected identity fields are
|
|
regenerated. Response rules run global -> vhost -> path, followed by dedicated
|
|
security/Server policy and optional compression. Security headers cover cache hits
|
|
and matched-host synthetic errors/ACL denials too. Protected transport fields
|
|
remain uneditable. `false`, `preserve` or null security settings otherwise leave
|
|
origin values unchanged. CSP and Permissions-Policy must suit the application;
|
|
RustProxy validates header encoding, not the semantic correctness of those policies.
|
|
|
|
The **Compression** tab inherits the enclosing/global policy by default. It can enable/disable dynamic compression and independently inherit/enable/disable zstd, Brotli, gzip and deflate. Selecting **Disabled** hides and disables the codec selectors while preserving their saved values for a later Inherit/Enabled selection. Global **Settings -> Response** codec switches are the hard availability ceiling, so a vhost cannot activate a codec disabled globally; `min_bytes` also remains global. Client `q` values are honored; equal-quality codecs use `zstd -> br -> gzip -> deflate`, and an existing `Content-Encoding` is never recompressed.
|
|
|
|
## Live state, cache and limits
|
|
|
|
The dashboard defaults a newly created vhost to `cache.enabled: false`. When the
|
|
effective cache state is disabled, the editor hides the remaining per-vhost cache
|
|
controls; enabling cache (or explicitly choosing inheritance from an enabled global
|
|
cache) reveals them. This is a UI default for new drafts, not a migration that rewrites
|
|
existing vhost cache settings.
|
|
|
|
|
|
`GET /api/backends` requires an admin session and returns `pools`,
|
|
`active_frontends`, `desired_frontends`. `/api/status` and SSE contain `backend_pools`
|
|
and complete active/desired endpoint plans. The dashboard renders per-member state,
|
|
active exchanges, request/error totals, EWMA header latency and payload traffic.
|
|
Prometheus adds bounded vhost/member labels; access logs include `backend` as
|
|
`vhost/member` for actual pool forwarding (null when no upstream exchange is made).
|
|
|
|
Bytes are observed payload, not NIC throughput; response counters include the
|
|
client-delivered encoded representation. Header latency includes connection/setup and any request upload; it is not full
|
|
exchange time. Active probes use body-free requests for an independent measurement.
|
|
Health mode off is labeled as checks off, not proof of a probe passing. Statistics
|
|
are observations, not persisted configuration. They reset on process restart.
|
|
|
|
Cookie affinity bypasses cache entirely. A pool with no eligible member does not invent
|
|
an unselected cached fallback. Config changes invalidate incompatible cache state.
|
|
RFC 5861/request `max-stale` behavior is owned by the shared cache and does not alter
|
|
backend member selection; request coalescing remains separate from stale-while-revalidate
|
|
deduplication.
|
|
|
|
Limits: 64 vhosts; hostname lists are not capped by an application-level item count;
|
|
listener entries remain capped at 16 per host, with 32 members and 64 paths per host;
|
|
128 backend definitions and 256 paths across all vhosts;
|
|
128 additional declared frontend entries plus two globals; 256 KiB serialized
|
|
configuration. Existing request/connection/DNS/queue bounds remain in force.
|
|
|
|
Every successful config publication rebuilds upstream connection pools. Compatible
|
|
member observations/tokens and in-flight exchanges survive, but idle sockets are
|
|
not retained across publication. Listener/address/ALPN changes are saved immediately
|
|
and require restart; routing changes apply to existing bound sockets immediately.
|
|
When moving a host away from an old IP, it can become unavailable until restart
|
|
rather than being served on an address no longer permitted by desired policy.
|
|
|
|
## Per-vhost operations in 0.3.0
|
|
|
|
The compact list opens a tabbed editor rather than displaying a full form beside
|
|
the list. Health summaries update on the list and editor without replacing unsaved
|
|
form controls. Header rules have a visual request/response editor; Advanced JSON
|
|
remains available and stays synchronized with those rows.
|
|
|
|
The SSL tab selects an existing certificate ID or uploads a chain/private-key
|
|
pair into private staging. Only Save activates the selection. A shared global SNI
|
|
resolver cannot serve different certificates for the same DNS name by destination
|
|
IP, so conflicting selections are rejected. Browser upload is local-admin only;
|
|
there is no ACME, encrypted-key password prompt, certificate download or automated
|
|
cleanup. Details and limits: MANAGEMENT.md.
|
|
|
|
Cache overrides inherit field by field and use one shared memory budget. The list
|
|
and Cache tab offer selective purge. An explicitly disabled global cache can still be
|
|
overridden by an explicitly enabled vhost. Path exclusions and sticky-cookie safeguards
|
|
remain in force. See CACHE.md.
|
|
|
|
The **Transform** tab is a separate vhost opt-in. `transform.enabled` defaults to false
|
|
for every vhost even when the global transform engine is enabled. JavaScript, CSS, HTML,
|
|
SVG and JSON can inherit or override global format switches, and the extension list may
|
|
be replaced per vhost. Eligible origin responses are transformed before shared-cache
|
|
storage and before downstream compression. Origin `Cache-Control: no-transform` always
|
|
wins and cannot be overridden from the vhost.
|
|
|
|
Pages offers a static/maintenance response, a fallback for unassigned paths, and
|
|
error overrides. The static/maintenance response can set an explicit HTTP status independently of its HTML body, so an operator can serve a maintenance document as `503 Service Unavailable` instead of returning `200`. Vhosts still retain their configured backend pool; static mode
|
|
avoids forwarding but is not a backend-less static-file server. Host/path ACLs run
|
|
first. Explicit error-status overrides change presentation, not authorization.
|
|
Empty/inherit means use the global error policy; to disable one inherited error
|
|
page specifically, configure an explicit replacement rather than expecting null
|
|
to override inheritance. See MANAGEMENT.md for exact precedence.
|
|
|
|
|
|
### Backend Host compatibility
|
|
|
|
New backends use the backend URL host for the upstream `Host` header by default, matching nginx `proxy_pass` behavior. Enable **Preserve client Host** only when the application intentionally routes on the public hostname. This is especially important for embedded/lighttpd-style administration panels that may close or reject requests carrying an unknown Host. Existing explicit settings are preserved during upgrades.
|
|
|
|
When an upstream request fails before an HTTP response is received, access logs include a bounded `upstream_error` field in addition to `upstream_status`.
|
|
|
|
Multiple explicit hostnames can belong to a single vhost: `suba.local` and `subb.local` share the same routing, backends and configuration. Only the www/non-www companion of the selected canonical hostname redirects; other aliases continue serving traffic directly.
|