Every setting is one environment variable
Every setting is an environment variable (plus one optional file, .oxphpdeny, for path deny rules), and every variable has a default that holds up in production. Deploy an empty environment, then override only the variables you care about.
Anything outside this set, such as the typo "ture", fails fast at startup with an error that names the variable. Empty equals unset: FOO=${FOO} substitutions that produce FOO= fall back to the default instead of refusing to start.
Server & workers
15 variablesAddress and port for the main HTTP server.
Root directory for serving files and PHP scripts.
Single canonical entry script. Unset = direct file mapping, *.php = front controller, non-.php = SPA fallback; with worker mode, the worker bootstrap. Resolved against DOCUMENT_ROOT.
Enable persistent worker mode. Requires ENTRY_FILE pointing at a .php script.
Maximum concurrent TCP connections. Also the ceiling for the default QUEUE_MAX_WAITING (half of it); a malformed value is a startup error. Keep it above PHP_WORKERS + QUEUE_CAPACITY + QUEUE_MAX_WAITING.
Async I/O threads. 1 = single-threaded, N = fixed count, unset = auto. Shares cores with PHP_WORKERS, so raising it to the full core count usually costs throughput. CPU counts the cores the process may run on (affinity mask, lowered to a cgroup quota), not a CPU weight or a limit a platform enforces outside the container; the startup line CPU count for default sizing shows what was counted.
PHP executor backend: sapi for real PHP, stub for benchmarking without PHP.
Worker pool size. N = fixed pool, MIN:MAX = dynamic scaling, 0 = auto. Auto sizing counts CPUs as TOKIO_WORKERS does. On a plan whose CPU limit the container cannot see (cpu_source: host in the startup log), set both explicitly.
Seconds a dynamic worker stays idle before being retired. Retirement waits for a moment with nothing in flight, so in-flight requests are never cut short.
Max pending requests in the PHP queue; arrivals at a full queue wait up to QUEUE_WAIT_TIMEOUT_MS. 0 = auto. For MIN:MAX pools, initial workers = the minimum.
Ceiling on how long a request may wait for a PHP worker before a 529. One deadline covers the wait for a queue slot and the wait in the queue. Under overload the server shortens it on its own (down to 1/64, min 10 ms); oxphp_admission_wait_budget_us shows what is in force. 0 = reject the moment the queue is full.
Max requests parked waiting for a queue slot; past it, immediate 529. Each waiter holds a connection and its buffered body. 0 = auto.
Max request-body bytes held by the waiting set; a body that would exceed it gets an immediate 529, bodyless requests keep waiting. 0 = auto.
Memory ceiling per worker in MiB before recycling. 0 = unlimited. Workers also recycle after three consecutive requests that came apart, or on demand via Worker::scheduleExit().
Build $_SERVER and $_GET before each script. Falsy skips that work; $_POST, $_FILES, $_COOKIE and php://input are unaffected.
Traffic & delivery
18 variablesMax seconds to receive HTTP headers after connect (Slowloris protection).
Max seconds for in-flight requests during graceful shutdown; stragglers are then cancelled. A request stuck in a blocking call can hold the process past it.
Max requests per IP per window. 0 = off; a full PHP queue answers 529.
Rate-limit window duration in seconds.
Path to the PEM certificate. Both TLS_CERT and TLS_KEY must be set to enable TLS.
Path to the PEM private key.
Minimum TLS version: 1.2 or 1.3. Validated at startup even when TLS is off.
Max simultaneous open streams per connection.
Max queued RST_STREAM frames before the connection is closed (Rapid Reset protection).
Max total decoded header bytes per request.
Seconds between PING frames; 0 disables.
Seconds to wait for a PING reply before closing the connection.
Cache-Control: max-age for static files. Accepts 30s, 5m, 2h, 30d, 1w, 1y, bare seconds or off. Replaces STATIC_CACHE_TTL.
mtime revalidation for the in-memory cache, checked at most every 3 s per file. Replaces STATIC_CACHE.
Content codings offered: br, zstd, gzip. off/none disables compression. Order is ignored: cached static files prefer Brotli, everything else zstd.
Brotli quality 0 to 11; 0 withdraws Brotli. Replaces COMPRESSION_LEVEL (whose 0 still disables every coding).
Zstandard level 0 to 19; 0 withdraws zstd. Out-of-range values abort startup.
Gzip level 0 to 9; 0 withdraws gzip. Out-of-range values abort startup.
Security
5 variablesClickjacking protection: SAMEORIGIN, DENY or off. Sets X-Frame-Options + CSP frame-ancestors on every response; application-set headers always win. Invalid values fall back to SAMEORIGIN with a startup warning.
Trusted proxies: a comma-separated mix of CIDRs, bare IPs and the keywords private and cloudflare. Real client IP from Forwarded / X-Forwarded-* via rightmost-non-trusted; with cloudflare, from CF-Connecting-IP when the request came through a Cloudflare edge.
Glob patterns whose .php files never execute via direct URI (/uploads/**). Matched before disk I/O, so there is no existence oracle.
Response on a PHP_DENY_PATHS match or a .oxphpdeny deny rule: an HTTP status from 400 to 599 or a /-prefixed PHP fallback script inside DOCUMENT_ROOT. In worker mode .oxphpdeny deny rules take a status code only.
Absolute paths under which symlinks may escape DOCUMENT_ROOT. Unset = no escapes allowed.
Logs, traces & profiles
39 variablesLog verbosity of the server's own diagnostics: trace, debug, info, warn, error. Does not filter the access log; client disconnects log at debug.
Per-request access log: all = every request, error = 4xx/5xx only, unset = off.
Internal server (/health, /metrics, /config). Port-only values bind 127.0.0.1; unset = not started.
CIDR allow-list for the internal server; outsiders get 403, health probes stay reachable. Loopback is not implicit.
Directory with custom {status}.html error pages.
Max body bytes for internal query endpoints (512 KiB).
W3C Trace Context propagation: reads traceparent/tracestate and forwards them to PHP via $_SERVER.
Enable OpenTelemetry span export. Implies TRACE_CONTEXT=true.
Export protocol: grpc or http/protobuf.
OTLP collector endpoint. With http/protobuf it is a base URL: spans are sent to /v1/traces under it.
Export timeout in milliseconds. Over gRPC it also bounds connecting to the collector, TLS handshake included, and on Linux sets how soon a connection that stopped taking data is closed.
Authentication headers: key=value,key2=value2.
Service name in exported spans.
Service version attribute.
Extra resource attributes: env=prod,region=us-east-1.
Sampling strategy: always_on, always_off, traceidratio and the parentbased_* variants.
Sampling ratio from 0.0 to 1.0 for ratio-based samplers; out-of-range values are clamped.
APM: auto-instrumentation of 34 PHP functions (PDO, mysqli, cURL, Redis…), error capture, tracing SDK. Requires OTEL_ENABLED. When off, oxphp_apm_trace() still runs its callback.
Slow-query threshold in ms; slower DB queries get oxphp.db.slow=true.
Record bind parameters in db.params spans. Recorded raw (not obfuscated), so this can put PII in traces. Enable it only where that is acceptable.
Cap on the exception.stacktrace attribute; truncated from the tail. 0 = no truncation.
Cap on the exception.message attribute (New Relic's per-attribute limit); truncated from the tail. 0 = no truncation.
Master switch. Other PROFILER_* vars are still parsed, so typos surface at startup.
Probability (0.0 to 1.0) that a request is sampled.
Also sample requests to the internal server.
Secret the explicit triggers (X-OxPHP-Profile header, OXPROF cookie, __oxprof query) must carry, and bearer for /__profiler/*. Unset = no token. Sampling and OxPHP\Profile\* functions never check it.
Per-request span cap; longer profiles are truncated.
Max nesting depth of recorded calls; deeper calls' time folds into the call at the limit. Clamped to 32.
Directory for on-disk profiles.
Formats written to disk: xhprof, speedscope, pprof, flamegraph.
Rate limit on profile files written per second.
Max profile files kept; older ones are pruned.
Remote endpoint to POST profiles to.
Wire format for exports.
Bearer token sent with each export.
XHGui-compatible wrapping; unset = auto-detect from the URL.
Buggregator envelope (always xhprof); unset = auto-detect from the URL. Mutually exclusive with PROFILER_EXPORT_XHGUI.
Buggregator app_name for project grouping.
Buggregator tags: key=value,key2=value2. Malformed tokens are a startup error.
Example configurations
Starting points straight from the docs. Copy one and adjust.
With the internal server running, /config returns the resolved configuration as JSON. TLS paths, INTERNAL_ADDR and ERROR_PAGES_DIR are scrubbed from the response.
Zero configuration is a valid configuration
Every default above is chosen so an empty environment just works.