v0.34.0_fina;

This commit is contained in:
Mateusz Gruszczyński committed 2026-10-18 12:00:59 +02:00
1 parent 1ca019d849
commit c207e8a03f
69 files changed
+1496 -13838

No files matched your search

+40 -403
View File
@@ -1,417 +1,54 @@
# PHP-FPM / FastCGI for the Web Server
# PHP-FPM with Web Services
**Separate pools for distinct Web Services:** Reusing the same PHP-FPM socket
with different `PHP_ADMIN_VALUE` maps can leak `open_basedir` restrictions into
a different virtual host. See the 0.29.7 guide
[PHP_FPM_ISOLATION.md](PHP_FPM_ISOLATION.md) for remediation and tests.
RustProxy's Web Server supports **FastCGI** forwarding to an existing **PHP-FPM** pool for configured script extensions. RustProxy does not include a PHP interpreter or run the PHP application itself.
RustProxy 0.25 can execute configured PHP extensions through a long-running PHP-FPM pool while preserving the reusable Web Service architecture.
## Before enabling PHP
## Architecture
Install and start PHP-FPM independently, configure the application files and create a **dedicated pool/socket for each application or security domain**. Set the correct Unix user/group, socket permissions, PHP version and pool-level configuration. Restrict a TCP FPM endpoint to a trusted private interface; never expose FastCGI directly to the Internet.
In **Web Server → PHP-FPM profiles** create a reusable profile, or set PHP options in one Web Service. Select a socket such as `unix:/run/php/app.sock` or a reachable TCP endpoint (use the syntax accepted by the editor); enable PHP, choose extensions (default `.php`) and set request/connect timeouts and concurrency. Attach the profile to a Web Service and publish that service using a Vhost.
## Important settings
| Setting | Effect |
|---|---|
| **Endpoint** | Unix socket or private TCP address of a real PHP-FPM service. |
| **Extensions** | Which files are eligible for PHP execution. Confirm front-controller routes and direct PHP paths. |
| **`try_files` / PATH_INFO** | Resolve PHP front-controller or `script.php/...` paths, where supported, without accidentally executing an unintended file. |
| **Connect/request timeouts** | Bound waiting for FPM connection and application execution. Defaults in source include 1 s connect and 30 s request. |
| **Concurrent requests** | Local RustProxy FastCGI request limit (default 64), **not** the number of FPM worker processes. |
| **Authorization forwarding** | Optional preservation of eligible `Authorization` information (default enabled). Review application expectations. |
| **Custom FastCGI parameters** | Static application-specific settings. Reserved transport/identity parameters cannot be overridden arbitrarily. |
| **PHP values** | `PHP_VALUE` and `PHP_ADMIN_VALUE` options for the external FPM workers. |
| **Body/header limits** | Bound upload size, response headers and stderr exposed in logs. |
## Pool isolation is mandatory for mixed applications
PHP-FPM workers can retain `PHP_VALUE` / `PHP_ADMIN_VALUE` behavior across requests. **Two applications with different INI/admin settings must not share one FPM worker pool**, even when they have different RustProxy Vhosts. RustProxy may warn about conflicting shared endpoints but cannot isolate PHP workers inside FPM.
Example separation:
```text
Request
-> existing listener / vhost / location / ACL / auth
-> Web Server -> Web Service
|- static file
`- configured PHP extension -> FastCGI -> PHP-FPM
-> existing error/header/logging/metrics/transport pipeline
Web Service: blog -> unix:/run/php/php_blog.sock
Web Service: webmail -> unix:/run/php/php_webmail.sock
```
Vhosts and locations still reference only a Web Service. Each Web Service may
use local PHP settings or reference a shared `php_fpm_profiles` entry from the
normal revisioned configuration; its local settings are retained on detach.
Give each pool access only to its own application's files; configure `open_basedir` and other hardening at FPM pool level where possible. Restart the affected FPM pools after policy changes to retire existing workers.
### Shared PHP-FPM profiles (0.29.4)
## WordPress and Roundcube
In **Web Server → Edit Web Service → PHP-FPM**, select **Local configuration**
or a named profile. **Manage PHP-FPM profiles** opens a modal with an expandable
editor for every profile and an **Add profile** action. A profile contains the
same PHP execution, FastCGI and PHP_VALUE/PHP_ADMIN_VALUE settings as local
configuration. Changes to a shared profile affect all attached services after
validation and apply. In-use profiles cannot be deleted.
For **WordPress**, point the document root to the website directory, enable PHP for `.php`, use the appropriate front-controller (`index.php`) and verify that `wp-config.php` exists and is readable only where needed. A redirect to `setup-config.php` can indicate filesystem/FPM/open_basedir misconfiguration rather than proxy cache.
Configuration example:
For **Roundcube**, use its published web root and a separate FPM pool with the application's expected file access. Some Roundcube static script URLs use `PATH_INFO`; enable/test handling that resolves `static.php/...` without turning arbitrary strings into executable script names.
```json
{
"php_fpm_profiles": [{
"id": "php82", "name": "PHP 8.2 FPM",
"php": {"enabled": true, "endpoint": "unix:/run/php/php8.2-fpm.sock"}
}],
"web_services": [{
"id": "site", "name": "Site", "document_root": "/var/www/site",
"php_profile_id": "php82"
}]
}
```
These are routing examples; presets do not install either application or choose a safe OS-specific pool automatically.
Unspecified PHP fields inherit the safe serde defaults. A service without
`php_profile_id` uses its own `php` object as in earlier versions.
## Diagnostics
## Where to configure each PHP setting (0.29.7)
- `502/503/504`: check FPM health, Unix socket group permissions, endpoint path, capacity and configured timeouts.
- PHP errors/blank page: check FPM stderr, application logs and effective INI values.
- Incorrect site appears to inherit another site's `open_basedir`: use distinct FPM pools and restart them.
- Compressed PHP content truncated: verify current application response/framing and try the same request without compression; review FastCGI and response logs.
- Cache behavior differs from static files: PHP may ignore validators or send `private` / `Set-Cookie`, preventing shared cache storage.
Both editors expose the **same fields and validation**:
1. **Web Server → Web Service → PHP-FPM → Local configuration:** set values on one service (`web_services[].php`). Select **Local configuration** in the profile selector first.
2. **Web Server → PHP-FPM profiles → Add/Edit profile:** set shared values on a profile (`php_fpm_profiles[].php`), then attach it in a Web Service's PHP-FPM tab (`php_profile_id`). Editing the profile changes every attached service. Per-service `php` values remain saved for later detach but do **not** override an attached profile.
3. **Actual PHP-FPM pool config** (`php-fpm.d/*.conf` or distribution equivalent): the pool must exist and listen on the configured socket. Isolate restrictive `open_basedir`/`php_admin_value` settings in *separate pools and sockets*, not by alternating request-time `PHP_ADMIN_VALUE` on shared workers. RustProxy does not launch PHP-FPM.
| UI field (JSON property) | What to enter / sample | Notes |
| --- | --- | --- |
| Enable PHP-FPM / FastCGI (`enabled`) | Checked for PHP apps | Off means PHP is not executed. Never serve PHP source. |
| PHP-FPM socket (`endpoint`) | `unix:/run/php/php_wordpress.sock`, `tcp:127.0.0.1:9000`, `tcp:[::1]:9000` | Only local Unix sockets or TCP loopback **IP**, not hostnames. Socket ownership and permissions must allow RustProxy access. |
| PHP extensions (`extensions`) | `php, phtml` | Comma-separated, no leading dots; at most 16. Only listed extensions are executable. |
| Enable PHP PATH_INFO splitting (`path_info`) | Checked for Roundcube `/static.php/skins/...` | Splits an existing PHP script path and its trailing URL; off for ordinary WordPress unless needed. |
| Forward Authorization (`pass_authorization`) | Checked when API / Basic / Bearer auth is used | Sends request `Authorization` to `HTTP_AUTHORIZATION` in FastCGI. |
| Connection timeout (`connect_timeout_ms`) | `1000` = 1 second | Integer 50–60000 milliseconds. |
| Request timeout (`request_timeout_ms`) | `30000` = 30 seconds | Integer 100–3600000 milliseconds for the full FastCGI request. |
| Max simultaneous PHP requests (`max_concurrent_requests`) | `64` | Integer 1–4096 per Web Service; excess is rejected with HTTP 503. |
| Request-body limit (`max_request_body_bytes`) | `67108864` = 64 MiB; `0` = no additional limit | Integer 0–8589934592 **bytes**, independent of PHP `post_max_size`. |
| Response-header limit (`max_response_header_bytes`) | `65536` = 64 KiB | Integer 4096–1048576 bytes. |
| PHP-FPM STDERR log limit (`stderr_log_bytes`) | `4096`; `0` = disabled | Integer 0–65536 bytes collected per request. |
| Custom FastCGI parameters (`custom_params`) | `APP_ENV = production` | One static `NAME = value` per line. Names A-Z/0-9/underscore, beginning with a letter. No core CGI, HTTP_* or PHP control params. |
| PHP INI overrides (`php_values` / `PHP_VALUE`) | `memory_limit = 256M` | One php.ini `directive = value` per line. PHP decides which directives can change at this scope. |
| Administrator PHP INI overrides (`php_admin_values` / `PHP_ADMIN_VALUE`) | `open_basedir = /var/www/webmail/:/tmp/` | Trusted operator only; for security isolation prefer dedicated PHP-FPM pool configuration. Do not define the same directive in both INI maps. |
**Syntax in the three multiline fields is not JSON:** use `key = value`, a separate line per entry; **do not** wrap it in `PHP_VALUE=...` or `php_admin_value[...]` inside the RustProxy editor. Those latter directives belong to the *native PHP-FPM pool configuration*, not the RustProxy FastCGI request fields. Empty values and control characters are rejected. Up to 64 entries per map, maximum 8192 bytes per value and 128 KiB combined.
### Ready-to-use WordPress and Roundcube examples
**WordPress — Web Service `/var/www/linuxiarz.pl/`, dedicated FPM socket:**
```text
PHP enabled: checked
PHP PATH_INFO: unchecked
Endpoint: unix:/run/php/php_wordpress.sock
PHP extensions: php
PHP_VALUE:
memory_limit = 256M
upload_max_filesize = 64M
post_max_size = 64M
max_execution_time = 60
PHP_ADMIN_VALUE: leave empty (or set security restrictions in WordPress's own FPM pool)
```
Use `index.php` in **Index files**, plus `try_files` lines `$uri`, `$uri/`, `/index.php?$query_string` in **Routing**. The WordPress pool must be allowed to read its `wp-config.php`, `wp-includes` and `wp-content` directories.
**Roundcube — Web Service `/var/www/webmail/public_html`, separate FPM socket:**
```text
PHP enabled: checked
PHP PATH_INFO: checked
Endpoint: unix:/run/php/php_roundcube.sock
PHP extensions: php
PHP_VALUE:
memory_limit = 256M
upload_max_filesize = 64M
post_max_size = 64M
PHP_ADMIN_VALUE: prefer the native Roundcube PHP-FPM pool instead; e.g.:
open_basedir = /var/www/webmail/:/usr/lib/php/:/tmp/:/etc/enigma
```
Add additional permitted external paths only if Roundcube's actual dependencies need them. Roundcube assets such as `/static.php/skins/elastic/...` require PATH_INFO splitting. Restart/recycle workers after separating pools; using a different RustProxy profile but the **same socket** does not isolate the worker state. See [pool isolation](PHP_FPM_ISOLATION.md).
### FastCGI vs php.ini examples
```text
# Custom FastCGI parameters — application-specific static environment:
APP_ENV = production
APP_DEBUG = 0
# PHP_VALUE — regular PHP-level options:
memory_limit = 256M
max_execution_time = 60
error_reporting = E_ALL
log_errors = On
display_errors = Off
date.timezone = Europe/Warsaw
# PHP_ADMIN_VALUE — privileged operator-level options:
open_basedir = /var/www/webmail/:/usr/lib/php/:/tmp/
```
The `#` comments above are explanations separating fields; **do not paste them** into the corresponding form inputs. Boolean INI directives commonly accept `On`/`Off`, size directives can accept `K`/`M`/`G` and timeouts use seconds. The actual PHP/FPM version determines supported directives and permitted scopes.
## Runtime model
RustProxy implements the FastCGI protocol directly. It does not spawn `php`, `php-cgi` or shell processes for individual requests.
Supported endpoint syntax:
```text
unix:/run/php/php-fpm.sock
tcp:127.0.0.1:9000
tcp:[::1]:9000
```
Use the `unix:` prefix for a Unix-domain socket. Use `tcp:` followed by a literal loopback IP address and port for TCP. Hostnames and non-loopback addresses are rejected. Unix sockets are preferred for a local PHP-FPM installation; exposing FastCGI directly to an untrusted network is not supported by this configuration.
## Web Service configuration
```json
{
"index": ["index.html", "index.php"],
"try_files": ["$uri", "$uri/", "/index.php?$query_string"],
"php": {
"enabled": true,
"extensions": ["php"],
"endpoint": "unix:/run/php/php-fpm.sock",
"connect_timeout_ms": 1000,
"request_timeout_ms": 30000,
"max_concurrent_requests": 64,
"pass_authorization": true,
"max_request_body_bytes": 0,
"max_response_header_bytes": 65536,
"stderr_log_bytes": 4096,
"custom_params": {
"APP_ENV": "production"
},
"php_values": {
"memory_limit": "256M",
"upload_max_filesize": "64M",
"post_max_size": "64M"
}
},
"symlink_policy": "deny"
}
```
The normal ordered `index` list handles `index.php`; there is no separate PHP-index option.
### Additional PHP/FastCGI options
- `path_info`: default-off secure script/path splitting. When enabled, `/index.php/foo/bar` may resolve the existing `index.php` script and pass `/foo/bar` as `PATH_INFO`.
- `pass_authorization`: forwards the inbound `Authorization` header as `HTTP_AUTHORIZATION`. Disable it when the application does not need that header.
- `max_request_body_bytes`: streaming request-body limit enforced before/during FastCGI forwarding. `0` means no additional RustProxy-side limit. A request exceeding the configured value receives `413 Payload Too Large`.
- `max_response_header_bytes`: maximum bytes accepted before the CGI response-header terminator. The allowed range is 4096..1048576 bytes.
- `stderr_log_bytes`: maximum PHP-FPM stderr bytes retained for one diagnostic request entry. `0` disables stderr capture; maximum 65536.
- `custom_params`: trusted static FastCGI parameters supplied by the operator.
- `php_values`: php.ini-style overrides encoded into one validated FastCGI `PHP_VALUE` parameter.
### Custom FastCGI parameter syntax
In the administration UI use one entry per line:
```text
APP_ENV = production
APP_DEBUG = 0
TENANT = main
```
Names are normalized to uppercase and are limited to letters, digits and `_` (the first character must be a letter). Values are static: there is no request-variable interpolation in this field. RustProxy rejects core CGI parameters, `HTTP_*`, `PHP_VALUE` and `PHP_ADMIN_VALUE`, so custom values cannot replace `SCRIPT_FILENAME`, routing data, HTTP-derived fields or PHP control parameters. Up to 64 custom FastCGI parameters and 64 PHP INI overrides are accepted, with at most 128 KiB of combined custom parameter/value data.
### PHP INI (`PHP_VALUE`) syntax
Use one `directive = value` entry per line:
```text
memory_limit = 256M
upload_max_filesize = 64M
post_max_size = 64M
max_execution_time = 30
```
RustProxy validates the lines and combines them with newline separators into the FastCGI `PHP_VALUE` parameter. PHP-FPM/PHP still decides whether a directive is changeable at this scope. `PHP_ADMIN_VALUE` is available separately through the validated administrator INI overrides for trusted PHP-FPM endpoints. Control characters/newlines inside names or values are rejected.
These custom parameters are stored as normal Web Service configuration. Do not put secrets there unless normal configuration storage is an acceptable location for them.
## `try_files` front controller
A typical application can use:
```text
$uri
$uri/
/index.php?$query_string
```
Existing static files/directories are used first. The final path becomes the fallback and can therefore reach `index.php`. This resolution remains inside the already-selected Web Service and does not introduce a second vhost router.
## Roundcube 1.7 static.php / PATH_INFO (0.29.5)
Roundcube serves CSS, JavaScript and images through URLs such as
`/static.php/skins/elastic/styles/styles.min.css?s=123`. These paths are
**PHP-FPM requests to the existing** `public_html/static.php` **script**, not
static filesystem lookups. Configure the selected Web Service (or attached
shared PHP-FPM profile) with `php.enabled: true`, `php.path_info: true`, and
`php.extensions: ["php"]`. Point its root to the Roundcube `public_html` tree.
You can keep the normal Web Service `try_files` (`$uri`, `$uri/`,
`/index.php?$query_string`). RustProxy 0.29.5 resolves a valid PHP script plus
PATH_INFO after security checks and rewrites, **before** falling back through
`try_files`. No regex rewrite or extra Web Service is required for these URLs
when the main vhost selects this Web Service.
For `/static.php/skins/elastic/images/favicon.ico?s=123`, FastCGI receives:
```text
SCRIPT_NAME=/static.php
SCRIPT_FILENAME=<document_root>/static.php
PATH_INFO=/skins/elastic/images/favicon.ico
REQUEST_URI=/static.php/skins/elastic/images/favicon.ico?s=123
QUERY_STRING=s=123
```
PHP-FPM still needs filesystem access to `static.php` and the Roundcube
installation outside `public_html` that script reads. The PHP `PATH_INFO`
option is explicit and remains off by default for unrelated services.
## Bounded execution
Each compiled Web Service owns a bounded PHP concurrency semaphore. `max_concurrent_requests` limits simultaneous FastCGI requests. RustProxy does not create an unbounded wait queue: if no execution slot is immediately available it returns `503 Service Unavailable` with `Retry-After`.
Connection establishment and complete FastCGI request execution have independent configured time limits. A connect failure produces a gateway error; a request timeout produces a gateway timeout. The outer normal RustProxy error-page processing remains able to replace these responses.
Request bodies are split into bounded FastCGI records rather than collected into one large buffer. When `max_request_body_bytes` is non-zero, the limit is checked both from `Content-Length` when available and while streaming chunked/unknown-length bodies. PHP-FPM stdout is parsed and delivered through a bounded response channel so application output can stream to the client.
## Secure script selection
Only configured extensions are executable. Before PHP is selected, RustProxy resolves the request/`try_files`/index path using the Web Server filesystem confinement rules and verifies that it is a regular file below the document root.
When PHP handling was selected:
- PHP source is never returned as a fallback;
- an unavailable PHP-FPM endpoint produces 502/504 behavior rather than source disclosure;
- encoded traversal/separators and `..` remain rejected;
- the current implementation requires `symlink_policy=deny`.
The last restriction is deliberate: PHP-FPM opens `SCRIPT_FILENAME` independently, so RustProxy does not claim an `inside_root` symlink guarantee that the external process cannot enforce atomically.
## FastCGI parameters
RustProxy supplies a controlled CGI/FastCGI parameter set including:
- `REQUEST_METHOD`
- `SCRIPT_FILENAME`
- `SCRIPT_NAME`
- `PATH_INFO` when `path_info` is enabled and a trailing path was securely split from an existing PHP script
- `QUERY_STRING`
- `REQUEST_URI`
- `DOCUMENT_ROOT`
- `SERVER_PROTOCOL`
- `SERVER_NAME`
- `SERVER_PORT`
- `REMOTE_ADDR`
- `REMOTE_PORT` when available
- `HTTPS`
- `GATEWAY_INTERFACE`
- `SERVER_SOFTWARE`
- `REDIRECT_STATUS`
- `CONTENT_TYPE`
- `CONTENT_LENGTH`
- selected request headers as `HTTP_*`
Request headers cannot become FastCGI `PHP_VALUE` or `PHP_ADMIN_VALUE` parameters. Operator-defined `custom_params` are allowed only through the validated Web Service configuration and cannot override core CGI/HTTP-derived fields. Operator-defined `php_values` are the only path that creates `PHP_VALUE`; `PHP_ADMIN_VALUE` is never generated.
An inbound `Proxy` header is **never** translated into FastCGI `HTTP_PROXY` (httpoxy mitigation), regardless of the optional Web Service hardening switch. The **Block common attack probes** option additionally blocks high-confidence sensitive/probe paths before PHP-FPM dispatch. FastCGI output cannot control downstream `Content-Length`, transfer framing, connection headers or `Proxy-*` transport headers; RustProxy owns downstream response framing.
`PATH_INFO` splitting is optional and disabled by default. When enabled, RustProxy searches for an existing configured PHP script prefix inside the document root, keeps `SCRIPT_FILENAME`/`SCRIPT_NAME` on that script, and supplies only the remaining suffix as `PATH_INFO`. `REQUEST_URI` remains the original request target.
## Response parsing
The FastCGI stdout parser understands standard CGI response headers, including `Status`. Hop-by-hop transport headers are stripped before returning the response through RustProxy.
For `HEAD`, PHP-FPM can still execute normally but the client response body is empty. GET/POST/PUT and other methods can reach a PHP script once routing resolves to a configured executable extension.
PHP stderr is bounded by `stderr_log_bytes` and recorded as a RustProxy `WARN` diagnostic. It goes to the configured error file when enabled, or the console/systemd journal otherwise; it is not sent to the client as page content. CGI response headers are separately bounded by `max_response_header_bytes`.
## Caching
PHP output does not use static file validators or the static-file OS representation path. Application response headers and the normal vhost/location response-header pipeline control client/intermediary caching.
The Web Service static `cache_rules` apply to local static file responses, not
to PHP stdout. When global/vhost caching is enabled, explicitly cacheable
PHP-FPM responses may enter the shared RustProxy cache; responses with
`Set-Cookie`, `Cache-Control: private` or `no-store` cannot be stored. In
particular, do not enable forced caching of authenticated WordPress pages
without auditing the origin response policy. HTTP/2 and HTTP/3 `:authority`
requests populate the `HTTP_HOST` FastCGI parameter, retaining the original
host/port from the incoming request when provided.
## PHP installation
RustProxy does not bundle PHP. Install and maintain PHP-FPM using the operating system/package ecosystem and enable only the extensions required by the application.
Operational requirements:
- a running PHP-FPM 8.x service supported by the operator's platform;
- a dedicated Unix socket or loopback TCP listener reachable by the RustProxy service account;
- document-root permissions usable by both RustProxy and the PHP-FPM worker identity as intended;
- correct socket owner/group/mode when using Unix sockets;
- PHP-FPM limits such as `pm.max_children`, application execution timeout and upload/body limits sized consistently with the RustProxy concurrency limit.
Extensions such as PDO drivers, mbstring, intl, gd, curl or opcache are application dependencies, not RustProxy dependencies.
## Example PHP-FPM pool concept
The exact paths/users depend on the operating system. For production, use a **separate PHP-FPM worker identity per site/application**. Never run PHP-FPM as `root` or as the `rustproxy` service account. A compromised CMS/plugin then must not inherit access to RustProxy's SQLite database, Secret Store, certificate private keys or backups.
A local pool can expose a socket such as:
```ini
user = site_example
group = site_example
listen = /run/php/rustproxy-example.sock
listen.owner = www-data
listen.group = rustproxy
listen.mode = 0660
```
The exact socket owner may differ by distribution; the important properties are that the PHP worker user is **not** `rustproxy`, and only RustProxy plus the pool manager can reach the socket. Configure the Web Service endpoint as:
```text
unix:/run/php/rustproxy-example.sock
```
Keep `/var/lib/rustproxy` mode/private ownership inaccessible to every PHP pool. Prefer separate writable upload/data directories from executable PHP source, and prevent the application from creating executable PHP files in upload areas. A CMS-level arbitrary file upload or vulnerable plugin can otherwise become PHP code execution even when the reverse proxy itself is correct.
Do not expose a PHP-FPM TCP listener to the public network.
## Verification targets
The implementation includes source/unit coverage for:
- PHP front-controller resolution through `try_files`;
- request body delivery to a FastCGI fixture;
- FastCGI response parsing;
- connection failure returning a gateway error without PHP source disclosure;
- bounded endpoint/time/concurrency configuration validation.
Release-host acceptance should additionally exercise a real PHP-FPM runtime over Unix socket and loopback TCP and verify ACL/auth/custom errors/headers plus H1/H2/H3 client transports.
## Per-Web-Service PHP administrative INI overrides (0.29.2)
`php.php_admin_values` is a validated operator-managed string map sent in a single
`PHP_ADMIN_VALUE` FastCGI parameter. Example:
```json
"php": {
"enabled": true,
"path_info": true,
"endpoint": "unix:/run/php/php-fpm-mail.sock",
"php_admin_values": {
"open_basedir": "/var/www/webmail/:/tmp/",
"display_errors": "Off"
}
}
```
Use separate Web Services for distinct per-location PHP settings, then assign those
services to the corresponding Custom Locations. Each Web Service can refer to the
same trusted PHP-FPM socket. The setting does **not** isolate worker process
identities or file permissions. Never expose PHP-FPM FastCGI to untrusted clients.
HTTP headers cannot supply `PHP_ADMIN_VALUE`, and `php_values` must not repeat
an admin directive. Control characters and excessive values are rejected.
Prometheus metrics use only configured Web Service IDs, never script paths,
URLs, PHP values, or dynamic user labels. `rust_proxy_php_requests_total` and
`rust_proxy_php_responses_total{class=...}` count responses after headers have
been received (including proxy-generated failures); the cumulative header
latency metric intentionally excludes streamed body time.
The same snapshot also exports `rust_proxy_web_server_requests_total`,
`rust_proxy_web_server_responses_total{class=...}` and
`rust_proxy_web_server_response_header_duration_microseconds_total` for all
Web Server requests (PHP and non-PHP).
Inspect **Web Server → Live metrics**, [Logs](LOGS.md) and the application's own FPM logs. The proxy's PHP request semaphore is not a replacement for monitoring external FPM worker capacity.