Configuration

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.

Boolean values
truthy on true 1 yes falsy off false 0 no

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 variables
Variable Default Description
0.0.0.0:80

Address and port for the main HTTP server.

/var/www/html/public

Root directory for serving files and PHP scripts.

(unset)

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.

false

Enable persistent worker mode. Requires ENTRY_FILE pointing at a .php script.

10000

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.

CPU / 2 (min 1)

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.

sapi

PHP executor backend: sapi for real PHP, stub for benchmarking without PHP.

CPU / 2 (min 1)

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.

30

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.

workers × 128

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.

1000

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.

workers × 128, ≤ MAX_CONNECTIONS / 2

Max requests parked waiting for a queue slot; past it, immediate 529. Each waiter holds a connection and its buffered body. 0 = auto.

64 MiB

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.

(unset)

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().

true

Build $_SERVER and $_GET before each script. Falsy skips that work; $_POST, $_FILES, $_COOKIE and php://input are unaffected.

Traffic & delivery

18 variables
Variable Default Description
5

Max seconds to receive HTTP headers after connect (Slowloris protection).

25

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.

0 (off)

Max requests per IP per window. 0 = off; a full PHP queue answers 529.

60

Rate-limit window duration in seconds.

(unset)

Path to the PEM certificate. Both TLS_CERT and TLS_KEY must be set to enable TLS.

(unset)

Path to the PEM private key.

1.2

Minimum TLS version: 1.2 or 1.3. Validated at startup even when TLS is off.

PHP_WORKERS_MAX × 4 (min 32)

Max simultaneous open streams per connection.

20

Max queued RST_STREAM frames before the connection is closed (Rapid Reset protection).

65536

Max total decoded header bytes per request.

20

Seconds between PING frames; 0 disables.

10

Seconds to wait for a PING reply before closing the connection.

30d

Cache-Control: max-age for static files. Accepts 30s, 5m, 2h, 30d, 1w, 1y, bare seconds or off. Replaces STATIC_CACHE_TTL.

off

mtime revalidation for the in-memory cache, checked at most every 3 s per file. Replaces STATIC_CACHE.

br,zstd,gzip

Content codings offered: br, zstd, gzip. off/none disables compression. Order is ignored: cached static files prefer Brotli, everything else zstd.

5

Brotli quality 0 to 11; 0 withdraws Brotli. Replaces COMPRESSION_LEVEL (whose 0 still disables every coding).

6

Zstandard level 0 to 19; 0 withdraws zstd. Out-of-range values abort startup.

6

Gzip level 0 to 9; 0 withdraws gzip. Out-of-range values abort startup.

Security

5 variables
Variable Default Description
SAMEORIGIN

Clickjacking 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.

(unset)

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.

(unset)

Glob patterns whose .php files never execute via direct URI (/uploads/**). Matched before disk I/O, so there is no existence oracle.

404

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.

(unset)

Absolute paths under which symlinks may escape DOCUMENT_ROOT. Unset = no escapes allowed.

Logs, traces & profiles

39 variables
Variable Default Description
info

Log verbosity of the server's own diagnostics: trace, debug, info, warn, error. Does not filter the access log; client disconnects log at debug.

(unset)

Per-request access log: all = every request, error = 4xx/5xx only, unset = off.

(unset)

Internal server (/health, /metrics, /config). Port-only values bind 127.0.0.1; unset = not started.

(unset)

CIDR allow-list for the internal server; outsiders get 403, health probes stay reachable. Loopback is not implicit.

(unset)

Directory with custom {status}.html error pages.

524288

Max body bytes for internal query endpoints (512 KiB).

false

W3C Trace Context propagation: reads traceparent/tracestate and forwards them to PHP via $_SERVER.

false

Enable OpenTelemetry span export. Implies TRACE_CONTEXT=true.

grpc

Export protocol: grpc or http/protobuf.

localhost:4317 / 4318

OTLP collector endpoint. With http/protobuf it is a base URL: spans are sent to /v1/traces under it.

10000

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.

(unset)

Authentication headers: key=value,key2=value2.

oxphp

Service name in exported spans.

(unset)

Service version attribute.

(unset)

Extra resource attributes: env=prod,region=us-east-1.

parentbased_traceidratio

Sampling strategy: always_on, always_off, traceidratio and the parentbased_* variants.

1.0

Sampling ratio from 0.0 to 1.0 for ratio-based samplers; out-of-range values are clamped.

false

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.

100

Slow-query threshold in ms; slower DB queries get oxphp.db.slow=true.

false

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.

8192

Cap on the exception.stacktrace attribute; truncated from the tail. 0 = no truncation.

4096

Cap on the exception.message attribute (New Relic's per-attribute limit); truncated from the tail. 0 = no truncation.

false

Master switch. Other PROFILER_* vars are still parsed, so typos surface at startup.

0.0

Probability (0.0 to 1.0) that a request is sampled.

false

Also sample requests to the internal server.

(unset)

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.

50000

Per-request span cap; longer profiles are truncated.

0 (no cap)

Max nesting depth of recorded calls; deeper calls' time folds into the call at the limit. Clamped to 32.

/tmp/oxphp-profiles

Directory for on-disk profiles.

xhprof,speedscope

Formats written to disk: xhprof, speedscope, pprof, flamegraph.

10

Rate limit on profile files written per second.

100

Max profile files kept; older ones are pruned.

(unset)

Remote endpoint to POST profiles to.

xhprof

Wire format for exports.

(unset)

Bearer token sent with each export.

(auto-detect)

XHGui-compatible wrapping; unset = auto-detect from the URL.

(auto-detect)

Buggregator envelope (always xhprof); unset = auto-detect from the URL. Mutually exclusive with PROFILER_EXPORT_XHGUI.

(unset)

Buggregator app_name for project grouping.

(unset)

Buggregator tags: key=value,key2=value2. Malformed tokens are a startup error.

Async & shared state

18 variables
Variable Default Description
off

Opt-in fiber-suspending replacements for blocking builtins. Categories: sleep (native sleep/usleep), streams (blocking tcp:// reads and stream_select), net (connects, TLS handshakes and reads, writes waiting for buffer room, unix:// / udp:// / udg:// reads; needs streams, Linux only). 1/true/all = every category. Installed set shown as runtime_hooks in /config.

0 (off)

Dedicated async worker threads. 0 = functions registered but throwing AsyncException on call.

ASYNC_WORKERS × 64

Max pending tasks in the async queue. 0 = auto.

256

Per-worker cap on concurrent task fibers; global in-flight cap = fibers × workers.

true

Master switch for the whole OxPHP\Shared\* subsystem. Off = its classes are not registered at all; guard with class_exists().

100000

Global cap on live Shared objects (Map keys do not count); creating one past it throws CapacityException.

1 GiB

Global cap on estimated memory across Shared objects, checked only when an object is created.

true

Toggles the oxphp_shared_* Prometheus exposition.

true

Toggles the /__ox_shared/* introspection API.

true

Value previews in introspection responses; disable if previews could leak data.

16

BFS depth for cycle checks; raise for deep graphs.

10000

Edges walked during cycle checks; raise for dense graphs.

1 MiB

Per-value size cap; larger inserts fail fast.

64 MiB

Per-channel total payload cap.

warn

Deadlock detector for Shared\Mutex: off, warn (log and count cycles) or strict (break them with DeadlockException). Debug builds default to strict.

100

How often the deadlock detector scans, in ms.

256

Per-string truncation in previews, in bytes, cut at a character boundary.

20

Entries sampled in /entry previews.

Presets

Example configurations

Starting points straight from the docs. Copy one and adjust.

# verbose logs while you build and debug
LOG_LEVEL=debug
# record every request, not just errors
ACCESS_LOG=all
# one worker: deterministic, breakpoint-friendly
PHP_WORKERS=1
# local /health, /metrics and /config endpoints
INTERNAL_ADDR=127.0.0.1:9090
# re-read edited static files without a restart
STATIC_REVALIDATE=on
Inspect the live configuration

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.

curl -s http://localhost:9090/config | jq .

Zero configuration is a valid configuration

Every default above is chosen so an empty environment just works.