# Web Server — static sites, downloads and routing The built-in Web Server serves local files and, optionally, passes PHP scripts to an **external PHP-FPM service**. A reusable **Web Service** is referenced by a Vhost or by a Custom location. Its files and PHP settings are not duplicated in the Vhost. ## Create a Web Service 1. Open **Web Server → New Web Service**. Choose an ID/name and an absolute document root readable by the RustProxy service account. 2. Set index files (default `index.html`, `index.htm`), whether directory listing is allowed (off by default), MIME handling and download extensions. 3. Add `try_files`, rewrites, response headers and cache rules only if your application requires them. 4. Enable PHP only if you have a reachable, protected PHP-FPM endpoint; otherwise static files do not need any PHP runtime. 5. Save, create or edit a **Vhost**, select **Web Server** as source and choose the service ID. 6. Test index files, static assets, missing files, direct directory access, downloads and headers. A Web Service can be shared across several Vhosts. A Vhost may also use ordinary Reverse Proxy as its default and a Web Service for `/assets` or another custom location. Web Server and HTTP reverse proxy use the normal frontend TLS, access, logging and eligible cache pipeline. **Linux DEB/RPM paths:** With the updated package unit, `/home/user/public_html`, `/var/www/site` and `/srv/site` are readable using an appropriately scoped service-user group or POSIX ACL; no `systemctl edit` or `ReadOnlyPaths=` exception is needed. The filesystem remains read-only to the service outside its private state/log/config paths. Grant execute/traversal on each parent directory and read permission on served files; see [Installation: external paths](INSTALL.md#linux-debrpm-read-only-access-to-external-paths). ### Generated filesystem permission commands As you enter an absolute **Document root**, the editor generates permission commands immediately below the path. They refresh when opening or clearing a service, applying a preset or changing the root, so the displayed commands refer to the current field. The path must not contain `.`/`..` components or control characters and must resolve to an existing directory when the commands run; `/` is not accepted. Install the Linux `acl` tools if needed, review the commands and run them as an administrator on the web server. Saving a Web Service does not execute shell commands or grant filesystem permissions. The helper grants the `rustproxy` service account traversal (`--x`) of parent directories, read/traversal (`r-x`) on the website's directories and read (`r--`) on regular files. Symlinked document roots are resolved when the commands run, so both the configured path and the target's ancestors can be traversed. Symlinks within the served tree are not followed by the permission walk. Read-only ACLs do not grant writes or relax the Web Service's own symlink policy. Keep private content outside the document root. Default read/traverse ACLs are added to the existing website directories for newly created content. Default ACL inheritance takes precedence over the creator's `umask`; missing defaults are seeded from the directory's existing permissions. Explicit restrictive creation modes can still mask RustProxy's read access. Files moved into the tree or installed with preserved permissions may need the commands reapplied. Repeat the commands after a deployment changes the document-root symlink target. For a custom service account, change **Service user** under **Options and help** before copying; this changes only the instructions, not the process identity or saved configuration. For PHP applications, the separate **PHP-FPM pool user** needs its own access to scripts and any writable application directories. The generated RustProxy ACLs do not configure that user. Files must also be visible within the service's container/systemd namespace and permitted by SELinux/AppArmor if used; filesystem ACLs cannot override those policies. ## File serving options | Feature | Behavior / caution | |---|---| | **Document root** | Files are served only through the configured root; keep private keys, source secrets and writable control files outside it. | | **Indexes / listing** | Configurable index priority; directory listing is disabled by default. | | **`try_files`** | Try requested paths, then a fallback such as `/index.html` or the PHP front controller. Useful for SPAs and frameworks. | | **Rewrites** | Bounded regex internal rewrites or supported redirect statuses; evaluated in the Web Service routing pipeline. | | **MIME / downloads** | Built-in type mapping with extensions and explicit attachment options. | | **Headers / cache** | Service-level headers and optional static cache-control rules; later Vhost/location response rules may override. | | **Compression** | Dynamic gzip/Brotli/Zstandard/deflate policy and eligible precompressed `.br`/`.gz` sidecars. | | **File validators / ranges** | Conditional `ETag`/date checks, HEAD and supported byte ranges for eligible files. | | **Symlinks** | `deny` by default; `inside_root` is an explicit less restrictive policy. | | **Hardening** | Optional checks for common sensitive/attack paths and removal of selected server-identifying headers; not a full WAF. | ### Static delivery (`sendfile`) — 0.34.0 In **Web Server → Edit service → Delivery**, `sendfile` supports `off` (default) and `auto`. On **Linux**, eligible full static **GET** responses of at least **64 KiB** over **unencrypted HTTP/1.1** may use a kernel file-transfer path. TLS, HTTP/2, HTTP/3, ranged or transformed content, compressed responses and active object-cache responses keep the normal streaming path. This transport choice should not invalidate otherwise compatible cached objects. It is an **unverified source candidate** in the provided archive: test it on the actual server before use and do not assume a measured speedup. ## Rewrites and application presets The editor supports common website presets to fill suggested fields; review their document root, `try_files`, MIME and PHP values before saving. The two independent hardening checkboxes (block common attack probes and remove fingerprinting headers) are directly below the preset selection and Apply action; the chosen values remain editable. Presets do not install WordPress, Roundcube or other applications. For a single-page app, a fallback similar to `$uri`, `$uri/`, `/index.html` serves the SPA entry for routes without an existing file. For PHP frameworks, use the appropriate front controller; see [PHP-FPM](PHP_FPM.md). ## `.htaccess` compatibility An optional bounded Apache-style `.htaccess` compatibility layer supports a **subset** of rules useful for migrations, not arbitrary Apache modules or dynamic directive execution. It can scan configured paths and enforce stricter handling of unsupported expressions. Prefer explicit `try_files`/rewrite/header controls when possible and test migrated sites. A real Apache configuration cannot be pasted unchanged into RustProxy. ## Admin editor layout In **Web Server → Edit → Delivery**, static file delivery, dynamic compression and precompressed assets have separate sections. In **PHP-FPM**, profile/endpoint, request options, resource limits and FastCGI/php.ini overrides are grouped. Disabled local controls indicate that PHP is off or a shared profile owns those settings; selection and saved values behave as before. ## PHP and error responses A Web Service may enable PHP via a shared PHP-FPM profile or its own configuration. PHP is executed by the FPM service, not RustProxy, and must not be exposed as a raw Internet port. Use dedicated pools for different applications. See [PHP-FPM](PHP_FPM.md). Missing static files return **HTTP 404 Not Found**, unreadable/blocked paths **403 Forbidden**, unsupported static methods **405 Method Not Allowed** (with `Allow: GET, HEAD`), and internal failures **500 Internal Server Error**. The built-in Web Server now uses the existing **application-style HTML error pages** for statuses with a bundled template (including 400, 403, 404, 500 and PHP-FPM gateway errors). Other generated error statuses, such as 405, use the shared styled HTML fallback. Neither internal filesystem paths nor `RustProxy: Web Server ...` diagnostics are included in the public body. Details for 5xx filesystem failures remain in server logs. These generated HTML responses retain the actual HTTP status, `Content-Type: text/html; charset=utf-8`, `X-Content-Type-Options: nosniff` and `Cache-Control: no-store`; `HEAD` preserves the matching `Content-Length` but sends no body. Explicit application/origin responses are still passed through without forced replacement, subject to the Vhost error interception options below. Vhosts can intercept selected application error bodies in **Pages** and return a configured HTML error page while preserving the status. Internal location `error_fallback` is different: it routes the error to a named internal handler. Error-page replacements are marked uncacheable. Origin-supplied and explicitly configured custom error bodies are preserved unless interception is enabled. ## Monitoring and issues A Web Service's dashboard shows request rate, status/error counts, response-header timing, delivered bytes and PHP permit usage (when PHP is configured). These are application observations, not proof of external PHP worker count or final compressed network bytes. - **403 / unexpected block:** inspect document-root permissions (including parent traversal), symlink policy, hardening, inherited ACLs and any SELinux/AppArmor denial; verify the active systemd unit is the updated one. - **404 / unwanted SPA rewrite:** check actual files, index list, `try_files`, location matching and rewrite order. - **Wrong Content-Type/download:** check MIME override and attachment extensions. - **PHP script downloaded instead of executed:** check PHP enablement, script extension and FPM endpoint. - **Cache not filling:** inspect both global and Vhost cache, static response headers and cookies. Keep all document roots and external application processes separately patched and secured.