# RustProxy 0.25.0+ — Full Web Server / PHP-FPM Plan Status: design and implementation TODO Target: RustProxy 0.25.0 + Baseline reviewed: RustProxy 0.24.2 ## 1. Goal RustProxy 0.25.0 should remain a reverse proxy exactly as it is today, while optionally becoming a full web server for a vhost: - serve static files directly from disk; - serve directory indexes; - support HTTP validators and byte ranges; - execute PHP through PHP-FPM / FastCGI; - provide structured `try_files` and internal rewrite behavior; - support Nginx-style location matching already present in RustProxy; - provide presets for common applications such as Drupal, WordPress, Laravel and Symfony; - allow fully custom structured configuration; - keep all current proxy, cache, ACL, authentication, TLS, HTTP/2, HTTP/3, WebSocket, header and security behavior intact for existing configurations. The feature must be additive. A 0.24.2 configuration loaded by 0.25.0 must continue to behave as a reverse proxy without requiring edits and without silently changing routing semantics. --- ## 2. Non-negotiable compatibility rules ### 2.1 Do not reinterpret existing configuration The meaning of the following existing fields must not change: - `Vhost.backends`; - `Vhost.routes`; - `PathRoute.path_prefix`; - `PathRoute.match_type`; - `PathRoute.backend_id`; - `PathRoute.cache`; - `PathRoute.acl`; - `PathRoute.headers`; - `PathRoute.rewrite`; - `PathRoute.response`; - vhost/global ACLs; - Basic Auth; - Tinyauth; - rate limits; - security headers; - response compression; - transform configuration; - cache configuration; - listener selection; - HTTP/1.1, HTTP/2 and HTTP/3 behavior; - WebSocket behavior; - backend balancing/health/affinity; - custom pages; - revision/CAS configuration publication. In particular, the current `PathRewrite` remains an **upstream proxy path rewrite**. It must not be silently changed into an internal web-server rewrite. ### 2.2 Existing route precedence stays unchanged Keep the existing location matching rules: 1. exact; 2. longest prefix; 3. `prefix_priority` / Nginx `^~` skips regex evaluation; 4. first matching regex/regex-insensitive route in configuration order; 5. longest ordinary prefix; 6. vhost fallback. Repeated literal slashes keep the existing normalized routing behavior. Existing rejection of ambiguous encoded separators, encoded `%`, dot segments, controls and malformed path forms must remain. ### 2.3 Existing vhosts remain proxy vhosts by default If the new `web` configuration is absent or disabled: - vhost validation remains equivalent to 0.24.2; - a backend pool remains required; - the main vhost backend remains the fallback; - existing Custom Locations remain optional overrides; - local `return` behavior remains unchanged; - no local filesystem or FastCGI operation is performed. ### 2.4 No arbitrary Nginx configuration parser Do not add a raw `nginx.conf` execution/parser path. RustProxy should expose structured configuration that can represent the common Nginx behaviors needed for application hosting. Unknown fields remain rejected. ### 2.5 No hidden preset behavior A preset generates ordinary RustProxy configuration. After generation, every field is editable. Runtime behavior must never depend on a hidden `wordpress=true` or `drupal=true` flag. --- ## 3. Baseline behavior in 0.24.2 that must be preserved The current code already provides useful building blocks and they should be reused rather than replaced: - vhost/frontend selection is tied to the accepted local listener address and TLS state; - exact/wildcard hostname ownership is already separated from path matching; - main backend fallback is separate from user Custom Locations; - Custom Locations already support: - prefix; - `^~`-style priority prefix; - exact match; - case-sensitive regex; - case-insensitive regex; - existing proxy rewrites support: - preserve; - strip prefix; - replace prefix; - regex replacement with captures; - route ACLs are additive to host/profile/vhost ACLs; - global, vhost and route header rules already exist; - a route can already generate a local response without opening an upstream connection; - authentication and ACL decisions run before upstream I/O; - the immutable compiled snapshot is published only after validated revisioned configuration writes; - the response pipeline already centralizes security headers, server headers, diagnostics and compression. 0.25.0 should add local web origins to this architecture, not create a separate HTTP stack. --- ## 4. User-facing vhost model Add a dedicated **WWW** / **Web Server** tab inside the existing vhost editor. Recommended tab order: ```text Details Backends Health Locations WWW Auth SSL Cache Transform Security Pages Advanced ``` Do not create separate top-level modules for Drupal, WordPress or PHP. ### 4.1 UI mode The UI may display a derived mode: - `Proxy`; - `Web Server`; - `Mixed`. Prefer deriving the label from actual configuration instead of making a new required runtime `mode` field. Examples: - no `web` config -> Proxy; - `web.enabled=true`, local web fallback, no proxy routes -> Web Server; - `web.enabled=true` plus proxy routes/backends -> Mixed. ### 4.2 WWW tab — main sections #### Web root - Enable Web Server; - document root; - fallback target; - index files; - autoindex; - default MIME type; - symlink policy; - default static security policy; - effective path preview. #### Application preset - Custom; - Static website; - SPA; - Generic PHP front controller; - WordPress; - Drupal modern; - Drupal legacy `q=` routing; - Joomla; - Laravel; - Symfony; - Nextcloud; - phpMyAdmin. The preset is a generator only. #### PHP / FastCGI - enable PHP; - PHP-FPM profile; - Unix socket or TCP endpoint; - test connection; - connect/read/write timeout; - maximum connections; - request body limit; - default FastCGI params; - custom FastCGI params; - optional PATH_INFO behavior; - effective `SCRIPT_FILENAME` preview. #### Static files - index list; - directory redirect; - autoindex; - validator behavior; - Range support; - dotfile policy; - executable/source-file deny policy; - optional per-location root/alias. #### Routing / front controller - structured `try_files` editor; - internal URI fallback; - named internal location fallback; - internal rewrites; - rewrite flow `last` / `break`; - query-string behavior; - internal redirect limit. #### Advanced - named internal locations; - FastCGI params; - MIME overrides; - route-specific web handler configuration; - conditional rules from an explicit safe allowlist; - effective behavior preview. --- ## 5. Proposed persisted configuration The exact Rust types can be adjusted during implementation, but the compatibility properties below should remain. ### 5.1 Add an optional vhost web section Add an optional field to `Vhost`: ```rust #[serde(default, skip_serializing_if = "Option::is_none")] pub web: Option, ``` When absent, behavior is exactly the legacy proxy behavior. Suggested shape: ```json { "web": { "enabled": true, "document_root": "/var/www/example/public", "fallback": "web", "indexes": ["index.php", "index.html"], "autoindex": false, "symlinks": "within_root", "default_mime": "application/octet-stream", "php_profiles": [], "named_locations": [] } } ``` Suggested fallback values: - `proxy` — retain the main backend pool as unmatched-path fallback; - `web` — local web root is unmatched-path fallback; - `not_found` — no origin fallback, use the existing 404/page path. Rules: - if `web` is absent/disabled, fallback is implicitly legacy `proxy`; - `fallback=proxy` requires at least one proxy backend; - `fallback=web` does not require an HTTP backend; - `fallback=not_found` does not require an HTTP backend unless a proxy route references one. ### 5.2 Add optional local web handling to a PathRoute Do not change the meaning of existing fields. Add an optional field: ```rust #[serde(default, skip_serializing_if = "Option::is_none")] pub web: Option, ``` Action resolution for persisted routes: ```text response != null -> existing local Return action web != null -> new local Web action otherwise -> existing Proxy action ``` Validation must reject ambiguous combinations. For a `web` route: - `response` must be null; - `backend_id` must be null unless a future explicit proxy web action is added; - existing `PathRewrite` must remain `preserve`; internal web rewrite uses a new field/type; - proxy cache override should initially be rejected/unused for local static/FastCGI routes rather than silently changing meaning; - route ACL and header rules remain valid and should apply. This allows the existing location matcher to be reused for static/PHP routes without creating a second public URL matcher. ### 5.3 Do not repurpose existing PathRewrite Add a separate web rewrite model, for example: ```json { "web": { "handler": "static", "rewrites": [ { "pattern": "^/(.*)$", "replacement": "/index.php", "query": "q=$1", "flow": "last" } ] } } ``` The existing proxy `rewrite` field continues to modify only the upstream proxy target. ### 5.4 Structured try_files Prefer a structured model over accepting an arbitrary Nginx directive string. Example: ```json { "try_files": [ {"kind": "file", "value": "$uri"}, {"kind": "directory", "value": "$uri"}, {"kind": "named", "value": "drupal"} ] } ``` Other supported terminal candidates: ```json {"kind": "uri", "value": "/index.php", "query": "$args"} ``` ```json {"kind": "status", "value": 404} ``` The UI can render a familiar preview: ```text try_files $uri $uri/ @drupal; ``` without storing or executing raw Nginx syntax. ### 5.5 Named locations Named locations are internal-only and should not become public URL patterns. Suggested shape: ```json { "named_locations": [ { "id": "drupal", "action": { "type": "internal_redirect", "uri": "/index.php", "query": "q=$uri" } } ] } ``` Rules: - IDs use the existing safe ID rules where possible; - named locations cannot be requested directly by a client; - only `try_files` / internal rewrite may enter them; - loops are bounded; - their configuration is compiled into the immutable snapshot. --- ## 6. Necessary internal routing change A pure Web Server vhost cannot require a fake HTTP backend. 0.24.2 currently requires `1..32` backends and the compiled main backend fallback is also used to establish route ownership. 0.25.0 therefore needs a small internal routing refactor, but it must be behavior-preserving for all old configurations. ### 6.1 Regression-gated refactor Introduce an internal compiled action/target concept, for example: ```rust enum CompiledRouteAction { Proxy(CompiledProxyAction), Return(LocationResponse), Static(CompiledStaticAction), FastCgi(CompiledFastCgiAction), } ``` or an equivalent representation. This is an internal model only. It does not require rewriting old persisted JSON. Compile old configurations exactly as follows: ```text PathRoute.response = Some -> Return PathRoute.response = None and PathRoute.web = None -> Proxy legacy vhost fallback -> Proxy ``` New configurations may additionally compile to Static/FastCGI/local web fallback. ### 6.2 Host ownership must not depend on having a proxy fallback Frontend hostname/listener ownership already exists separately and must remain authoritative. For an enabled vhost: ```text claimed frontend -> existing custom location selection -> configured fallback target (proxy/web/not_found) ``` Do not continue to a broader wildcard merely because a path did not match, preserving the existing claimed-vhost behavior. ### 6.3 Backend validation becomes conditional Legacy proxy vhost: ```text web absent/disabled -> require 1..32 backends exactly as today ``` Web vhost: ```text 0..32 backends allowed ``` but validation must require a backend if any configured route or fallback uses Proxy. No HTTP backend should be required for a pure static/PHP vhost. ### 6.4 Legacy root normalization `PathRoute::is_legacy_main_fallback()` must treat any new web action/configuration as a deliberate customized `/` route. A new web route must never be removed as the old synthetic root fallback. --- ## 7. Request pipeline The existing outer request/response pipeline is valuable and should be retained. Desired high-level order: ```text accepted connection -> current listener/TLS/frontend selection -> current host/path normalization -> current redirect/local-host handling -> current host/profile/vhost ACL -> current rate limits -> current Basic Auth / Tinyauth -> current location selection -> current location ACL -> action dispatch: Return -> existing local return Static -> new static handler FastCGI -> new FastCGI handler Proxy -> existing proxy path unchanged -> current error-page processing -> current global/vhost/location response header rules -> current security headers/server headers/diagnostics -> current compression where safe -> H1/H2/H3 response body ``` ### 7.1 Critical rule Do not fork a second HTTP server pipeline for local web content. Static and FastCGI responses must flow through the same outer policies as proxy responses wherever those policies make sense. ### 7.2 Local web response marker Do not mark normal local website files with the existing behavior that forces placeholder/default pages to `no-store` and strips validators. Add a distinct response extension/marker for local web-origin responses if needed so that: - proxy-cache diagnostics are not incorrectly added; - `ETag` and `Last-Modified` remain intact; - application `Cache-Control` remains intact; - global/vhost/location response headers still apply; - security headers still apply; - compression may still apply when safe; - custom error pages may still replace error responses. --- ## 8. Static file server Create a dedicated module instead of growing `proxy/mod.rs` further. Suggested files: ```text src/web/mod.rs src/web/static_files.rs src/web/path.rs src/web/rewrite.rs src/web/fastcgi.rs src/web/fastcgi_protocol.rs src/web/presets.rs # only if presets are also generated server-side ``` ### 8.1 Required static features - GET; - HEAD; - 405 for unsupported methods when the route is a pure static file handler; - `Allow: GET, HEAD` where appropriate; - document root; - per-location root override; - alias support; - directory detection; - trailing-slash directory redirect; - index resolution; - MIME type detection; - configurable MIME overrides; - default `application/octet-stream`; - `Content-Length`; - `Last-Modified`; - `ETag`; - `If-None-Match`; - `If-Modified-Since`; - `304 Not Modified`; - byte ranges; - `206 Partial Content`; - `416 Range Not Satisfiable`; - `Accept-Ranges: bytes`; - `If-Range`; - streaming file bodies; - no whole-file buffering; - directory autoindex optional and OFF by default; - correct HEAD metadata with no body; - correct behavior under HTTP/1.1, HTTP/2 and HTTP/3. ### 8.2 Validators Recommended ETag strategy for 0.25.0: - metadata-based validator using stable file metadata such as size + modification time; - do not hash the complete file on every request; - document whether the tag is weak or strong; - obey HTTP conditional precedence correctly (`If-None-Match` before date validators). ### 8.3 Ranges P0: - one satisfiable byte range; - suffix ranges; - open-ended ranges; - correct 416 with `Content-Range: bytes */size`; - `If-Range`. For syntactically valid multi-range requests, either implement multipart ranges or safely ignore the Range and return a complete `200`. Do not generate malformed multipart responses. ### 8.4 Index handling Example: ```text index index.php index.html; ``` If a directory resolves to `index.php`, **never serve PHP source as a static file**. The index URI must enter the internal web dispatch path so the PHP/FastCGI handler is selected. ### 8.5 Autoindex - default OFF; - HTML escape names; - URL encode links; - never expose filesystem paths outside the configured root; - do not show denied dotfiles; - do not follow a symlink that violates the root policy; - HEAD support; - bounded directory entry count or streamed/bounded generation to avoid memory abuse. --- ## 9. Filesystem security This is a release blocker for 0.25.0. ### 9.1 Reuse current path validation Reuse the existing normalized routing path as the basis for local URL mapping. Do not add a second decoder that could allow double decoding. Continue rejecting: - encoded `/`; - encoded `\\`; - encoded `%`; - dot segments; - malformed percent escapes; - control characters; - ambiguous separators; - malformed UTF-8. ### 9.2 Root escape prevention A requested path must never escape its configured document root/alias through: - `..`; - percent encoding; - symlinks; - rename races where feasible to prevent; - platform separator differences. Prefer a capability/open-beneath style implementation rather than simple string-prefix checks. Cross-platform correctness for Linux/BSD is more important than Linux-only `sendfile` optimization. ### 9.3 Symlink policy Suggested explicit values: - `deny`; - `within_root` — recommended default; - `allow` — advanced opt-in with clear warning. Never make `allow` the default. ### 9.4 Sensitive files Provide a secure default/preset policy for common sensitive paths: - `.git`; - `.svn`; - `.hg`; - `.env`; - `.htaccess`; - `.htpasswd`; - editor/backup files where configured; - application-specific config files from presets. Do not over-block arbitrary dotfiles if the administrator explicitly opts out, but presets should be secure by default. ### 9.5 PHP/source disclosure prevention PHP-like executable source extensions must not silently fall back to static serving when: - PHP-FPM is down; - the PHP route is invalid; - FastCGI times out; - the FastCGI connection fails. Return an error (`502`/`504` as appropriate). Never expose source code as a fallback. Protect at least common PHP script extensions used by the configured policy (`.php`, and optional configured extensions such as `.phtml`/`.phar`). --- ## 10. Internal web routing ### 10.1 try_files Required behavior: ```text try_files $uri $uri/ @drupal ``` and: ```text try_files $uri $uri/ /index.php?$args ``` Candidate checks should be performed relative to the effective route root/alias. No candidate may escape the allowed filesystem root. ### 10.2 Internal redirect An internal redirect may change URI/query and run web route selection again. Required safeguards: - hard maximum redirect/rewrite depth, e.g. 10; - loop detection/diagnostics; - no client-visible redirect unless explicitly configured as a 3xx response; - re-evaluate the effective location after `last`; - preserve or replace query according to explicit configuration; - internal redirect target must pass the same path validator. ### 10.3 `last` and `break` Implement explicit structured behavior: - `last` — update internal URI and reselect location; - `break` — stop rewrite evaluation and continue the current location handler with the rewritten internal path. Do not reuse the existing proxy rewrite enum for this purpose. ### 10.4 Safe variables Initial variable allowlist should include only values with clear semantics: - `$uri` — normalized internal URI path; - `$request_uri` — original path/query where safe for value expansion, never for filesystem joining directly; - `$args`; - `$host`; - `$scheme`; - `$request_method`; - regex captures `$1..$9` for the current web rewrite rule; - `$document_root` for FastCGI parameter expansion; - `$request_filename` as a derived safe path representation for conditions, not as an unchecked arbitrary path. No arbitrary environment variable expansion from public requests. ### 10.5 Safe conditional rules Do not implement general Nginx `if` scripting. Support structured conditions needed by normal web hosting: - file exists; - file does not exist; - regular file exists; - directory exists; - URI regex; - request method; - hostname if genuinely needed. Example equivalent of: ```nginx if (!-e $request_filename) { rewrite ^/(.*)$ /index.php?q=$1 last; } ``` should be representable as an explicit condition + internal rewrite, not raw script text. --- ## 11. FastCGI / PHP-FPM Implement FastCGI as a local web origin, not as an HTTP proxy backend. ### 11.1 Endpoints Support: - Unix socket: ```text /run/php/php8.4-fpm.sock ``` - TCP: ```text 127.0.0.1:9000 ``` Optionally allow remote TCP FastCGI only through explicit configured endpoints. Never derive the endpoint from request data. ### 11.2 FastCGI protocol Implement/test the required FastCGI responder flow: - `FCGI_BEGIN_REQUEST`; - role `FCGI_RESPONDER`; - `FCGI_PARAMS`; - `FCGI_STDIN`; - `FCGI_STDOUT`; - `FCGI_STDERR`; - `FCGI_END_REQUEST`; - FastCGI name/value length encoding; - record splitting for values/bodies larger than one record; - bounded stderr capture/logging; - protocol error handling; - request IDs; - connection close/reuse state. ### 11.3 Connection model - async I/O; - bounded number of FastCGI connections per profile; - queue timeout; - connect timeout; - read timeout; - write/body idle timeout; - backpressure; - cancellation on downstream disconnect; - no unlimited buffers; - do not assume FastCGI multiplexing support; - reuse a connection only after a clean `END_REQUEST` and when configured/safe. PHP-FPM compatibility is the primary target. ### 11.4 Request body - stream POST/PUT/PATCH bodies to `FCGI_STDIN`; - do not buffer normal uploads completely in memory; - enforce a configurable maximum request size for web/PHP routes; - handle missing/invalid Content-Length safely; - preserve streaming behavior for HTTP/2 and HTTP/3; - abort the FastCGI request/connection safely when the client disconnects. ### 11.5 CGI/FastCGI response parsing Parse CGI-style response headers from `FCGI_STDOUT`: - `Status`; - `Content-Type`; - `Content-Length` if valid; - `Location`; - repeated `Set-Cookie`; - arbitrary safe end-to-end headers. Then: - remove/reject hop-by-hop/framing headers that an application must not control; - do not trust an application-supplied transfer framing header; - pass the body as a streaming HTTP response; - map malformed FastCGI output to `502`; - map connect failure to `502`; - map timeout to `504`; - map unavailable/capacity state consistently with RustProxy conventions. ### 11.6 PHP-FPM profiles Suggested profile: ```json { "id": "php-default", "endpoint": { "type": "unix", "path": "/run/php/php8.4-fpm.sock" }, "connect_timeout_ms": 1000, "response_timeout_seconds": 120, "write_timeout_seconds": 60, "max_connections": 32, "queue_timeout_ms": 5000, "keep_conn": true, "max_request_body_bytes": 134217728, "params": {} } ``` Limits are examples and must be validated against existing RustProxy resource conventions before final defaults are chosen. --- ## 12. FastCGI parameters Provide a safe built-in default equivalent to the useful part of common `fastcgi_params` / `fastcgi.conf` setups. Required parameters: - `GATEWAY_INTERFACE`; - `SERVER_SOFTWARE`; - `QUERY_STRING`; - `REQUEST_METHOD`; - `CONTENT_TYPE`; - `CONTENT_LENGTH`; - `SCRIPT_FILENAME`; - `SCRIPT_NAME`; - `REQUEST_URI`; - `DOCUMENT_URI`; - `DOCUMENT_ROOT`; - `SERVER_PROTOCOL`; - `REQUEST_SCHEME`; - `HTTPS`; - `REMOTE_ADDR`; - `REMOTE_PORT`; - `SERVER_ADDR`; - `SERVER_PORT`; - `SERVER_NAME`. Also expose incoming request headers as safe CGI `HTTP_*` parameters after normal RustProxy header validation/sanitization. ### 12.1 Trust boundary `REMOTE_ADDR` must come from RustProxy's already validated effective client identity, not directly from an untrusted forwarding header. `SERVER_ADDR` / `SERVER_PORT` must come from the accepted local frontend. `SERVER_NAME` comes from the normalized claimed vhost/host. ### 12.2 SCRIPT_FILENAME Default PHP mapping: ```text $document_root$fastcgi_script_name ``` but calculate it structurally rather than concatenating unchecked request text. Before opening FastCGI: - resolve against the permitted document root; - require an existing regular file unless explicitly configured otherwise; - enforce symlink/root policy; - reject traversal/ambiguous paths; - never pass an arbitrary filesystem path supplied by a client. ### 12.3 Custom params Allow per-profile and per-route overrides. Precedence suggestion: ```text built-in defaults -> PHP profile params -> route params ``` Protect parameters that define RustProxy's trust boundary from unsafe browser-controlled replacement where needed. ### 12.4 PATH_INFO Default OFF/safe. Provide an explicit structured option for applications that require PATH_INFO, similar in purpose to `fastcgi_split_path_info`, with tests for script/path confusion. Never enable permissive PHP path guessing by default. Document a secure PHP-FPM/PHP recommendation such as disabling unsafe path fixing where appropriate. --- ## 13. Mapping Nginx concepts to RustProxy | Nginx concept | RustProxy 0.25 design | | --- | --- | | `server_name` | existing vhost `frontend.hosts` | | `listen` | existing named listeners | | `location /x` | existing `PathMatch::Prefix` | | `location ^~ /x` | existing `PathMatch::PrefixPriority` | | `location = /x` | existing `PathMatch::Exact` | | `location ~ regex` | existing `PathMatch::Regex` | | `location ~* regex` | existing `PathMatch::RegexInsensitive` | | `root` | vhost web root / route root override | | `alias` | route web alias | | `index` | vhost/route web indexes | | `try_files` | structured web `try_files` | | `rewrite ... last` | new internal web rewrite + location reselection | | `rewrite ... break` | new internal web rewrite without reselection | | `if (-e ...)` | structured file condition | | `if (!-e ...)` | structured not-exists condition | | `fastcgi_pass` | PHP/FastCGI profile endpoint | | `fastcgi_param` | FastCGI profile/route params | | `add_header` | existing response header rules | | `return` | existing `LocationResponse` | | `Cache-Control` | existing response header rules / preset helper | | `Vary` | existing response header rules plus compression-owned values | | `status_zone` | RustProxy metrics keyed by bounded vhost/route IDs, not arbitrary dynamic zones | Do not duplicate existing header/ACL/return functionality inside the new WWW subsystem. --- ## 14. Drupal examples ### 14.1 Simple Drupal/front-controller behavior Nginx-style intent: ```nginx location / { try_files $uri $uri/ @drupal; index index.php index.html; } location @drupal { rewrite ^/(.*)$ /index.php?q=$1 last; } ``` RustProxy should represent this structurally as: ```text Web root: /var/www/drupal Indexes: index.php, index.html Fallback static handler: try file $uri try directory $uri otherwise -> @drupal Named location @drupal: internal URI -> /index.php query -> q= reselect location Regex location \.php$: FastCGI -> php-default script must exist under document root ``` ### 14.2 Advanced imagecache behavior Nginx-style intent: ```nginx location ^~ /files/imagecache/ { add_header Cache-Control "public, max-age=604800"; if_modified_since exact; index index.php; if (!-e $request_filename) { rewrite ^/(.*)$ /index.php?q=$1 last; break; } add_header Vary Accept-Encoding; } ``` RustProxy representation: ```text Location match: type: prefix_priority path: /files/imagecache/ Action: local web/static Response headers: existing HeaderRules: Cache-Control = public, max-age=604800 Vary += Accept-Encoding (with normal de-duplication/compression rules) Index: index.php Condition: if requested file does not exist Internal rewrite: regex: ^/(.*)$ target: /index.php query: q=$1 flow: last ``` The existing proxy `PathRewrite` is not used for this internal rewrite. --- ## 15. WordPress preset Generate ordinary configuration equivalent in intent to: ```nginx location / { try_files $uri $uri/ /index.php?$args; } location ~ \.php$ { fastcgi_pass ...; } ``` Preset requirements: - administrator selects WordPress root; - default indexes include `index.php`, `index.html`; - local static fallback; - front-controller fallback to `/index.php` preserving query args; - PHP regex route; - PHP script existence check; - block `.ht*`; - block common secret/backup files; - never serve PHP source statically; - allow normal `/wp-content` static files; - no hidden WordPress runtime flag. --- ## 16. Drupal presets Provide at least two clear choices: ### Drupal modern - normal static file checks; - front controller `/index.php`; - preserve normal query string; - secure PHP route; - common private file restrictions appropriate to the chosen document root. ### Drupal legacy `q=` - normal static file checks; - front controller `/index.php?q=`; - preserve/append original query according to explicit generated config; - secure PHP route. Do not guess the site's Drupal generation silently. The selected preset should be visible before applying. --- ## 17. Laravel / Symfony presets ### Laravel - document root must point to `public/`; - static file/directory check; - `/index.php` front controller; - PHP-FPM; - deny direct access outside the public root by construction. ### Symfony - document root `public/`; - static file check; - `/index.php` front controller; - PHP-FPM; - secure source-file behavior. The UI should warn if the administrator selects a known project root while the expected public directory exists beneath it, but never silently rewrite an administrator path without showing the result. --- ## 18. Static / SPA presets ### Static website - web fallback static; - indexes `index.html`; - no PHP; - autoindex off; - validators/ranges enabled; - secure dotfile policy. ### SPA Equivalent intent: ```text try file $uri otherwise internal fallback /index.html ``` API/static prefixes can still use existing Custom Locations or explicit web locations. --- ## 19. Nextcloud/phpMyAdmin and advanced presets Treat these as presets, not special server code. They may need: - additional blocked paths; - PATH_INFO handling; - well-known redirects/rewrites; - larger upload limits; - application-specific headers. Before shipping a preset, add fixture tests that verify the generated structured configuration. Do not ship a preset based only on an approximate Nginx snippet. --- ## 20. MIME handling Add a maintained built-in MIME map for common web types: - HTML; - CSS; - JavaScript; - JSON; - XML; - SVG; - images; - fonts; - audio/video; - WASM; - PDF; - archives; - text. Rules: - match file extension case-insensitively where appropriate; - administrator may override/add mappings; - unknown type -> `application/octet-stream`; - never infer executable behavior from MIME type; - PHP execution is determined only by an explicit FastCGI route/handler. --- ## 21. Cache and compression behavior ### 21.1 Browser cache Use normal HTTP headers for local static content: - `Cache-Control`; - `Expires` if configured; - `ETag`; - `Last-Modified`; - `Vary` where required. Existing route/vhost response header rules should remain the main mechanism for custom cache headers. ### 21.2 RustProxy object cache For 0.25.0 P0, do not require local static files to pass through the proxy object cache. The disk file is already the local origin and the OS page cache provides the primary file cache. Likewise, do not silently apply the existing proxy cache semantics to FastCGI unless a dedicated design is added and tested. A future FastCGI cache can be added explicitly. ### 21.3 Compression Reuse the existing response compression layer when safe. Rules: - do not compress a byte-range response incorrectly; - preserve validator semantics; - make `Vary: Accept-Encoding` correct; - do not double-compress a FastCGI response that already has Content-Encoding; - honor existing no-transform/private compression rules where applicable; - HEAD remains bodyless. Optional precompressed `.br`/`.gz` static file selection can be deferred unless implemented with complete validator/Vary tests. --- ## 22. Headers Reuse existing HeaderRules. Do not create another web-only header engine. For a local web route: - response header rules apply to static/FastCGI responses; - request header rules should apply before FastCGI `HTTP_*` parameter generation; - protected forwarding/framing headers remain protected; - canonical client identity remains RustProxy-owned; - FastCGI output cannot override hop-by-hop transport framing. --- ## 23. Authentication / ACL / rate limits Local web content must not bypass current gates. The following must continue to run before filesystem content/FastCGI application data is returned: - host ACL; - reusable ACL profile; - vhost ACL; - route ACL; - country policy; - vhost rate limits; - Basic Auth; - Tinyauth. Do not create a direct static-file shortcut before authentication. Authenticated responses retain current privacy protections. --- ## 24. Error handling Reuse existing custom error pages where possible. Expected mapping: - missing static file -> 404; - denied path/file policy -> 403/404 according to chosen security policy; - unsupported static method -> 405; - invalid Range -> 416; - FastCGI connect/protocol failure -> 502; - FastCGI timeout -> 504; - FastCGI capacity/queue exhaustion -> 503; - internal rewrite loop -> 500 with bounded diagnostic log; - invalid internal target -> 400/500 depending on whether it originated from public input or invalid administrator configuration. Do not leak absolute filesystem paths, socket paths or PHP-FPM stderr to public clients. Operational logs may expose sanitized administrator-useful detail. --- ## 25. Observability Add bounded metrics/log fields without high-cardinality public values. ### 25.1 Request origin/action Expose a bounded action category: - `proxy`; - `return`; - `static`; - `fastcgi`. ### 25.2 Static metrics Suggested counters: - static requests; - static bytes served; - 304 count; - range responses; - not found; - denied path; - index resolutions; - internal redirects/rewrite loop failures. ### 25.3 FastCGI metrics Suggested counters/gauges: - requests; - active connections; - queued requests; - connect errors; - protocol errors; - timeouts; - response latency; - bytes in/out. Use bounded profile/route IDs from configuration as labels. Never use arbitrary URI, script filename or query string as a Prometheus label. ### 25.4 Live logs Add useful bounded fields: - route ID; - action type; - FastCGI profile ID; - status; - duration; - optional relative script path, sanitized/truncated; - internal rewrite count. Do not log FastCGI secrets or full POST bodies. --- ## 26. Admin UI details ### 26.1 Main WWW card Fields: ```text [ ] Enable web server Document root: [/var/www/example/public] Unmatched requests: [Serve from web root | Proxy | 404] Index files: [index.php] [index.html] Autoindex: [off] Symlinks: [within root] Preset: [Custom v] ``` Show a warning if the root is not accessible by the RustProxy service user. ### 26.2 PHP card ```text [ ] Enable PHP/FastCGI Profile: php-default Endpoint type: Unix socket / TCP Socket: /run/php/php8.4-fpm.sock or host:port: 127.0.0.1:9000 [Test connection] ``` Then Advanced: - timeouts; - max connections; - queue timeout; - request body limit; - custom FastCGI params; - PATH_INFO. ### 26.3 Front controller builder Provide common choices: ```text Existing file -> serve it Existing directory -> serve/index it Otherwise -> /index.php Query handling -> preserve ``` Advanced representation may show: ```text try_files $uri $uri/ /index.php?$args ``` but the saved model remains structured. ### 26.4 Web locations Do not hide that URL matching is the same RustProxy location system. A location card can show action: - Proxy; - Return; - Static files; - PHP/FastCGI. The existing **Locations** tab remains the complete routing view. The **WWW** tab provides web-server-oriented defaults, presets and helpers. Avoid maintaining two independent location lists. ### 26.5 Effective behavior preview Extend the current location preview with: - action: Static / FastCGI; - effective document root/alias; - index list; - try_files chain; - selected PHP profile; - internal rewrite summary; - script filename template; - ACL/header composition. --- ## 27. API / OpenAPI Update: - vhost schema; - route schema; - WebServerSettings; - WebRouteSettings; - FastCGI profile schemas; - try_files candidates; - internal rewrite/condition schemas; - named location schemas; - status/diagnostic schemas if new metrics are returned. Keep: - authenticated admin API rules; - CSRF/revision CAS behavior; - `deny_unknown_fields` semantics; - current `/configuration` compatibility behavior; - no new anonymous admin endpoint. A connection test endpoint for FastCGI, if added, must be authenticated and bounded and must not permit an arbitrary user-supplied network destination outside the validated draft/config context. --- ## 28. Configuration validation Validation must happen before publishing a snapshot. Validate at least: - document root is absolute; - configured path length/control characters; - alias/root compatibility; - index filenames contain no separators/traversal; - MIME override names/values; - FastCGI endpoint syntax; - Unix socket path syntax; - timeout ranges; - connection/queue limits; - request body limit; - unique PHP profile IDs; - unique named location IDs; - named fallback references exist; - rewrite regex compiles within bounded regex limits; - internal rewrite replacement length; - try_files item count; - rewrite/redirect depth configured within hard server maximum; - web route and local response are mutually exclusive; - web route and legacy proxy-only settings do not conflict; - proxy route requires a backend pool; - `fallback=proxy` requires a backend pool; - pure web vhost may have zero backends; - PHP handler references an existing FastCGI profile; - PHP source cannot accidentally be configured as generic static fallback without an explicit dangerous override, preferably disallow entirely. Filesystem existence checks may be shown as UI/runtime diagnostics, but decide carefully whether a temporarily missing mount should invalidate the persisted configuration. Prefer safe runtime `503/404` diagnostics over preventing startup solely because a removable/mounted document root is momentarily absent, unless security depends on canonicalization. --- ## 29. Snapshot/runtime model All web configuration must compile into the same immutable snapshot philosophy as the rest of RustProxy. Request path must not query SQLite. Compile/store: - normalized root/alias configuration; - compiled regexes; - validated FastCGI endpoints; - parsed header/param templates; - named location lookup maps; - MIME map overlays; - bounded limits; - route action enum/representation. Keep dynamic state outside desired config: - FastCGI connection pool state; - FastCGI counters; - observed socket availability; - filesystem metadata/cache observations if any are added. Do not persist live FastCGI connection state. --- ## 30. FastCGI profile lifecycle On config publication: - reuse compatible runtime pool state when profile identity and endpoint/security parameters are unchanged, if practical; - otherwise retire the old pool gracefully; - existing in-flight requests hold their runtime references until completion; - new requests use the new snapshot/profile; - no request replay to another PHP-FPM endpoint after partial body transmission. This should follow the existing backend lease/publication philosophy. --- ## 31. System/service integration RustProxy remains non-root. Documentation must explain: ### Static roots The RustProxy service user needs read/search permission on the configured document tree. RustProxy must not automatically `chmod`, `chown` or upload site content in 0.25.0. ### PHP-FPM Unix socket The service user needs permission to connect to the PHP-FPM socket. Document typical approaches: - configure PHP-FPM socket owner/group/mode; - add RustProxy service user to the appropriate group where operationally acceptable. Do not automatically alter PHP-FPM configuration. ### MAC policies Mention SELinux/AppArmor/system policy considerations where relevant, but do not weaken them automatically. --- ## 32. Preset generation rules Presets must: - modify only the current vhost draft; - show what will change before/after apply; - preserve hostname/listener/TLS/auth settings unless a preset explicitly needs a web field; - never delete backends silently; - never silently remove custom routes; - warn about route conflicts; - create deterministic route IDs; - produce valid ordinary configuration; - be editable immediately after generation; - pass the same validator as handwritten configuration. If the vhost already has routes, offer a clear conflict strategy in the UI rather than overwriting them automatically. --- ## 33. Example proposed configuration — pure WordPress Illustrative only; final schema names may differ. ```json { "id": "wordpress", "enabled": true, "frontend": { "hosts": ["www.example.com"], "listener_ids": ["https"] }, "backends": [], "web": { "enabled": true, "document_root": "/srv/www/wordpress", "fallback": "web", "indexes": ["index.php", "index.html"], "autoindex": false, "symlinks": "within_root", "default_mime": "application/octet-stream", "php_profiles": [ { "id": "php-default", "endpoint": { "type": "unix", "path": "/run/php/php8.4-fpm.sock" } } ] }, "routes": [ { "id": "php", "path_prefix": "\\.php$", "match_type": "regex", "enabled": true, "web": { "handler": "fastcgi", "fastcgi_profile": "php-default", "require_script_file": true } }, { "id": "root-web", "path_prefix": "/", "match_type": "prefix", "enabled": true, "web": { "handler": "static", "try_files": [ {"kind": "file", "value": "$uri"}, {"kind": "directory", "value": "$uri"}, {"kind": "uri", "value": "/index.php", "query": "$args"} ] } } ] } ``` A final implementation may use the vhost web fallback for the root static action instead of persisting a synthetic `/` route. Whichever representation is chosen, do not reintroduce the old ambiguity between the vhost fallback and a user Custom Location. --- ## 34. Example proposed configuration — mixed static/PHP/proxy Use case: - website served locally; - `/api/` proxied to an application backend; - `.php` sent to PHP-FPM. Conceptually: ```text /api/ -> existing Proxy action *.php -> new FastCGI action all else -> local Static web fallback ``` The `/api/` proxy must use the same existing backend selection, health, retry/capacity, WebSocket and cache rules as 0.24.2. Turning on WWW must not route `/api/` through FastCGI or filesystem code when the existing Custom Location selected Proxy. --- ## 35. Source-file change map Expected areas. Keep changes focused. ### `src/vhost.rs` - optional `web` vhost settings; - optional route web settings; - validation; - conditional backend requirement; - legacy synthetic-root normalization updated to recognize deliberate web route configuration. ### `src/config.rs` - compile web settings; - compile route actions; - compile local web fallback; - keep existing match precedence; - validate cross references; - preserve claimed-host behavior. ### `src/proxy/mod.rs` Keep existing proxy branch as intact as practical. Changes should mainly be: - dispatch selected compiled action; - call the new web module for Static/FastCGI; - preserve common ACL/auth/response pipeline; - distinguish local web responses from placeholder/error local responses; - expose final/effective route to response policy if internal rewrites can select another route. Do not implement filesystem/FastCGI protocol details directly in this large module. ### `src/web/*` New implementation modules for: - secure path resolution; - static files; - conditions; - try_files; - internal rewrites; - FastCGI protocol/client; - MIME handling. ### `src/admin/web/vhosts.js` - WWW tab; - web settings editor; - PHP profile editor; - preset generator; - web route actions; - effective behavior preview; - validation messages. ### `src/admin/openapi.json` - schema additions only; preserve existing paths/contracts where possible. ### `langs/en_US.json`, `langs/pl_pl.json` - all new UI text goes through discovered language packs; - no hardcoded PL/EN switch. ### Documentation Add at least: ```text docs/WEB_SERVER.md docs/STATIC_FILES.md docs/FASTCGI.md docs/WEB_REWRITES.md docs/WEB_PRESETS.md ``` Update: ```text README.md ROADMAP.md CHANGELOG.md docs/VHOSTS.md docs/CUSTOM_LOCATIONS.md docs/API.md docs/SECURITY.md docs/TEST_REPORT.md ``` --- ## 36. Tests — existing behavior regression gate Before considering new WWW functionality accepted, prove old behavior is unchanged. Keep/add tests for: - exact route precedence; - `^~` priority prefix; - regex order; - longest prefix; - custom route -> main backend fallback; - claimed exact host does not fall through to broader wildcard; - old synthetic root normalization; - deliberate customized `/` remains a real route; - existing preserve/strip/replace/regex proxy rewrite behavior; - existing query preservation; - local `return`; - backend pinning; - balancing; - ACL composition; - Basic Auth; - Tinyauth; - cache behavior; - header rules; - security headers; - compression; - WebSockets; - H1/H2/H3. Add fixture tests that deserialize representative 0.24.2 JSON and verify effective compiled behavior under 0.25.0. --- ## 37. Tests — static files Required tests: - GET existing file; - HEAD existing file; - missing file 404; - directory trailing slash redirect; - directory index HTML; - directory index PHP enters FastCGI rather than static source; - autoindex disabled; - autoindex enabled; - MIME known extension; - MIME unknown extension; - Content-Length; - Last-Modified; - ETag; - If-None-Match 304; - If-Modified-Since 304; - validator precedence; - Range start/end; - suffix Range; - open-ended Range; - invalid/unsatisfiable Range 416; - If-Range match/miss; - HEAD + Range semantics; - large file streaming; - client disconnect; - compression with static file; - range not incorrectly compressed; - custom response headers; - custom error pages; - H1/H2/H3. --- ## 38. Tests — filesystem security Include explicit attacks: ```text /../secret /%2e%2e/secret /%2fetc/passwd /%5c..%5csecret /%252e%252e/secret //..//secret /a;b malformed % escape invalid UTF-8 escape symlink -> outside root symlink chain -> outside root alias boundary confusion root prefix string collision (/srv/www vs /srv/www2) ``` Also test races/behavior to the extent the selected safe filesystem API allows. PHP source must remain undisclosed under every failure case. --- ## 39. Tests — try_files and rewrites Required: - `$uri` existing file; - `$uri/` existing directory; - fallback URI; - named location; - status fallback `=404` equivalent; - query preserve; - query replace; - query append if supported; - regex captures; - `last` reselects location; - `break` remains in location; - maximum internal redirects; - direct loop A -> A; - loop A -> B -> A; - rewritten path still passes ambiguity/path validation; - condition file exists/not exists; - condition directory exists; - route ACL remains enforced after internal reselection. --- ## 40. Tests — FastCGI Build a deterministic fake FastCGI peer for automated tests. Real PHP-FPM remains a release-host integration gate. Test: - Unix socket connect; - TCP connect; - BEGIN/PARAMS/STDIN protocol framing; - long FastCGI param length encoding; - record splitting; - GET; - HEAD; - POST body streaming; - large upload; - chunked/H2/H3 request body adaptation; - standard PHP-like response; - `Status` header; - repeated `Set-Cookie`; - `Location`; - stderr handling; - END_REQUEST; - malformed response headers; - premature EOF; - protocol status failure; - connect refused; - connect timeout; - response timeout; - queue capacity exhaustion; - clean connection reuse; - broken connection not reused; - downstream cancellation; - protected hop-by-hop headers stripped; - `SCRIPT_FILENAME` correct; - missing script rejected before FastCGI; - script symlink outside root rejected; - `REMOTE_ADDR` uses trusted effective identity; - FastCGI custom params precedence; - PATH_INFO disabled default; - PATH_INFO explicit mode. --- ## 41. Tests — presets/UI For every shipped preset: - generated JSON/schema validates; - deterministic output; - expected web root/fallback; - expected routes; - PHP handler where needed; - safe blocked paths; - no hidden runtime preset flag; - applying a preset does not silently delete unrelated hostname/TLS/auth settings; - conflict warning for existing routes; - Custom mode round-trips generated values; - Advanced JSON and visual editor edit the same model; - EN/PL language packs contain every new key; - `scripts/check_source.py` language key checks pass; - JS syntax checks pass. --- ## 42. Release-host integration tests Before production-ready status, test with real services: ### PHP-FPM - supported distro PHP-FPM over Unix socket; - TCP PHP-FPM; - upload through PHP; - slow PHP timeout; - FPM restart while RustProxy remains running; - socket disappears/reappears; - connection pool recovery. ### Applications At minimum: - WordPress install/front page/admin login/static assets/permalinks; - Drupal front page/static assets/front-controller routing; - Laravel or Symfony front controller; - SPA fallback. ### Transports Run application requests over: - HTTP/1.1; - HTTP/2; - HTTP/3. No separate local-web implementation per transport. --- ## 43. Performance/resource requirements - no whole-file static buffering; - no whole-request PHP upload buffering; - bounded FastCGI connections; - bounded FastCGI queue; - bounded stderr capture; - bounded rewrite depth; - bounded try_files count; - bounded index list; - bounded named locations; - bounded custom params; - bounded autoindex generation; - no request-path SQLite access; - no request-path external configuration/provider calls; - cancellation-aware I/O; - full response/request permits retained for body lifetime consistent with existing resource policy. Cross-platform correctness first. `sendfile`/zero-copy may be an optimization after correct async streaming is proven. --- ## 44. Suggested hard limits Final values should be selected consistently with existing project limits, but define explicit bounds for at least: - max web routes per vhost; - max try_files candidates per route; - max internal rewrites per route; - max internal redirect depth; - max named locations; - max PHP profiles; - max FastCGI custom params; - max param name/value length; - max FastCGI stderr captured per request; - max index entries; - max MIME overrides; - max request body per web/PHP route; - max FastCGI connections/profile; - max FastCGI queue waiters. Do not create attacker-controlled unbounded vectors/maps on the request path. --- ## 45. Implementation sequence ### Phase 0 — lock current behavior - [ ] Add/confirm regression tests for current route/fallback behavior. - [ ] Add 0.24.2 config fixtures. - [ ] Add tests proving existing `PathRewrite` semantics. - [ ] Add tests for ACL/auth/header/security order. - [ ] Record current H1/H2/H3 behavior. ### Phase 1 — additive schema and internal action model - [ ] Add optional `Vhost.web`. - [ ] Add optional `PathRoute.web`. - [ ] Add new config structs with `deny_unknown_fields`. - [ ] Add validation. - [ ] Add internal compiled action/target model. - [ ] Compile all old configs to exactly existing Proxy/Return behavior. - [ ] Allow zero HTTP backends only when configuration has no Proxy requirement. - [ ] Update host ownership/fallback selection without changing legacy behavior. - [ ] Update synthetic-root normalization. - [ ] Run full regression suite before implementing filesystem access. ### Phase 2 — static file engine - [ ] Secure URL -> filesystem path resolver. - [ ] Root/alias. - [ ] GET/HEAD. - [ ] directory redirects. - [ ] index. - [ ] MIME. - [ ] Content-Length. - [ ] ETag/Last-Modified. - [ ] conditional GET. - [ ] Range/If-Range. - [ ] streaming body. - [ ] symlink policy. - [ ] dotfile/sensitive-file policy. - [ ] PHP source deny. - [ ] optional autoindex. - [ ] local web response marker/pipeline integration. ### Phase 3 — web routing - [ ] structured try_files. - [ ] named internal locations. - [ ] safe variables. - [ ] file/directory conditions. - [ ] internal rewrite engine. - [ ] query handling. - [ ] `last`. - [ ] `break`. - [ ] internal redirect limit. - [ ] final route response-header policy handling. - [ ] rewrite/loop diagnostics. ### Phase 4 — FastCGI/PHP-FPM - [ ] FastCGI wire protocol. - [ ] Unix sockets. - [ ] TCP. - [ ] connection pool. - [ ] timeouts/queue/cancellation. - [ ] PARAMS generation. - [ ] streaming STDIN. - [ ] streaming STDOUT. - [ ] bounded STDERR. - [ ] CGI response header parser. - [ ] status/error mapping. - [ ] PHP script existence/security. - [ ] FastCGI params. - [ ] optional PATH_INFO. - [ ] real PHP-FPM integration test. ### Phase 5 — WWW dashboard - [ ] new tab. - [ ] web root editor. - [ ] fallback editor. - [ ] PHP profiles. - [ ] FastCGI test UI. - [ ] static options. - [ ] try_files builder. - [ ] rewrite builder. - [ ] named locations. - [ ] location action Static/FastCGI. - [ ] effective behavior preview. - [ ] route conflict detection. - [ ] no duplicate independent location system. ### Phase 6 — presets - [ ] Static. - [ ] SPA. - [ ] Generic PHP. - [ ] WordPress. - [ ] Drupal modern. - [ ] Drupal legacy `q=`. - [ ] Joomla. - [ ] Laravel. - [ ] Symfony. - [ ] Nextcloud. - [ ] phpMyAdmin. - [ ] fixture tests for every preset. ### Phase 7 — API/observability/docs - [ ] OpenAPI. - [ ] metrics. - [ ] Prometheus. - [ ] live logs. - [ ] status diagnostics. - [ ] EN/PL translations. - [ ] docs. - [ ] README/ROADMAP/CHANGELOG. ### Phase 8 — release acceptance - [ ] `python3 scripts/preflight.py`. - [ ] `python3 scripts/check_source.py`. - [ ] `python3 scripts/release_smoke.py`. - [ ] `./dev.sh fmt`. - [ ] `./dev.sh check`. - [ ] `./dev.sh test`. - [ ] JS syntax checks. - [ ] OpenAPI/lang JSON parse. - [ ] real PHP-FPM Unix/TCP. - [ ] WordPress runtime. - [ ] Drupal runtime. - [ ] H1/H2/H3 runtime. - [ ] security/path traversal suite. - [ ] large file/upload streaming/resource test. - [ ] update `docs/TEST_REPORT.md` with what was actually executed vs blocked. --- ## 46. Explicit non-goals for 0.25.0 Unless separately accepted, do not expand scope into: - full Nginx config parser; - `.htaccess` parser; - Apache config compatibility; - arbitrary embedded scripting; - CGI process execution; - mod_php equivalent; - Node/Python/Ruby process manager; - site file upload/file manager; - automatic CMS installer/updater; - automatic PHP-FPM configuration changes; - arbitrary shell commands from web config; - arbitrary environment variable exposure; - FastCGI cache without a separate cache design; - reverse proxy behavior rewrite; - new authentication stack; - new TLS stack. The feature is a web/static/FastCGI origin inside the existing RustProxy vhost and policy architecture. --- ## 47. Definition of Done RustProxy 0.25.0 Web Server is complete when all of the following are true: - an unchanged 0.24.2 proxy config behaves the same under 0.25.0; - a vhost can run with zero HTTP backends when it is a pure web server; - a vhost can mix local static/PHP and existing proxied locations; - static files are streamed securely from disk; - directory/index handling works; - PHP source cannot leak through static fallback; - PHP-FPM works over Unix socket and TCP; - request/response bodies stream with bounded resources; - FastCGI params have safe trusted values; - `try_files` handles normal front-controller applications; - internal rewrites are bounded and do not reuse/alter legacy proxy rewrite semantics; - Drupal/WordPress presets generate editable ordinary config; - existing ACL/Auth/Tinyauth/rate/security/header/compression policies still apply; - existing H1/H2/H3 request pipeline is reused; - path traversal/symlink/root escape tests pass; - existing proxy/location/cache/WebSocket regression tests pass; - UI/API/manual JSON all edit the same canonical model; - release documentation clearly distinguishes verified runtime tests from static/source checks. --- ## 48. Recommended architectural summary The lowest-risk design is: ```text Existing vhost/frontend/listener/auth/ACL pipeline | v Existing location matcher | +--------+---------+-----------+ | | | Proxy Return Web | | | existing code existing code +---------------------+ Static FastCGI | | filesystem origin PHP-FPM origin +----------+----------+ | v existing response policy headers/security/compression/etc. ``` The WWW tab is a configuration UX over this canonical vhost model, not a separate server process and not a second competing routing engine. The main compatibility rule is simple: > If `web` is not configured, the request must follow the same path and semantics as RustProxy 0.24.2.