Environment Variables
This is the canonical reference for every ZEALPHP_* environment variable read by the framework. Each entry lists the exact variable name, the value the code falls back to when it is unset, its scope, and what it controls.
Boolean (env_flag) semantics. Most on/off toggles are parsed by env_flag() (src/utils.php), which treats 1 / true / on / yes as true and 0 / false / off / no / none as false (case-insensitive). A handful of vars use stricter rules instead (only the literal '1' enables/disables, or "anything not false/empty/0") — those are called out per-row.
Precedence. For variables that also have a CLI flag (port, host, workers, daemonize, PID file, dev hot-reload, task workers), the resolution order is CLI flag > environment variable > app.php default. The CLI-relevant subset of these is also documented in docs/cli.md. The dev hot-reload var is also covered in docs/hot-reload.md.
Variables marked internal, test, or build/CI are not user knobs — internal vars are IPC/runtime channels the framework sets for its own subprocesses (e.g.
ZEALPHP_REQUEST_CONTEXT,ZEALPHP_CWD). Do not set those by hand. They live in their own section at the bottom so they aren't confused with user-facing configuration.
Server & CLI
| Variable | Default | Scope | Description |
|---|---|---|---|
ZEALPHP_PORT |
8080 |
user | TCP port the HTTP/WebSocket server binds to (read in app.php via $envInt('ZEALPHP_PORT', 8080), min 1). |
ZEALPHP_HOST |
0.0.0.0 |
user | Bind host passed to App::init() (getenv('ZEALPHP_HOST') ?: '0.0.0.0'). |
ZEALPHP_WORKERS |
unset → OpenSwoole worker_num default |
user | Sets the OpenSwoole worker_num setting (HTTP worker count); applied only when set (max(1, (int)value)), else OpenSwoole's own default. |
ZEALPHP_TASK_WORKERS |
8 |
user | Sets the OpenSwoole task_worker_num setting ($envInt('ZEALPHP_TASK_WORKERS', 8, 0), min 0 so 0 disables task workers). |
ZEALPHP_DAEMONIZE |
false |
user | When truthy (env_flag), sets the OpenSwoole daemonize setting so the server runs in the background. |
ZEALPHP_PID_FILE |
<log-dir>/zealphp_<port>.pid |
user | Explicit path for the server PID file (read in app.php and App::resolvePidFile() so start and stop/status agree); falls back to the resolved log dir (default /tmp/zealphp). |
ZEALPHP_DEV |
false (disabled) |
user | Enables dev route hot-reload (workers poll route/*.php mtimes and call reloadRoutes()). App::devReload() treats it as enabled when it is not false, '', or '0' (note: NOT env_flag semantics, so 'no'/'off' count as enabled). |
ZEALPHP_MAX_CONN |
unset → OpenSwoole max_conn default |
user | Sets the OpenSwoole max_conn setting (max concurrent connections); applied only when set (max(1, (int)value)). |
ZEALPHP_MAX_COROUTINE |
unset → OpenSwoole max_coroutine default |
user | Sets the OpenSwoole max_coroutine setting (max coroutines per worker); applied only when set (max(1, (int)value)). |
ZEALPHP_BACKLOG |
unset → OpenSwoole backlog default |
user | Sets the OpenSwoole backlog setting (listen backlog queue length); applied only when set (max(1, (int)value)). |
ZEALPHP_REACTOR_NUM |
unset → OpenSwoole reactor_num default |
user | Sets the OpenSwoole reactor_num setting (reactor thread count); applied only when set (max(1, (int)value)). |
ZEALPHP_MAX_REQUEST |
100000 |
user | Sets the OpenSwoole max_request setting (requests a worker handles before recycling); read in App::run() via (int)(getenv('ZEALPHP_MAX_REQUEST') ?: 100000). |
ZEALPHP_TZ |
unset → no override (keeps php.ini date.timezone) |
user | When set and non-empty, app.php calls date_default_timezone_set() with this value. |
Logging
ZEALPHP_LOG_DIRhas a per-user fallback chain — when unset, the resolver tries/tmp/zealphp, then per-user XDG/temp dirs, then./tmp/zealphpand./logs/zealphp. Seedocs/cli.mdanddocs/hot-reload.mdfor the log/PID directory behaviour.
| Variable | Default | Scope | Description |
|---|---|---|---|
ZEALPHP_LOG_DIR |
unset → first writable candidate (/tmp/zealphp, then per-user XDG/temp, then ./tmp/zealphp, ./logs/zealphp) |
user | Explicit override for the directory ZealPHP writes logs and per-port PID files into (read in zealphp_log_dir_candidates() in src/utils.php and the PID-dir/logs resolution in App.php). |
ZEALPHP_LOG_FILE |
unset → kind-specific default under resolve_log_dir() (access.log / zlog.log / debug.log) |
user | Single fallback log-file path used for every log kind when the kind-specific *_LOG_FILE var is unset (log_file_for() in src/utils.php). |
ZEALPHP_ACCESS_LOG |
true (also forced off when ZEALPHP_BENCH_MODE is on) |
user | env_flag toggle for access logging (access_logging_enabled() in src/utils.php); 0/false/off/no/none disables it. |
ZEALPHP_ACCESS_LOG_FILE |
unset → falls back to ZEALPHP_LOG_FILE, then resolve_log_dir()/access.log |
user | Explicit file path for the access log (the access kind in log_file_for()). |
ZEALPHP_DEBUG_LOG |
unset/empty → debug logging enabled (also forced off when ZEALPHP_BENCH_MODE is on) |
user | Toggle for debug logging (debug_logging_enabled() in src/utils.php); 0/false/off/no/none disables it, else on (falls through to ZEALPHP_ELOG when unset/empty). |
ZEALPHP_DEBUG_LOG_FILE |
unset → falls back to ZEALPHP_LOG_FILE, then resolve_log_dir()/debug.log |
user | Explicit file path for the debug log (the debug kind in log_file_for()). |
ZEALPHP_ZLOG_FILE |
unset → falls back to ZEALPHP_LOG_FILE, then resolve_log_dir()/zlog.log |
user | Explicit file path for the zlog (the zlog kind in log_file_for()). |
ZEALPHP_SERVER_LOG_FILE |
unset → empty; daemonize mode defaults to <logDir>/server.log (the logs CLI command surfaces resolve_log_dir()/server.log) |
user | Path for OpenSwoole's own server log (wired into $settings['log_file'] in app.php). |
ZEALPHP_ELOG |
unset/empty → debug logging stays enabled | user | Legacy alias consulted by debug_logging_enabled() only when ZEALPHP_DEBUG_LOG is unset/empty; same 0/false/off/no/none disabling semantics. |
ZEALPHP_LOG_ASYNC |
true |
user | env_flag toggle for the async coroutine-channel log sink (async_logging_enabled()); when disabled, logs are written synchronously via fopen+fwrite. |
ZEALPHP_RECYCLE_LOG |
true |
user | env_flag toggle gating the worker-recycle observability line in the server's workerStop handler (App.php); set falsey to silence the request-count/RSS/uptime line. |
Lifecycle & isolation
| Variable | Default | Scope | Description |
|---|---|---|---|
ZEALPHP_SUPERGLOBALS |
false |
user | In app.php, sets App::superglobals() (env_flag(...,false)) — process-wide PHP superglobals ($g storage) vs per-coroutine RequestContext; unset → coroutine mode. |
ZEALPHP_PROCESS_ISOLATION |
unset (no override; resolves to follow $superglobals) |
user | Only when set+non-empty, calls App::processIsolation(env_flag(...,false)) — per-include CGI subprocess dispatch (Apache mod_php-style) vs in-process executeFile(). |
ZEALPHP_ENABLE_COROUTINE |
unset (no override; resolves to !$superglobals) |
user | Only when set+non-empty, calls App::enableCoroutine(env_flag(...,true)) — toggles OpenSwoole's enable_coroutine auto-coroutine-per-request setting. |
ZEALPHP_CGI_MODE |
unset (no override) | user | Only when set+non-empty, its raw string value is passed to App::cgiMode($value) — selects the .php CGI dispatch strategy (proc|fork|fcgi). |
ZEALPHP_HTTP_COMPRESSION |
!$compressionMiddleware (true when CompressionMiddleware is NOT registered, false when it is) |
user | In app.php, sets the OpenSwoole http_compression server setting (env_flag) — toggles OpenSwoole's built-in gzip/deflate response compression. |
ZEALPHP_OPCACHE_ADVISORY |
unset (advisory enabled; only literal '0' suppresses) |
user | Read in App::opcacheLegacyBootCheck() via getenv()==='0'; when '0', suppresses the boot-time advisory about opcache + coroutine-legacy re-declaring require_once'd classes. |
ZEALPHP_FN_STATICS_DISABLE |
unset (isolation stays ENABLED in coroutine-legacy; only literal '1' disables) |
user | Read in App::mode() via (string)getenv()!=='1' to set coroutineStaticsIsolation() — when '1', disables per-coroutine isolation of function-local static $x (Stage 5) for raw throughput. |
ZEALPHP_FN_STATICS_RESET_DISABLE |
unset (reset ENABLED; any non-empty value not starting with '0' disables) |
user | Read in ext-zealphp zealphp_reset_request_statics(); when set, disables the per-request reset of function-local static $x to their templates. |
ZEALPHP_CLASS_STATICS_RESET_DISABLE |
unset (reset ENABLED; any non-empty value not starting with '0' disables) |
user | Read in ext-zealphp zealphp_reset_request_class_statics(); when set, disables the per-request reset of class static properties to their templates. |
ZEALPHP_GLOBALS_ISOLATION_DISABLE |
unset (isolation NOT disabled; only literal '1' disables) |
user | Read in App::run() via (string)getenv()==='1'; when '1', disables the Stage 2 per-coroutine $GLOBALS COW isolation even if coroutineGlobalsIsolation(true) was called. |
ZEALPHP_INI_ISOLATE |
false |
user | In app.php (env_flag(...,false)); when truthy, registers IniIsolationMiddleware which snapshots/restores per-request ini_set() changes to prevent ini leakage across requests on a worker. |
ZEALPHP_ALLOW_COMPILE_HOOK_FILE |
unset (HOOK_FILE dropped in silentRedeclare+coroutine mode; only literal '1' opts back in) |
user | Read in App::run() via (string)getenv()!=='1'; when '1', keeps OpenSwoole's HOOK_FILE flag enabled under silentRedeclare+enableCoroutine (else stripped to avoid a mid-compile yield SIGSEGV/hang). |
Store & backends
| Variable | Default | Scope | Description |
|---|---|---|---|
ZEALPHP_STORE_BACKEND |
unset → no override; Store/Counter keep their compiled-in default (Table/atomic) | user | Read at App::init() (src/App.php:1281): a non-empty value is passed to Store::defaultBackend() and Counter::defaultBackend('redis' if 'redis' else 'atomic'), flipping the shared-state backend (e.g. redis/tiered) before app.php's Store::make() calls run. |
ZEALPHP_REDIS_URL |
redis://127.0.0.1:6379 |
user | Redis/Valkey connection URL used by Store::redisUrlFromEnv() (src/Store.php:207) and Counter (src/Counter.php:241) to build the RedisConnectionPool for redis/tiered backends. |
ZEALPHP_REDIS_PREFER |
unset → no prefer option emitted (driver auto-detects: phpredis when ext-redis loaded, else predis) |
user | Selects the Redis client lib via Store::poolOptsFromEnv() (src/Store.php:227) / Counter (src/Counter.php:254): lowercased, accepted only as auto/phpredis/predis (else ignored); also read in App::redisBootChecks() (src/App.php:4252) to warn that phpredis SUBSCRIBE deadlocks without HOOK_ALL. |
ZEALPHP_MEMCACHED_SERVERS |
127.0.0.1:11211 |
user | Memcached server list used by Store::memcachedServersFromEnv() (src/Store.php:213) and Counter (src/Counter.php:247) when building the memcached backend. |
ZEALPHP_TIERED_INVALIDATION_SECRET |
unset/empty → null (insecure trust mode: any Redis writer can forge an L1-evict message) |
user | Shared HMAC secret read by TieredBackend::__construct() (src/Store/TieredBackend.php:64) when no invalidationSecret ctor arg is passed; signs/verifies cross-node L1 cache-invalidation messages, dropping unsigned/mismatched ones. |
Security / site / middleware
| Variable | Default | Scope | Description |
|---|---|---|---|
ZEALPHP_CORS_ORIGINS |
unset → ["*"] (wildcard) with a one-time elog() warning |
user | Comma-separated CORS origin allowlist read by CorsMiddleware::resolveOriginsList() when no explicit origins are passed to the constructor; empty/unset → wildcard *. |
ZEALPHP_SESSION_SECURE |
unset → auto-detect HTTPS (Secure flag true when HTTPS=on, X-Forwarded-Proto=https, or SERVER_PORT=443) |
user | When set, forces the session cookie's Secure flag via filter_var(FILTER_VALIDATE_BOOLEAN); unset → auto-detect HTTPS (src/Session/utils.php). |
ZEALPHP_WS_HMAC |
not read by the framework — app-supplied; effective default is no HMAC until the app wires it | user | Documented in the WSRouter::setChannelHmacSecret() docblock (src/WSRouter.php:261) as the env var an app reads in app.php to pass into setChannelHmacSecret(); the framework never calls getenv on it. When set, WSRouter signs/verifies ws:server:* and ws:room:* pub/sub publishes with an HMAC envelope. |
ZEALPHP_SITE_URL |
unset/empty → falls back to ZEALPHP_SITE_HOST, then https://php.zeal.ninja |
user | Canonical base URL used by site_url() (src/utils.php) to build absolute URLs; trailing slash trimmed and an https:// scheme prepended if none present. |
ZEALPHP_SITE_HOST |
unset → falls through to ZEALPHP_SITE_URL logic, ultimately https://php.zeal.ninja |
user | Fallback canonical host used by site_url() only when ZEALPHP_SITE_URL is unset/empty; a bare host gets an https:// scheme prepended. |
ZEALPHP_RATE_LIMIT_LOOPBACK |
unset → loopback requests are NOT rate-limited (skipped) | user | RateLimitMiddleware skips rate limiting for loopback client IPs unless this is exactly '1', in which case loopback requests are also counted against the limit (useful for testing). |
Demo-site & Learn app
| Variable | Default | Scope | Description |
|---|---|---|---|
ZEALPHP_DEMO_MIDDLEWARE |
false |
user | When truthy (env_flag), app.php loads examples/demo_middleware.php and registers the demo RequestLogMiddleware + QueryDumpMiddleware (logging-only demo middleware). |
ZEALPHP_COMPRESSION_MIDDLEWARE |
false |
user | When truthy (env_flag), app.php registers the reference CompressionMiddleware (intended only when OpenSwoole's built-in http_compression is disabled). |
ZEALPHP_ASSET_VERSION |
unset → resolved at boot: git commit short hash (12 chars), else newest mtime across public/css+public/js, else time() |
build/CI | Cache-bust token defined as a PHP constant in app.php (only if not already defined) and appended as ?v=… to CSS/JS asset URLs in template/_head.php; auto-derived rather than typically set by the user. |
ZEALPHP_LEARN_AI_MODEL |
gpt-4.1-mini |
user | OpenAI model name used by the Learn demo's notes/chat AI agent; read via getenv() with a ?: 'gpt-4.1-mini' fallback in src/Learn/Chat.php and api/learn/chat_status.php. |
ZEALPHP_LEARN_DB_PATH |
storage/learn.db |
user | Filesystem path to the Learn demo's SQLite database (resolved relative to ZEALPHP_ROOT when not absolute) in src/Learn/DB.php::path(). |
ZEALPHP_LEARN_MAX_NOTES |
256 |
user | Maximum notes a user may create in the Learn demo before Notes::create() refuses; read with a ?: 256 fallback in src/Learn/Notes.php. |
Internal, test & build/CI variables
These are not user-facing configuration. Internal vars are IPC/runtime channels the framework sets for its own subprocesses; test/build vars are set by the test suite or CI. Do not set internal vars (e.g. ZEALPHP_REQUEST_CONTEXT, ZEALPHP_CWD) by hand.
| Variable | Default | Scope | Description |
|---|---|---|---|
ZEALPHP_CWD |
unset → no chdir (set internally to App::$cwd) |
internal | CGI-subprocess env var set by App from self::$cwd and read by cgi_worker.php (getenv('ZEALPHP_CWD'); chdir's to it when present) so the child runs in the parent's working directory. |
ZEALPHP_REQUEST_CONTEXT |
'{}' (empty JSON object) when unset |
internal | IPC channel — JSON-encoded per-request context (server/get/post/cookie/files/env) App passes to the CGI subprocess; cgi_worker.php reads it via json_decode(getenv('ZEALPHP_REQUEST_CONTEXT') ?: '{}', true) to rebuild superglobals. |
ZEALPHP_CGI_AUTOLOAD |
unset (no autoload; treated as off unless exactly '1') |
internal | Read in src/cgi_worker.php via getenv()==='1'; when '1', the CGI subprocess requires vendor/autoload.php. Set internally by App.php (~line 5695) when App::cgiSubprocessAutoload(true), not by users directly. |
ZEALPHP_POOL_MAX_REQUESTS |
500 |
internal | Requests a CGI pool worker handles before recycling; src/pool_worker.php reads it with ?: '500', and WorkerPool.php injects it into each child's env from App::cgiPoolMaxRequests. Normally set via the App API, not directly. |
ZEALPHP_CGI_DEBUG_DEPRECATIONS |
unset (deprecation warnings suppressed) | user | In proc-mode CGI (src/cgi_worker.php), unless set to exactly '1' the worker suppresses E_DEPRECATED/E_USER_DEPRECATED to avoid a stderr-pipe-fill deadlock; '1' restores them. |
ZEALPHP_POOL_DEBUG_DEPRECATIONS |
unset (deprecation warnings suppressed) | user | In the CGI pool worker (src/pool_worker.php), unless set to exactly '1' the worker suppresses E_DEPRECATED/E_USER_DEPRECATED to avoid a stderr-pipe-fill deadlock; '1' restores them. |
ZEALPHP_ROOT |
repo root (dirname(__DIR__, 2)) when the constant is undefined |
test | A PHP constant (NOT an env var); only define()d in tests/bootstrap.php, read via defined()/constant() in src/Learn/DB.php (and Learn test files) to locate the SQLite DB, falling back to repo root when undefined. |
ZEALPHP_LEARN_RATE_LIMIT_LOOPBACK |
unset (loopback rate-limiting bypassed) | test | When set to exactly '1', re-enables Learn-demo rate limiting for loopback clients (otherwise bypassed so the integration suite can re-run without restarting); checked in src/Learn/Auth.php::rateLimit(). |
ZEALPHP_COVERAGE_DIR |
unset (coverage collection inert) | test | Directory where the running server dumps per-process src/ code-coverage .cov files; coverage instrumentation in app.php only activates when this is a non-empty string and a coverage driver is loaded. |
ZEALPHP_BENCH_MODE |
false |
build/CI | When truthy (env_flag), disables all logging for benchmark runs; read once via env_flag('ZEALPHP_BENCH_MODE', false) in bench_mode_enabled() (src/utils.php). |
ZEALPHP_SKIP_DOCS_BUILD |
false |
build/CI | When truthy (env_flag), skips the boot-time background API-docs build in app.php (CI sets it so an in-flight build doesn't flake timing-sensitive integration tests). |
Excluded from this reference (matched by a ZEALPHP_* grep but not real env vars): ZEALPHP_VERSION (only the PHP_ZEALPHP_VERSION C macro), ZEALPHP_H (a php_zealphp.h include guard), ZEALPHP_POOL_WORKER_READY (a stderr readiness sentinel string in src/pool_worker.php), and ZEALPHP_HOOK_C (a define() constant in an ext test fixture).