610 lines
49 KiB
Markdown
610 lines
49 KiB
Markdown
# Listener and response settings
|
||
|
||
## Administration appearance preferences
|
||
|
||
**Settings -> Appearance** contains dashboard-only preferences. They are stored per administrator in the existing `admin_preferences` SQLite document and do not create a proxy configuration revision. The preferences are theme (`dark`/`light`), language (`auto` or a language-pack id), global interface scale (70-125%), timestamp format (`24h`/`12h`), chart style (`default`, `soft`, `technical`, `high_contrast`, `neon`, `monochrome`, `warm` or `cool`), button style (`normal` or `compact`), table/log density (`normal`, `compact`, `ultra`), dashboard density (`normal`, `compact`, `ultra`), Overview card order and Overview card visibility. `auto` language follows the browser language when a matching pack exists and otherwise falls back to the configured default pack.
|
||
|
||
The scale applies to the complete authenticated administration surface, including navigation, workspace content, dialogs, overlays and the draft dock. Text fields, selects, textareas, checkboxes and radios also receive a small proportional control-size adjustment on top of global zoom so field proportions track the selected scale more visibly. Button style is independent: **Normal** keeps the refreshed control sizing and **Compact** restores the smaller source-style sizing, including generated entity actions and tab buttons inside dialogs. Table/log and dashboard density are additional layout controls and do not change the whole-interface scale. The dashboard editor can hide/show and reorder the telemetry cards at the top of Overview; this changes presentation only, not metric collection. Stream card explanations appear on hover and keyboard focus instead of expanding the card list. Stream cards default hidden but, once switched on, remain present even when no Stream is active (neutral no-data state). The selected time format is shared by logs, backend/runtime diagnostics, audit/session timestamps, certificate views and configuration-history timestamps. Chart presets change colors, surfaces, grid and line presentation only; they do not change sampled data, and the palette selector exposes the main colors for each preset. Each Appearance subsection has its own reset action, and **Reset appearance** restores all documented Appearance defaults without touching unrelated account preferences. A small local cache is retained only to avoid a theme/scale flash before authentication, while the authenticated SQLite preference remains authoritative.
|
||
|
||
Unfinished Web Server, Vhost, Redirect and Stream editors remain session-only drafts. Their bottom-right dock uses square corners, can collapse to a single title bar, shows the age of each suspended draft and does not persist draft form values to SQLite.
|
||
|
||
The Settings page includes a local search field that filters category tabs using translated category labels and page content. Filtering never rebuilds settings forms, so unsaved values remain in the DOM. Category navigation remains sticky on desktop and marks categories containing unsaved forms.
|
||
|
||
Unfinished Web Server, Vhost, Redirect and Stream forms use a session-only draft dock. Closing a dirty editor minimizes its exact browser state to the bottom-right dock so another editor can be opened independently. The dock follows the square-corner panel styling of the dashboard and intentionally avoids rounded/glass presentation. Drafts are intentionally not written to SQLite because form state can contain credentials or other secrets; they are discarded on logout/session reset.
|
||
|
||
## Certificate API credentials (0.31.9)
|
||
|
||
Open **Settings → Certificate API** (`/settings?tab=certificate-api`) to manage external certificate automation credentials. The credential list now has **View settings**, **Edit settings**, **Regenerate token** and **Revoke** actions. The preview shows Key ID, non-secret comment, allowed certificate IDs, reload-only/deploy scope and creation/last-use times. Existing bearer token values cannot be viewed: only hashes are stored.
|
||
|
||
**Edit settings** loads the current credential into a form with fixed Key ID. You may update its comment, list of 1–32 explicit certificate IDs, and **Also allow deploy** permission. **Save settings** changes only this metadata; the current secret stays valid and is never shown or replaced. Legacy `*` certificate grants must be replaced with explicit IDs before saving an edit.
|
||
|
||
**Regenerate token** requires confirmation. It generates a new random bearer token, preserves the Key ID/comment/permissions/allowed IDs, immediately invalidates the old token and reveals the new token only once. Copy it into the external automation before closing the page. Regeneration updates the token's creation/rotation timestamp and clears last-used metadata. The same existing session + Origin + CSRF checks protect both operations; bearer-only external clients cannot read or change credentials.
|
||
|
||
The existing **Generate / rotate token** form still creates new Key IDs (and retains its previous ability to rotate a Key ID by supplying it). Use **Edit settings** to modify settings without rotating, and **Regenerate token** to rotate without changing any settings. See [API](API.md) and [access control](API_ACCESS.md).
|
||
|
||
## Refresh preferences (0.31.0)
|
||
|
||
**Settings -> Appearance -> Data refresh** saves an additional `refresh` object in the existing `admin_preferences` document for the signed-in account. It does not modify proxy configuration or create any migration. Older documents, including partially populated `refresh` objects, use defaults. Browser and server enforce exact allowed values:
|
||
|
||
| Preference | Default | Allowed seconds |
|
||
|---|---:|---|
|
||
| `refresh.live_enabled` | `true` | `true`, `false` |
|
||
| `refresh.overview_seconds` | 2 | 1, 2, 5, 10 |
|
||
| `refresh.traffic_seconds` | 5 | 0, 3, 5, 10, 30 |
|
||
| `refresh.cache_seconds` | 5 | 0, 3, 5, 10, 30 |
|
||
| `refresh.system_info_seconds` | 60 | 0, 15, 30, 60, 300 |
|
||
|
||
`live_enabled=false` disables automatic SSE **and all polling**, including system information. `0` disables only that source's automatic polling. Overview and System information have **Refresh now** buttons; Traffic and Cache retain their own buttons, filters, charts and pagination. The per-view Auto checkboxes for Traffic/Cache/Transform and the Docker live switch remain independent opt-outs, and none changes another administrator's preferences. **Reset section** restores refresh defaults without changing theme or unrelated preferences.
|
||
|
||
The main `/api/events?interval=N` connection (allowlisted 1/2/5/10, default 2 seconds) carries compact live status plus the existing bounded log snapshot. The server checks the session on every event and retains its heartbeat, viewer cap and normal disconnect cleanup. On an SSE error the browser polls `/api/status?view=live` and `/api/logs` until snapshots resume, not concurrently during healthy streaming. The old full `/api/status` response is kept for API clients and initial diagnostics. The separate authenticated `/api/system-info` reuses cached diagnostics and ACME metadata, never runs diagnostic subprocesses per request, and is fetched automatically only for Overview or the relevant Settings pages. A hidden browser tab pauses the channel and all automatic polling; returning revalidates its session before normal operation. All interval changes take effect immediately, without reloading or changing any server-global cadence.
|
||
|
||
## System information (0.30.1)
|
||
|
||
**Settings -> System information** (`/settings?tab=system-info`) is a read-only view of the authenticated `/api/system-info` endpoint (and the initial, backwards-compatible full `/api/status` response). It reports the OS name/kernel on Linux when available, executable architecture, logical CPU count, CPU and RAM telemetry, host/process uptime, RustProxy version, process RSS, thread and file-descriptor counts, and the existing statistics source and data/database locations. Values not supported by the host/platform display as unavailable, never as zero.
|
||
|
||
ACME diagnostics formerly shown in About and under Administration are now on this tab: mode/state, certificate environment path, and the active Python/pip/Certbot versions. Versions are probed by the ACME runtime setup with bounded subprocess output/timeouts and published from stored runtime status. They are not queried at every page refresh or status API request. Failed/unavailable probes leave the version empty. No new persistent settings or permissions are introduced.
|
||
|
||
## Rules and headers (0.31.7)
|
||
|
||
**Settings -> Rules & headers** (`/settings?tab=policies`) now groups the existing **Global User-Agent blocklist**, **Global custom headers** and **Client response** forms as persistent sub-tabs, following the Cache settings layout. There is no migration: each form continues to update its previous configuration section. **Settings -> Security** retains administrative hostname, Origin, session and password policies.
|
||
|
||
**Vhosts -> Security -> Traffic policy** also supports optional `rate_limit.user_agent_limits` in each vhost, for example:
|
||
|
||
```json
|
||
{
|
||
"rate_limit": {
|
||
"enabled": true, "requests": 120, "window_seconds": 60, "burst": 30,
|
||
"user_agent_limits": [
|
||
{"pattern": "*scanner*", "requests": 10, "window_seconds": 60, "burst": 0},
|
||
{"pattern": "BadBot", "requests": 2, "window_seconds": 60, "burst": 0}
|
||
]
|
||
}
|
||
}
|
||
```
|
||
|
||
The first case-insensitive match wins. A plain pattern searches for a substring and `*` matches any sequence. A matching User-Agent uses its **own bucket for the vhost, rule and client IP** and overrides that request's standard/country throttle; nonmatching requests continue using existing rate rules. Rules also work with `enabled=false` for the base limiter. The User-Agent header is untrusted, so avoid treating these limits as authentication or identity enforcement.
|
||
|
||
The **Revision history** detail shows only changed leaf fields, scrolling locally when there are many. **Full JSON comparison** is directly below Restore in the action group and opens a separate standard dialog. ACL profile *Attached to* details are collapsed by default. Listener HTTP/2 and HTTP/3 controls have additional spacing.
|
||
|
||
## Configuration and update semantics
|
||
|
||
GET `/api/config` returns a revisioned envelope. PUT the entire document with
|
||
`expected_revision`. Defaults apply when fields are omitted. Unknown fields are
|
||
rejected; failed validation/persistence leaves the old snapshot active. Existing
|
||
backend/route/TLS/ACL data must be retained when changing settings. The panel does
|
||
this and preserves dirty settings forms across status/language refreshes.
|
||
|
||
```json
|
||
{
|
||
"listeners": {
|
||
"endpoints": [
|
||
{"id":"http", "name":"HTTP", "scope":"public", "enabled":true,
|
||
"binding":{"address":"0.0.0.0:80", "tls":false, "http2":false, "http3":null}},
|
||
{"id":"https", "name":"HTTPS", "scope":"public", "enabled":true,
|
||
"binding":{"address":"0.0.0.0:443", "tls":true, "http2":true,
|
||
"http2_settings":{"idle_timeout_seconds":300, "max_connection_lifetime_seconds":0, "drain_timeout_seconds":10}}}
|
||
]
|
||
},
|
||
"server_header": {"mode":"custom", "value":"RustProxy"},
|
||
"compression": {"gzip":true, "br":true, "zstd":true, "deflate":false, "min_bytes":1024},
|
||
"headers": {
|
||
"request": [{"op":"set", "name":"x-proxy-name", "value":"RustProxy"}],
|
||
"response": [
|
||
{"op":"remove", "name":"x-powered-by"},
|
||
{"op":"set", "name":"x-served-by", "value":"RustProxy"}
|
||
]
|
||
}
|
||
}
|
||
```
|
||
|
||
This is a settings fragment, not a replacement for the existing routing/vhost document.
|
||
A complete opt-in example is in `examples/cache-dashboard.json`.
|
||
|
||
The dashboard exposes the complete raw JSON editor under **Settings -> Manual config**.
|
||
The same card exposes **Revision history**, which reads the newest 512 retained configuration revisions from the configuration-history table introduced in SQLite schema 8 (current database schema 9) and groups them by month/day. Selecting a revision shows timestamp, actor and only the changed fields against the previous retained revision; the separate Full JSON comparison dialog contains complete JSON documents. An older retained revision can be restored from the same dialog; RustProxy validates it through the normal configuration update path and saves it as a new revision, preserving the prior history.
|
||
The historical `/configuration` browser route is retained only as a compatibility alias and
|
||
opens that Settings block; it is no longer a primary-navigation item. Prefer the structured
|
||
section editors for routine changes so unrelated sections are not republished.
|
||
|
||
## GeoIP updates
|
||
|
||
**Settings -> GeoIP updates** controls the background country-database refresh and is enabled by default. The worker starts only after all server sockets have bound successfully, attempts the first refresh immediately, and then refreshes every 24 hours by default. Sources are direct HTTPS file URLs rather than coupled release/checksum base URLs. Publisher SHA-256 verification is optional per file; every downloaded payload is still size-bounded, streamed to a private staging file and parsed as the selected database format before publication.
|
||
|
||
Two formats are supported. **CSV ranges** uses separate IPv4 and IPv6 files and defaults to the direct `sapics/ip-location-db` `user-country-ipv4.csv` and `user-country-ipv6.csv` assets. **MaxMind DB (MMDB)** uses one `country.mmdb` file for both address families and defaults to the combined sapics `user-country.mmdb` asset. Standard MaxMind country databases are read from `country.iso_code`, then `registered_country.iso_code`; custom country MMDBs may expose top-level `country_code` or a root two-letter country string. To manage a private MMDB outside the updater, place it at `<data-dir>/geoip/country.mmdb`, select MMDB, disable automatic updates and leave the MMDB source URL empty; **Download now** then validates/reloads the local file instead of fetching it.
|
||
|
||
When a checksum URL is empty, RustProxy deliberately skips publisher-checksum comparison. It still computes a local SHA-256 for the validated download and compares it with the installed file, so unchanged content is not republished or hot-reloaded. This avoids coupling a moving `latest` data URL to a separately moving checksum release. If a checksum URL is supplied, a mismatch rejects the staged file. The configured maximum is 1 MiB..256 MiB and the interval is 60 seconds..31 days. Failures are warnings only; proxy traffic continues and the worker retries. Manual UI refresh, the background worker and managed upgrades share `.geoip-update.lock`. After publication the `ArcSwap` database is replaced atomically: requests already holding a snapshot may finish on it, while subsequent country ACL/rate-limit/logging lookups and status/metrics reads use the new database without a restart.
|
||
|
||
```json
|
||
{
|
||
"geoip_updates": {
|
||
"enabled": true,
|
||
"format": "csv_pair",
|
||
"ipv4_url": "https://github.com/sapics/ip-location-db/releases/download/latest/user-country-ipv4.csv",
|
||
"ipv6_url": "https://github.com/sapics/ip-location-db/releases/download/latest/user-country-ipv6.csv",
|
||
"mmdb_url": "https://github.com/sapics/ip-location-db/releases/download/latest/user-country.mmdb",
|
||
"ipv4_checksum_url": null,
|
||
"ipv6_checksum_url": null,
|
||
"mmdb_checksum_url": null,
|
||
"max_bytes": 268435456,
|
||
"interval_seconds": 86400
|
||
}
|
||
}
|
||
```
|
||
|
||
## Administration HTTPS
|
||
|
||
`administration` is separate from public proxy listeners. It defaults to the startup
|
||
loopback address and plain HTTP. Persisted address/certificate changes are validated
|
||
immediately but take effect after process restart. A non-loopback persisted bind is
|
||
accepted only when `administration.tls` is true. Plain remote HTTP remains available only when process startup explicitly allows it with
|
||
`--allow-remote-admin`.
|
||
|
||
```json
|
||
{
|
||
"administration": {
|
||
"listen": "0.0.0.0:9090",
|
||
"tls": true,
|
||
"certificate_id": "admin-example",
|
||
"allowed_hosts": ["admin.example.com"],
|
||
"public_origin": null,
|
||
"session_days": 30,
|
||
"panel_compression": {"zstd": true, "br": true, "gzip": true}
|
||
}
|
||
}
|
||
```
|
||
|
||
### Fixed public origin for a reverse-proxied panel (0.31.4)
|
||
|
||
For a **panel** reached through a trusted reverse proxy, set the optional
|
||
`administration.public_origin` in **Settings → Administration** to the exact
|
||
browser-facing HTTPS origin (for example `https://panel.example.test`).
|
||
**Do not duplicate that hostname in `administration.allowed_hosts`.** Its DNS
|
||
hostname is implicitly included in the runtime panel Host allowlist. **Restart
|
||
RustProxy**; this is a restart-bound access-control setting. Omit it (`null`) for
|
||
direct administration or when forwarded headers are canonical. This setting
|
||
is not a wildcard, does not accept HTTP or a URL path, and requires a DNS
|
||
hostname rather than an IP address. SQLite schema 9 remains unchanged.
|
||
|
||
The advanced **Additional allowed DNS hostnames** field controls independent,
|
||
extra hostnames only. Older configurations containing both values are
|
||
accepted; on editing/saving the redundant duplicate is omitted from the
|
||
persisted list. If `public_origin` changes or is cleared, its old hostname is
|
||
no longer implicitly allowed (unless explicitly retained as an additional host).
|
||
|
||
Example for the default **private loopback** administration listener behind an
|
||
HTTPS-terminating proxy:
|
||
|
||
```json
|
||
{
|
||
"administration": {
|
||
"allowed_hosts": [],
|
||
"public_origin": "https://panel.example.test"
|
||
}
|
||
}
|
||
```
|
||
|
||
The last hop must connect from loopback or an existing explicitly trusted proxy
|
||
CIDR. If it is not loopback, configure **only the actual proxy peer CIDR** under
|
||
Forwarding → Trusted proxies; be aware the general trusted-proxy setting also
|
||
applies to public traffic and must never include untrusted clients. The
|
||
administration listener should not be reachable directly from external clients.
|
||
Do **not** grant trust to public networks to work around Origin failures.
|
||
|
||
The pinned URL, not an element of `X-Forwarded-Proto`, defines the panel
|
||
scheme/authority. A trusted, bounded proxy chain `https, http` (HTTPS on the
|
||
browser side, HTTP on an internal hop) is permitted in this explicit mode.
|
||
An `http` first hop, malformed/too-long protocol chain, duplicate/conflicting
|
||
`X-Forwarded-Host`, different `Origin`, `Sec-Fetch-Site: cross-site`, and missing
|
||
CSRF protection for state-changing requests still result in HTTP 403. The
|
||
public proxy/vhost data plane is **not changed**, except that an already opted-in
|
||
self-admin vhost can recognize the pinned hostname for its reserved panel session
|
||
cookie; other upstreams continue to strip it. Credential-bearing certificate
|
||
automation retains its original TLS/forwarding checks.
|
||
|
||
Prefer fixing the proxy's forwarding headers rather than configuring the pin
|
||
when practical. For a TLS-only public entry, the closest trusted hop should
|
||
**replace**, not append, forwarded headers using known values, for example:
|
||
|
||
```nginx
|
||
proxy_set_header Host panel.example.test;
|
||
proxy_set_header X-Forwarded-Host panel.example.test;
|
||
proxy_set_header X-Forwarded-Proto https;
|
||
```
|
||
|
||
Only use that `https` setting on an entry that actually verifies external TLS;
|
||
never trust an arbitrary inbound `X-Forwarded-Proto`. If multiple intermediate
|
||
proxy hops must append protocol values, the pinned mode provides an explicit
|
||
server-side comparison target without accepting a client-supplied origin as
|
||
authoritative. Confirm deployment using a real mobile browser and the safe
|
||
`administration origin rejected` logs before production rollout.
|
||
|
||
**Administration → Panel content compression** (next to the admin listener/TLS settings)
|
||
controls Zstd, Brotli and gzip for the authenticated HTML document, JavaScript,
|
||
CSS and language bundles. Each browser request is negotiated using
|
||
`Accept-Encoding` quality values: the highest positive `q` wins, and ties
|
||
prefer **Zstd → Brotli → gzip**. Codecs can be enabled independently; the
|
||
identity representation is returned when none is acceptable or all are off.
|
||
The source is encoded only once per embedded asset and shared across visitors.
|
||
Settings persist in the existing revisioned administration document, without
|
||
additional CLI flags or a separate environment/config variable, and take effect
|
||
without restart. Each compressed response includes `Content-Encoding`,
|
||
`Vary: Accept-Encoding` and the representation-specific `Content-Length`.
|
||
The response compression of proxied websites remains independent; API responses,
|
||
SSE and WebSocket streams are not affected.
|
||
|
||
**Reverse proxy:** an upstream may remove or replace `Accept-Encoding`. RustProxy
|
||
negotiates from the header it actually receives, not the original browser request.
|
||
A reverse proxy normally passes through already encoded responses instead of
|
||
recompressing them; if the reverse should exclusively manage encoding, configure
|
||
it to strip `Accept-Encoding` on requests upstream, or turn the panel codecs off.
|
||
This provides compatible HTTP negotiation but does not guarantee identical
|
||
compression with every reverse proxy configuration.
|
||
|
||
`certificate_id: null` uses `data/certs/default.pem` and `default.key`, generated by
|
||
the application. A selected inventory certificate is loaded from the same private
|
||
certificate directory. `session_days` controls the absolute browser-session lifetime
|
||
and must be 7..30 (default 30). It applies without restart; changing listener/TLS/certificate
|
||
settings still requires restart. Session cookies are HttpOnly/SameSite=Strict and receive
|
||
the `Secure` attribute on HTTPS. When HTTPS is enabled, accidental plain HTTP received on the same administration
|
||
port receives a permanent redirect to the same authority/path over `https://`; the
|
||
plaintext path contains no administration handlers. An empty `allowed_hosts` accepts
|
||
valid DNS Host values on the configured HTTPS port as well as valid numeric destination
|
||
addresses. Once `allowed_hosts` contains entries it becomes a strict DNS allowlist for
|
||
browser-facing DNS authorities. An explicitly listed hostname may arrive from a trusted
|
||
reverse proxy on an external port such as 443 even when the private admin socket is 9090.
|
||
Direct IP/`localhost` access is still accepted only on the actual configured admin port.
|
||
For HTTPS termination in front of a plain local admin socket, the trusted proxy must pass
|
||
its external Host and scheme; `X-Forwarded-Host` is accepted only from loopback/configured
|
||
trusted proxy peers and `X-Forwarded-Proto` additionally follows the forwarded-proto trust
|
||
policy. Origin/CSRF comparison and the session cookie Secure flag use that external origin.
|
||
Use a firewall and normal network policy even with HTTPS.
|
||
|
||
## Structured access logging
|
||
|
||
The **Settings -> Logging** page is split into **Outputs**, **JSON format** and **Error log** subsections, using the same in-card sub-tab layout and keyboard interaction as Cache. Outputs controls optional external access-log sinks while the existing bounded live ring remains available to the panel. Sink I/O is performed by a bounded background queue; request processing only performs a non-blocking enqueue. Supported destinations:
|
||
|
||
- JSONL file append, either one combined file such as `/var/log/rustproxy/access.jsonl` or
|
||
one file per resolved vhost under a configurable directory (default `/var/log/rustproxy/vhosts`).
|
||
Unmatched traffic uses `_unmatched.jsonl`. The managed systemd service provisions
|
||
`/var/log/rustproxy` for the service account and keeps that directory writable under
|
||
`ProtectSystem=strict`. Other custom paths still need matching service permissions.
|
||
- Logstash TCP using `host:port` (IP or DNS). Each event is one JSON line.
|
||
- Loki HTTP(S) push. Optional bearer credentials are read from an environment variable
|
||
named by `bearer_token_env`; the secret itself is not stored in configuration.
|
||
|
||
`logging.format.fields` maps output field names to standard event fields; an empty map
|
||
emits the complete standard event. The panel provides Standard, Nginx-style, ECS-style
|
||
and Minimal presets plus a visual field selector/renamer with JSON preview. Advanced raw
|
||
mapping remains available for aliases not representable by the simple builder. Supported mapped sources include `vhost`, `route`, `cache_effective_enabled`, `transform_effective_enabled`, `backend`, request/response metadata and cache outcome fields. `static_fields` adds constant string fields. For low-level `protocol_error` events, RustProxy always preserves the required diagnostic envelope (`event`, error kind/detail, time, client/local/TLS/protocol fields, status and outcome) even with a custom mapping, so malformed-traffic records remain searchable in the built-in Logs explorer. Loki labels are separately configurable.
|
||
`/api/status.logging.sinks` reports queue capacity, dropped events, successfully delivered
|
||
events and sink errors.
|
||
|
||
## Hot and cold cache tiers
|
||
|
||
`cache.max_bytes` and `cache.max_entries` bound the HOT RAM tier. `max_bytes` is charged from retained body backing allocation plus entry metadata rather than only payload length. `cache.transient_max_bytes` separately bounds in-progress fills and full bodies moving between HOT and COLD; `cache.cold_queue_max_bytes` additionally caps queued demotions in bytes, and `cache.hot_eviction_max_objects` bounds one LRU admission attempt. The source defaults are 256 MiB HOT, 16 MiB maximum object, 64 MiB transient and 32 MiB COLD queue. The Settings panel calculates conservative/recommended/performance presets from the lower of host RAM and a finite cgroup memory limit without changing the COLD disk budget. It also warns when HOT + transient is a large fraction of effective RAM, when the additional configured cache budget is large compared with current host/cgroup headroom, when the COLD queue exceeds transient RAM, or when the object ceiling cannot fit in transient RAM. Automatic presets are disabled below 256 MiB effective RAM. On a 2 GiB effective-memory VM the recommended profile selects approximately 400 MiB HOT, 96 MiB transient, 16 MiB maximum object, 48 MiB COLD queue, 6400 HOT entries and a 64-object LRU admission limit. The optional persistent
|
||
cold tier is controlled by `cold_enabled`, `cold_path`, `cold_max_bytes`,
|
||
`cold_max_entries`, `cold_gc_interval_seconds`, `cold_gc_max_delete`,
|
||
`cold_after_seconds`, `cold_on_lru`, `cold_on_idle` and `cold_on_shutdown`.
|
||
`cold_gc_interval_seconds` defaults to 60 and `cold_gc_max_delete` to 256. Pressure GC
|
||
starts at roughly 90% of the configured byte/entry budget and cleans toward roughly 80%
|
||
without deleting more than the configured per-pass cap. `cold_path` is relative to the RustProxy data directory and cannot
|
||
contain parent traversal or an absolute path. The two demotion switches independently
|
||
select LRU-pressure and idle objects; shutdown flush is a best-effort direct write during
|
||
a graceful restart. Demotions use a small bounded queue and release hot accounting before
|
||
disk persistence so cold storage does not retain an unnecessary second body buffer in RAM.
|
||
Cold metadata is readable JSON and bodies are raw `.body` files; reads are size/SHA-256
|
||
checked before promotion to RAM. Since 0.21.0 objects are sharded by the first two SHA-256
|
||
hex characters (`cache/cold/00/...` through `ff/...`), with shard directories created on
|
||
first write. The previous flat Cold Cache is intentionally not migrated. Purge operations
|
||
clear both in-memory indexes and schedule disk cleanup; selective cleanup uses the same
|
||
GC/eviction path and runs asynchronously.
|
||
|
||
Version 0.22 adds foreground fill collapsing (`collapse_enabled`,
|
||
`collapse_wait_timeout_ms`, `collapse_timeout_fallback`), detailed stale-if-error policy
|
||
(`stale_if_error_timeout`, `stale_if_error_connection`, `stale_if_error_dns`,
|
||
`stale_if_error_tls`, `stale_if_error_statuses`) and opt-in proactive refresh
|
||
(`proactive_refresh_enabled`, `proactive_refresh_before_expiry_seconds`,
|
||
`proactive_refresh_percent`, `proactive_refresh_max_inflight`). Defaults preserve the
|
||
previous stale-if-error behavior; proactive refresh is disabled by default. Selective
|
||
PURGE/BAN is available in the authenticated API/panel, and HTTP BAN/PURGE has an optional, separately stored CIDR allowlist (`cache.http_invalidation_allowed_cidrs`, empty by default). See
|
||
[CACHE.md](CACHE.md) for request-collapse, Range and invalidation semantics.
|
||
The adjacent **info (`i`) popover** in Settings → Cache shows ready-to-copy
|
||
`curl -X PURGE` and `curl -X BAN` examples in English and Polish, including
|
||
the explicit `BAN /` confirmation. These public methods authorize by the
|
||
source IP/CIDR only; they do not accept a browser admin session as a bypass.
|
||
See [0.31.0 security audit](SECURITY_AUDIT_0.31.0.md) for reverse-proxy trust and tests.
|
||
|
||
## Static-response transformation
|
||
|
||
`transform` is a formal identity-response stage between the upstream response and shared
|
||
cache storage. `transform.enabled` is the global kill switch, but every vhost must also
|
||
set its own `transform.enabled=true`; vhosts stay disabled by default even when the global
|
||
engine is enabled. Global defaults enable JavaScript and CSS transformation and leave
|
||
HTML, SVG and JSON off. `extensions` controls extension-based detection and
|
||
`max_body_bytes` (1 KiB..32 MiB, 2 MiB default) bounds aggregation/decoding.
|
||
|
||
The pipeline is `origin -> decode supported Content-Encoding -> transform -> cache
|
||
identity representation -> client compression`. JavaScript/CSS can be enabled normally;
|
||
HTML minification, SVG optimization and JSON compaction are independent optional formats.
|
||
Recognition uses safe configured extensions and known Content-Type values. Binary/image
|
||
formats outside those explicit transform types are not transformed.
|
||
|
||
Origin `Cache-Control: no-transform` always disables transformation. When bytes change,
|
||
RustProxy removes representation-specific length/digest/encoding metadata and creates a
|
||
new strong `rustproxy-*` ETag for the transformed representation. The original origin
|
||
ETag/Last-Modified are kept separately and used for origin revalidation, so a proxy ETag
|
||
is never sent upstream as though it validated the original object. The transformed
|
||
identity body is what enters cache, preventing repeated minification on every HIT.
|
||
|
||
## Global TLS protocol and cipher policy
|
||
|
||
`tls.policy` is the global baseline inherited by every TLS host. It controls TLS 1.2,
|
||
TLS 1.3 and optional rustls cipher-suite names. An empty `cipher_suites` list uses the
|
||
ring provider defaults. Settings shows both the currently effective cipher suites and
|
||
the complete selectable suite inventory. At least one protocol version and
|
||
one compatible effective suite are required. This policy is also used by administration
|
||
HTTPS. HTTP/3 requires TLS 1.3 and a TLS 1.3 suite. Vhosts can narrow TCP TLS with
|
||
`tls_policy`; when a vhost has HTTP/3 enabled on a selected H3-capable listener it must
|
||
match the global policy because QUIC TLS is shared per UDP listener. Available suite names are exposed in authenticated status for
|
||
the Settings editor.
|
||
|
||
## Listeners: saved intent, not hot rebinding
|
||
|
||
When an HTTP, HTTPS or administration address was explicitly supplied by systemd/CLI
|
||
(or the corresponding service environment variable), that exact startup bind is a
|
||
process contract: the panel shows it as locked and configuration updates cannot remove,
|
||
disable or change it. If an address was not explicitly supplied at startup, listener
|
||
entries remain freely addable/removable/editable subject to normal validation.
|
||
|
||
`listeners.endpoints` is the only persisted frontend socket configuration. Each
|
||
endpoint has a stable id/name, an informational scope label, an enabled flag and one
|
||
primary binding containing IP:port, TLS, HTTP/2 plus its optional lifecycle settings, and optional HTTP/3 settings. Optional
|
||
`additional_addresses` reuse that same transport policy on up to 15 further addresses.
|
||
When more than one address is configured every address must be explicit; `0.0.0.0` and
|
||
`::` are rejected so a multi-IP selection cannot silently mean every interface. IP/port
|
||
and transport capability belong here, not inside a vhost. IPv6 uses `[address]:port`.
|
||
|
||
A vhost references endpoints through `frontend.listener_ids`. Listener changes are
|
||
restart-required because they alter TCP/UDP sockets or TLS ALPN capability. The
|
||
listener editor owns the HTTP/2/H2C and HTTP/3 enable/disable switches. An H2/H2C row
|
||
links to **HTTP connections -> HTTP/2** for idle, absolute-lifetime and graceful-drain
|
||
limits; an enabled H3 row links to **HTTP connections -> HTTP/3** for advertisement,
|
||
connection, stream and idle-timeout limits. Both tuning pages are listener-scoped and
|
||
do not own protocol enablement. Vhost H2/H3 switches only narrow the protocols already
|
||
enabled by the selected endpoints; they never create a protocol that Settings disabled.
|
||
|
||
The administration listener is independent and cannot share a frontend port.
|
||
Bindings must be valid local unicast/wildcard addresses; conflicting overlapping
|
||
endpoints are rejected rather than silently widened.
|
||
|
||
## Server and other client headers
|
||
|
||
`server_header.mode` is `preserve`, `hide`, or `custom`. Fresh installations default to `custom` with the exact value `RustProxy`; existing saved installations retain their configured policy. Custom values are
|
||
nonempty, bounded to 256 bytes and validated against header injection. Preserve
|
||
means retain the result of origin/global/route rules; it does not add a missing
|
||
header. Hide/custom take final precedence, including proxy-generated errors.
|
||
|
||
Global and route `headers.response` apply to client responses; `headers.request`
|
||
apply only upstream. Each scope/direction supports up to 32 ordered set/append/remove
|
||
rules. Remove has no value. Global rules precede route rules. Multiple Set-Cookie
|
||
values remain distinct. Admin-session cookie names are never forwarded or set by
|
||
public proxy rules. Transport, framing and trusted forwarding/identity fields are
|
||
protected. Cache diagnostic fields are owned by the cache controls, not generic rules.
|
||
|
||
Global response rules also cover generated errors; route-specific rules apply only
|
||
after an origin route response exists. Client policy changes use hot configuration
|
||
publication and invalidate the RAM cache. In-flight exchanges retain their old
|
||
snapshot; no mixed-version configuration is applied midway through a response.
|
||
|
||
## Compression
|
||
|
||
`zstd`, Brotli and gzip default ON; deflate defaults OFF. `min_bytes` defaults to 1024
|
||
(valid 32..1048576). The streaming middleware keeps its default content-type safety
|
||
predicate plus the RustProxy minimum-size predicate. Unknown-length eligible bodies can
|
||
be streamed without aggregating the complete response merely to discover its size.
|
||
|
||
RustProxy parses `Accept-Encoding` quality weights before invoking the encoder. Client
|
||
`q` values win. When two or more enabled codecs have equal quality, the deterministic
|
||
server preference is `zstd -> br -> gzip -> deflate`. An existing `Content-Encoding` is
|
||
always passed through and never recompressed. `no-transform`, request ranges/Content-Range,
|
||
SSE, upgrades, HEAD/1xx/204/304 and explicitly skipped internal responses remain excluded.
|
||
`Cookie`, `Authorization`, `Set-Cookie`, `Cache-Control: private` and `no-store` do not by
|
||
themselves disable compression; cacheability and dynamic compression are separate policies.
|
||
Applications that place secrets and attacker-controlled reflection in the same compressed
|
||
response can disable compression at the vhost or Web Service scope when their threat model
|
||
requires BREACH-style mitigation.
|
||
|
||
The global Response panel selects available codecs and the minimum size. Each ordinary
|
||
vhost and Web Server service may inherit, enable or disable dynamic compression and independently
|
||
inherit/enable/disable zstd, Brotli, gzip and deflate. Docker vhosts intentionally expose only one
|
||
Compression checkbox: when enabled they use the globally enabled codec set and priority, with no
|
||
per-Docker-vhost codec selectors. Global codec switches remain the hard ceiling: a local `true`
|
||
override cannot enable a codec disabled in the global Response panel. `min_bytes` remains global.
|
||
|
||
`Vary: Accept-Encoding` is added when compression is negotiated; content length is removed
|
||
for the transformed body. Strong ETags are weakened, and identity-body content digests and
|
||
`Accept-Ranges` are removed. Encoders use their fastest preset; there is no panel
|
||
compression-quality slider in this release.
|
||
|
||
## Diagnostics and units
|
||
|
||
Samples run off the traffic path roughly every two seconds. CPU and network rates
|
||
need two successful samples. Host CPU is aggregate; process CPU uses 100% per busy
|
||
logical CPU and can exceed 100%. Neither is normalized to a container CPU quota.
|
||
Host memory is MemTotal minus MemAvailable. RSS/FDs/threads and process I/O come
|
||
from procfs. Network rates sum non-loopback interfaces in the process network
|
||
namespace, not just RustProxy traffic. Load average is OS scope, not latency.
|
||
|
||
The process's cgroup v2 memory files are read at the standard mount location when
|
||
available. Linux keeps the existing `/proc`/cgroup metric names, units and behavior.
|
||
FreeBSD/OpenBSD/NetBSD use native system interfaces where the same metric is available;
|
||
unknown or unsupported values remain null rather than being fabricated. Snapshot timestamps and sample age are shown;
|
||
cache counters and proxy request metrics are separate from OS resource metrics.
|
||
The dashboard history is browser-local and resets on reload/sign-out. No time-series
|
||
database, disk utilization chart or Windows/macOS OS sampler is implemented.
|
||
|
||
## HTTP/2 compatibility and per-vhost settings
|
||
|
||
HTTP/2 lifecycle settings are listener-scoped under `listeners.endpoints[].binding.http2_settings`:
|
||
|
||
- `idle_timeout_seconds` (default 300) retires only a truly unused H2 connection; active streams and current frame I/O postpone it.
|
||
- `max_connection_lifetime_seconds` (default 0) is an independent absolute lifetime; `0` disables this limit.
|
||
- `drain_timeout_seconds` (default 10) bounds how long accepted streams may finish after retirement begins.
|
||
|
||
Idle and lifetime retirement both send GOAWAY, stop accepting new streams, drain already accepted streams and then close TCP. RFC 8441 WebSocket/H2 Extended CONNECT streams are counted as active until their response body/tunnel ends. The same settings apply to TLS H2 and cleartext H2C on that named listener.
|
||
|
||
For backward compatibility, the top-level `http2` object is still accepted as the fallback for an H2/H2C listener that has no explicit `binding.http2_settings`. The structured UI writes explicit per-listener settings; operators should treat the top-level object and `PUT /api/config/http2` as legacy compatibility surfaces rather than the preferred configuration path.
|
||
|
||
A named TLS listener with `binding.http2: true` advertises H2 capability and always
|
||
retains HTTP/1.1 fallback. `binding.http2: false` makes that listener HTTP/1.1-only.
|
||
Cleartext H2 uses `binding.h2c: true`. Changing listener transport capability or H2 lifecycle settings requires restart.
|
||
|
||
A vhost can additionally set `http2_enabled: false`. RustProxy reads TLS ClientHello
|
||
and SNI before committing to ALPN, so a vhost that disables H2 gets HTTP/1.1-only
|
||
ALPN even while another hostname on the same listener keeps H2. `http3_enabled` is
|
||
the equivalent per-vhost H3 opt-out. The panel enables these switches only when a
|
||
selected listener provides the protocol. No failed application request is replayed
|
||
to downgrade.
|
||
|
||
Vhosts have their own backend pool, health/keepalive/affinity controls, ACL and
|
||
security/custom response headers. See VHOSTS.md. Global Server policy still takes
|
||
final precedence. Per-vhost compression can filter individual globally enabled codecs.
|
||
|
||
The reported `SizeAbove(u16)` compilation error is removed by a custom u64 predicate,
|
||
not a cast or panic-producing conversion. Compression thresholds retain the full
|
||
32..1048576-byte setting range. Known body length/Content-Length is compared exactly;
|
||
unknown streaming lengths are eligible without buffering the full body. Actual
|
||
compression execution still requires the compiler-enabled acceptance suite.
|
||
|
||
## Management in 0.3.0
|
||
|
||
Settings contains a direct Security shortcut (accounts, sessions and host ACLs)
|
||
and a vhost shortcut (SSL, headers, cache and pages). Compact is the default density;
|
||
Dense and Comfortable change spacing/control sizes without browser zoom or logo
|
||
redesign. The preference is local to the browser, not a global proxy setting.
|
||
|
||
Global Pages configures error overrides, direct-IP and unknown-host responses.
|
||
See MANAGEMENT.md for inheritance, allowed statuses and security implications.
|
||
Access logs have moved out of the dashboard into a separate modal/docked window.
|
||
Filters stay above the scrollable rows; counts and exports use the same selection.
|
||
|
||
## Named listeners, forwarding and diagnostics (0.4.0)
|
||
|
||
As of 0.8.0, the vhost editor no longer asks for a free-form listener IP. Configure
|
||
listener IP/port/protocol once in this Settings section, then select named endpoints in
|
||
the vhost. The pre-production inline/global listener format is no longer accepted. This separates
|
||
socket intent from host routing and avoids duplicating listening IP configuration across
|
||
vhosts.
|
||
|
||
`listeners.endpoints` is the only proxy-listener configuration. Each record has id,
|
||
name, scope, enabled and a binding with address, tls, http2, optional http2_settings, h2c and optional http3. Scope
|
||
is only a label. A vhost uses `frontend.listener_ids`; every reference must exist.
|
||
Conflicting protocols on shared sockets are rejected.
|
||
Changes to effective transport plans need restart, including H2 lifecycle and H3 limits. The UI
|
||
shows saved named entries and actual bound transports separately.
|
||
|
||
`forwarding` controls trusted CIDRs and forwarded-proto acceptance.
|
||
`diagnostic_headers` exposes only `request_id` (false initially). Vhosts can override the diagnostic
|
||
policy and Server policy; absent/null means inherit. Runtime/cache/static/error
|
||
responses pass through the same final header policy.
|
||
|
||
See INTEGRATIONS_040.md and HTTP3.md for complete JSON, limits and security details.
|
||
|
||
|
||
## 0.5.0 changes (override earlier baseline examples)
|
||
|
||
A named TLS listener with omitted http3 uses default H3 limits. Explicit null is
|
||
persisted as disabled. Overlapping named sockets must match protocols AND H3 limits;
|
||
the error identifies owners and remedies. A changed binding requires restart, including
|
||
UDP protocol/limit changes. Check UDP port availability before upgrading.
|
||
|
||
The diagnostic control is `request_id` (false by default). The removed hide_mock_origin field is not accepted; use normal response-header removal
|
||
for X-Mock-Origin. Existing custom Server values remain unchanged.
|
||
|
||
Cache decision/object inspection is under Cache, separate from Overview. Basic
|
||
manual certificate issuance is under Certificates; TSIG is never a configuration
|
||
field. See CACHE_DIAGNOSTICS.md and ACME_RFC2136.md for exact scopes and limits.
|
||
|
||
## 0.6 navigation and exporter
|
||
|
||
The Settings page now has eight switching cards: Listeners, Response, Cache,
|
||
Headers, Forwarding, Pages, Prometheus and Security. Unsaved values remain in
|
||
place while switching; switching alone never writes configuration. Security is
|
||
not a separate sidebar item. Use arrow keys/Home/End in the tab bar.
|
||
|
||
The Prometheus card controls enabled, bind IP/port, URL path, client CIDRs,
|
||
bearer-token environment variable name and optional collectors, including the bounded
|
||
Traffic analytics collector. Only the variable name is exported; supply the actual secret to the service separately. See
|
||
PROMETHEUS.md for immediate revocation, restart rules and scrape configuration.
|
||
|
||
## Stream Dashboard cards (0.32.0)
|
||
|
||
Under **Settings → Appearance → Dashboard layout**, the existing Show/Hide and Up/Down card controls now include Stream overview, traffic, sessions and backend health. They are opt-in and account-scoped, saved through the existing `/api/preferences` flow. Legacy 12-card orders migrate in memory with all four Stream cards hidden. They use the existing status/SSE stream and stop computing their charts while hidden; no backend monitoring or listener settings are changed when toggled. An explicitly enabled Stream card stays on Overview and shows a neutral no-data state when no Stream is active. Overview details and chart legends stay visible inside the cards (without hover tooltips). Appearance Stream card descriptions use browser-native `title` hints on hover, with the same localized descriptions available to assistive technology through `aria-description` on the visibility buttons; they no longer add floating boxes or horizontal scrolling.
|
||
|
||
## Full Overview layout and additional metrics (0.32.3)
|
||
|
||
Open **Overview -> Arrange cards visually** (or **Settings -> Appearance -> Dashboard layout -> Visual layout editor**) to change **all 49** dashboard blocks: 30 previous tiles, 12 new optional charts, three large live charts, three diagnostic panels and the backend table. Drag tiles/sections, use arrow buttons for keyboard access, or hide and re-enable individual blocks. Large sections retain their own original charts, controls and live data after being moved. The responsive six-column desktop view scales to narrower widths. Visibility and order are account-scoped; they never alter backend behavior.
|
||
|
||
**Default view** loads the factory order and visibility into the editor **without saving**; **Save layout** applies it. **Reset layout** asks for confirmation, then persists the same default order and visibility in one preferences write. **Cancel** preserves the current layout. All 12 added charts are hidden until explicitly enabled and use a maximum of 30 status samples without additional HTTP polling. They cover process CPU/RSS/file descriptors, connections, WebSocket tunnels, TLS handshake failure and request-error rates, cache HIT share and HOT RAM usage, incoming HTTP/2 request share, mean response duration, and unhealthy backends. Unknown and first-delta measurements show `-`; response time is a mean, not a percentile, and HTTP/2 share is **frontend** traffic rather than negotiated upstream ALPN.
|
||
|
||
## HTTP backend Overview cards (0.32.3)
|
||
|
||
**Settings -> Appearance -> Dashboard layout** now contains fourteen opt-in
|
||
HTTP origin cards: **Backend health**, **Active backend requests**, **Backend
|
||
errors**, **Health probe RTT**, **Backend traffic**, **Configured backend
|
||
modes**, **Backend request rate**, **Backend capacity**, **HTTP 5xx**, **Backend
|
||
timeouts**, **Upstream connection errors**, **Backend retries**, **Backend
|
||
queue pressure**, and **Backend connection pools**. All are hidden by default on new and migrated administrator profiles;
|
||
card visibility and ordering use existing per-account `/api/preferences` fields.
|
||
They do not affect routing, health checks, upstream keep-alive or listeners.
|
||
|
||
The cards reuse `backend_pools` already included in the live status payload:
|
||
|
||
- Health separates up/down/checking and draining/disabled members. Active
|
||
requests is a sum of in-flight pool member exchanges, not open TCP sockets.
|
||
- Errors show lifetime member error counts and a sampled errors/s graph. Traffic
|
||
uses the positive differences of per-member in/out byte counters; resets and
|
||
newly added backend members cannot generate an artificial rate spike.
|
||
- Health probe RTT is the mean last observed healthy probe time, **not** client
|
||
end-to-end latency. It shows no data when health probes are unavailable.
|
||
- Configured backend modes distinguish saved `http1`, `auto`, compatible `http2`
|
||
and strict `h2` values, and count H2C cleartext members. This is **not an observed
|
||
H1/H2 negotiation rate**: TLS ALPN can select H1 for Auto and compatible HTTP/2.
|
||
- Backend request rate uses started-request counter deltas, with per-member
|
||
baselines after a restart or config change. Backend capacity compares active
|
||
requests against finite per-member concurrency limits; unlimited members are
|
||
excluded and a neutral state is shown when no such limit is configured.
|
||
|
||
The six additional diagnostics use pre-existing `backend_pools.members` counters:
|
||
HTTP 5xx responses, all timeout classes, combined connect/DNS/TLS/I/O failures,
|
||
retry attempts and successes from the existing safe retry policy, queue timeouts
|
||
and capacity rejections, and Hyper client-generation rotations and age. Rotations
|
||
are **not TLS handshake counts**. Rates are based on per-member positive counter
|
||
differences; newly added or reset members cannot create arbitrary spikes.
|
||
|
||
To arrange the actual Overview cards visually, click **Arrange cards visually**
|
||
on Overview or **Visual layout editor** in Settings -> Appearance -> Dashboard
|
||
layout. The modal displays copies of the existing telemetry cards; drag to
|
||
reorder, or use the move arrows for keyboard-only input. Hide/show tiles in
|
||
the modal and select **Save layout** once to persist order and visibility in
|
||
the existing `dashboard_widgets` and `dashboard_hidden_widgets` preferences.
|
||
Cancel/Escape discards the draft. The existing Appearance visibility and move
|
||
buttons remain available. No extra API endpoint, refresh loop, or database
|
||
migration is needed.
|
||
|
||
TLS ALPN selects H2 or H1 only when opening a **new TLS connection**, not for
|
||
each request. Hyper already pools reusable connections per compiled backend;
|
||
subsequent requests share the negotiated connection (H2 multiplexing when
|
||
possible). Existing bounded pool recycling (`pool_recycle_requests` and
|
||
`pool_recycle_seconds`), configured idle timeouts, and reopening failed
|
||
connections naturally re-evaluate TLS capabilities. A backend-global protocol
|
||
cache or scheduled H2 probe would be unsafe for origins with multiple DNS/peer
|
||
capabilities and would add network work. No traffic replay/downgrade on
|
||
post-request errors was added; strict H2, H2C, H1 and WebSocket separation
|
||
remain intact.
|
||
|
||
The sample charts are browser-local, bounded and reset on a changed config
|
||
revision, logout or source unavailability. They are not computed while hidden.
|
||
No additional polling, SQLite schema migration or new administrator endpoint
|
||
is introduced. Existing Stream widgets remain independent.
|