Environment variables
Reference list of every environment variable read by the Phlix Media Server, its default, and a one-line description. Each entry links back to the file that consumes it.
Container & bootstrap
| Variable | Default | Description |
|---|---|---|
PHLIX_CONTAINER_COMPILE | unset | When truthy (1, true, yes, on) enables PHP-DI's compiled-container cache in var/cache/container/. Disabled by default for dev parity. See Phlix\Common\Container\ContainerFactory. |
Events
| Variable | Default | Description |
|---|---|---|
PHLIX_DEBUG_EVENTS | 0 | When truthy (1, true, yes, on) it (1) wraps the PSR-14 dispatcher in Tukio's DebugEventDispatcher, which logs every dispatched event class at debug level on the events channel, and (2) enables the events log handler so those records are actually written to .logs/events.log. When off, events.log stays empty. Useful for tracing plugin behaviour; leave off in production. See Phlix\Common\Events\EventDispatcherFactory, config/logger.php, and docs/dev/event-reference.md. |
Plugins
| Variable | Default | Description |
|---|---|---|
PHLIX_PLUGINS_ALLOW_HTTP | 0 | When truthy (1, true, yes, on) lets the plugin loader accept plain http:// source URLs. Default off — HTTPS or file:// only. See Phlix\Plugins\Installer\HttpInstaller. |
PHLIX_PLUGINS_REQUIRE_SIGNATURE | 0 | When truthy, the plugin loader refuses to install unsigned plugins and refuses signatures missing from the trusted-key allowlist. Default off, which means unsigned plugins install with a warning on the plugins log channel. See Phlix\Plugins\Signature\SignatureVerifier. |
PHLIX_PLUGINS_COMPOSER_TIMEOUT | 120 | Hard timeout (seconds) on the per-plugin composer install --no-dev subprocess. See Phlix\Plugins\Installer\ComposerRunner. |
PHLIX_PLUGINS_CATALOG_REF | (unset) | Overrides the git ref the official catalog (detain/phlix-plugins) resolves to — a release tag or commit. Highest precedence: wins over the plugins.catalog.channel setting and over the built-in OFFICIAL_PINNED_REF default (precedence: env > setting > default). Use it to pin a newer catalog release or roll back without a code change. See Plugin Catalog → Release channel and Phlix\Plugins\Catalog\CatalogSourceResolver. |
PHLIX_PLUGINS_ALLOW_UNVERIFIED | 0 | Default-deny override. When unset, the loader refuses to install a plugin whose source is remote and whose catalog entry carries no pinned artifact sha256, throwing PluginInstallException. When truthy (1, true, yes, on) the install proceeds and a warning is logged on the plugins channel. file:// and scheme-less sources are exempt — the gate never applies to them — and a source that is digest-pinned bypasses it regardless of this variable. See Phlix\Plugins\PluginLoader::assertVerifiedOrOverride(). |
PHLIX_GITHUB_TOKEN | (unset) | GitHub OAuth token seeded into the per-plugin COMPOSER_HOME/auth.json (mode 0600) so composer install --no-dev can reach private or rate-limited GitHub repositories. Checked first, then GITHUB_TOKEN, then GH_TOKEN. With none set the token-less path stays fully functional. See Phlix\Plugins\Installer\ComposerRunner::discoverGithubToken(). |
GITHUB_TOKEN | (unset) | Second-priority fallback for the plugin composer GitHub token. See PHLIX_GITHUB_TOKEN. |
GH_TOKEN | (unset) | Third-priority fallback for the plugin composer GitHub token. See PHLIX_GITHUB_TOKEN. |
PHLIX_ANIDB_CACHE_DIR | sys_get_temp_dir()/phlix-plugin-anidb | Writable directory outside the plugin tree where the AniDB plugin writes its title_index.json title-dump cache. The packaged systemd unit exports it as /var/cache/phlix/anidb and adds it to ReadWritePaths, because ProtectSystem=strict would otherwise block the write. Resolution order: constructor argument → the cache_dir plugin setting → this variable → sys_get_temp_dir(). Read by the AniDB plugin, not by the server itself. |
Auth
| Variable | Default | Description |
|---|---|---|
JWT_SECRET | default-secret-change-me | HMAC secret used to sign / verify JWT access and refresh tokens. The default is intentionally insecure so a missing env var fails closed in production deployments. Read by Phlix\Common\Container\Providers\AuthServicesProvider. |
PHLIX_SIGNED_URL_SECRET | derived from JWT_SECRET | HMAC key for the signed media URLs that gate the binary/streaming endpoints (/media/{id}/stream, /hls/**, /dash/**, book/audiobook/photo bytes). When unset it is derived from JWT_SECRET via a domain-separated HMAC, so a leaked stream token can never be replayed as a JWT (and vice-versa). Set an explicit value to rotate stream tokens independently of JWTs. Read by Phlix\Auth\SignedUrl::fromEnv(). |
PHLIX_SIGNED_URL_TTL | 21600 (6 h) | Lifetime, in seconds, of a minted signed media URL before it expires. Must be a positive integer. Read by Phlix\Auth\SignedUrl::fromEnv(). |
Auth rate limiting (SV-4.15)
The previously-unlimited auth surfaces (register, refresh, WebAuthn login start+finish, the public JWKS endpoint, and WS-connect on :8097) are rate-limited per surface. HTTP surfaces reply 429 Too Many Requests with a Retry-After header and body {"error":"Too Many Requests","code":"rate_limited"}; WS-connect rejects the handshake. login keeps its own IP-keyed DB-backed limiter (migration 074) and is not configured here. Read by config/server.php → rate_limit and Phlix\Common\RateLimit\RateLimitProfiles.
Each surface's max (attempts) and window (seconds) are overridable by a pair of env vars named for the surface. <SURFACE> ∈ REGISTER, REFRESH, WEBAUTHN_START, WEBAUTHN_FINISH, JWKS, WS_CONNECT.
| Variable | Default | Description |
|---|---|---|
RATE_LIMIT_REGISTER_MAX / _WINDOW | 5 / 600 | Registration attempts per window. |
RATE_LIMIT_REFRESH_MAX / _WINDOW | 30 / 60 | Token-refresh attempts per window. |
RATE_LIMIT_WEBAUTHN_START_MAX / _WINDOW | 10 / 60 | WebAuthn login-ceremony start attempts per window. |
RATE_LIMIT_WEBAUTHN_FINISH_MAX / _WINDOW | 10 / 60 | WebAuthn login-ceremony finish attempts per window. |
RATE_LIMIT_JWKS_MAX / _WINDOW | 120 / 60 | Public JWKS (/.well-known/jwks.json) requests per window. |
RATE_LIMIT_WS_CONNECT_MAX / _WINDOW | 30 / 60 | WebSocket (:8097) connection attempts per window. |
TRUSTED_PROXIES | loopback only (127.0.0.1, ::1) | Comma-separated IP/CIDR list of the reverse-proxy hops in front of Phlix. Used to derive the real client IP from X-Forwarded-For/X-Real-IP for rate-limit keys and the DLNA IP allowlist. Must list every non-loopback proxy hop (nginx/HAProxy) — otherwise IP-keyed limits bucket every request under the proxy address (or trust a client-forged header), and a non-loopback proxy fronting DLNA makes every request appear to come from the proxy. The stock install fronts Phlix over loopback, so the default is correct. Read by Phlix\Common\Http\TrustedProxyResolver. See /advanced/reverse-proxy. |
Register / refresh / WebAuthn use a shared DB-backed limiter (migration
085_rate_limit_buckets.sql) so the budget is truly global across all HTTP workers. JWKS and WS-connect use a worker-local in-memory limiter (JWKS is cache-frontable; the:8097WS worker runscount=1, so per-worker equals global there). Apply migration 085 on deploy.
HLS / segment cache
| Variable | Default | Description |
|---|---|---|
HLS_MIN_DISK_SPACE_BYTES | 524288000 (500 MiB) | Free-space floor for the HLS segment-cache directory (config/server.php → hls.min_disk_space_bytes). When free space on the segment-cache filesystem drops below this, the server sweeps the cache and returns 503 with Retry-After: 3 rather than failing an encode with ENOSPC. |
Hub / Pairing (phlix-server)
| Variable | Default | Description |
|---|---|---|
PHLIX_HUB_URL | unset | Base URL of the Phlix Hub to pair with (e.g. https://hub.example.com). When set, the server initiates pairing automatically on startup if enrolled. See Phlix\Hub\HubClient and config/hub.php. |
PHLIX_HUB_JWKS_URL | unset | URL of the hub's JWKS endpoint for validating hub-issued JWTs. Typically set automatically during enrollment. See Phlix\Hub\HubJwtValidator and config/hub.php. |
PHLIX_HUB_ENROLLMENT_TOKEN | unset | When set, overrides the enrollment token read from config/hub-enrollment.json. Mostly useful for automation / container orchestration. See Phlix\Hub\HubClient::loadEnrollment(). |
PHLIX_HUB_HEARTBEAT_INTERVAL | 60 | Interval in seconds between hub heartbeat calls. Must be between 30 and 3600. See Phlix\Hub\HubClient::startHeartbeatLoop() and config/hub.php. |
PHLIX_SUBDOMAIN_AUTO_CLAIM | 1 | When truthy (1, true, yes, on) automatically claims a *.phlix.media subdomain from the hub after enrollment. See Phlix\Hub\SubdomainClient and config/hub.php. |
PHLIX_TLS_ENABLED | 1 | When truthy enables TLS/HTTPS for the server's public hostname. Requires a subdomain to be allocated. See config/hub.php. |
PHLIX_DOMAIN | unset (config/hub.php substitutes phlix.media for its own hub.domain) | This server's public authority — a host, optionally with a port (media.example.com, media.example.com:8443). No scheme, no path, no trailing dot. scripts/install.sh --domain <domain> writes it to the env file the systemd unit reads. Two jobs: (1) the base domain for server subdomains (e.g. abc12345.phlix.media) that config/hub.php composes into hub.domain / hub.public_url; (2) the allowlist for deriving the OAuth2/OIDC redirect_uri — see below. |
PHLIX_DOMAIN and external sign-in
/auth/oidc/authorize and /auth/github/authorize must send the provider an absolute redirect_uri. Phlix builds <scheme>://<Host><callback path> from the request only when the request's Host equals PHLIX_DOMAIN — the port is part of the comparison, with the scheme's default port (:443 https, :80 http) normalised away on both sides. Host is client-supplied, so an unvouched-for value could otherwise be turned into a redirect_uri pointing at an attacker's host.
It fails closed. If PHLIX_DOMAIN is unset or malformed, nothing is derived and the authorize endpoint answers 503 callback_url_not_configured — no Location, no cookie, no OAuth state row. A malformed value is treated as unconfigured, never as an allowlist that cannot match: https://media.example.com/, media.example.com/app, media.example.com:, media.example.com., media.example.com:99999, media.example.com:https and whitespace all mean "no derivation".
The escape hatch is a per-provider absolute redirect_uri setting, which takes priority and works with PHLIX_DOMAIN unset. Full setup, the misconfiguration response and when to prefer each option: Single Sign-On → Callback URLs and PHLIX_DOMAIN.
Hub / Server (phlix-hub)
These environment variables apply when running phlix-hub itself (not when the server connects to a hub).
HTTP worker (config/server.php)
| Variable | Default | Description |
|---|---|---|
HUB_HOST | 0.0.0.0 | Bind address for the Workerman HTTP worker. |
HUB_PORT | 8800 | TCP port the worker listens on. |
HUB_WORKERS | 2 | Number of worker processes Workerman should fork. |
HUB_WORKERMAN_LOG | <repo>/.logs/workerman.log | Path Workerman writes its master-log to. Directory must exist or be writable. |
Database (config/database.php)
| Variable | Default | Description |
|---|---|---|
HUB_DB_HOST | 127.0.0.1 | MySQL host the hub connects to. |
HUB_DB_PORT | 3306 | MySQL port. |
HUB_DB_USER | phlix_hub | MySQL username. |
HUB_DB_PASSWORD | phlix_hub | MySQL password. Override in any non-dev env. |
HUB_DB_NAME | phlix_hub | Database name. |
Auth (config/auth.php)
| Variable | Default | Description |
|---|---|---|
HUB_JWT_SECRET | (dev fallback) | HMAC-SHA256 secret for issuing JWTs. Required in production — must be ≥32 bytes. Falls back to a random per-process secret in dev. |
HUB_JWT_ACCESS_TTL | 3600 | Access-token lifetime in seconds (default 1 hour). |
HUB_JWT_REFRESH_TTL | 604800 | Refresh-token lifetime in seconds (default 7 days). |
When
HUB_JWT_SECRETis unset, the hub generates a random secret at container-build time. Tokens issued with that secret are valid only for the lifetime of the current PHP process — restarting the worker invalidates every existing session. Always set this var explicitly in production.
Container caching
| Variable | Default | Description |
|---|---|---|
PHLIX_HUB_CONTAINER_COMPILE | unset | When truthy (1, true, yes, on), PHP-DI writes compiled definitions to var/cache/container/ for faster cold-start. Off for dev. |
Hub / Arr Integration
Controls Radarr (movies) and Sonarr (series) connectivity for the Hub's media-request system. The hub uses these to push approved requests into the appropriate Arr instance. Both instances must be reachable from the Hub host and have API v3 enabled.
| Variable | Default | Description |
|---|---|---|
HUB_RADARR_URL | http://localhost:7878 | Base URL of the Radarr instance (API v3). |
HUB_RADARR_API_KEY | empty | Radarr API key. Generate in Radarr → Settings → General → Security → API Key. |
HUB_RADARR_ENABLED | 0 | When truthy (1, true, yes, on) enables Radarr integration. When falsy, movie approvals fail with approve_failed. |
HUB_SONARR_URL | http://localhost:8989 | Base URL of the Sonarr instance (API v3). |
HUB_SONARR_API_KEY | empty | Sonarr API key. Generate in Sonarr → Settings → General → Security → API Key. |
HUB_SONARR_ENABLED | 0 | When truthy (1, true, yes, on) enables Sonarr integration. When falsy, series approvals fail with approve_failed. |
Tip: Both Arr instances must have at least one quality profile and root folder configured before requests can be approved. The hub uses the first available profile and root folder automatically.
See docs/hub/requests.md for the full media-request workflow.
Relay tunnel
| Variable | Default | Description |
|---|---|---|
PHLIX_RELAY_ENABLED | 0 | When truthy (1, true, yes, on) enables the persistent relay tunnel to the hub. Requires the server to be enrolled. See Phlix\Hub\RelayConfig and config/relay.php. |
PHLIX_RELAY_HUB_URL | wss://hub.example.com/api/v1/servers/{id}/relay | WebSocket URL of the hub relay endpoint. {id} is replaced with the server's UUID from enrollment. See Phlix\Hub\RelayConsumer and config/relay.php. |
PHLIX_RELAY_HUB_WS_URL | empty (derived from enrollment) | Explicit hub server-tunnel WS endpoint (config key relay.hub_relay_ws_url). Highest-precedence override — when empty the URL is derived from the enrollment's hub_base_url at start time. The transport keys off the scheme: a ws:// URL is plaintext, a wss:// URL is TLS. If you set a wss:// URL here, also set PHLIX_RELAY_TLS=1 so the cert/verify vars apply. See Phlix\Hub\RelayConfig and config/relay.php. |
PHLIX_RELAY_TLS | false | When truthy (1, true, yes, on) the derived relay scheme becomes wss:// and the tunnel connects with TLS. Default is false (plaintext ws://), mirroring the hub's HUB_RELAY_TLS default — the hub relay listener (:8802) is plaintext unless HUB_RELAY_TLS=true is set on the hub. A TLS relay deploy requires both PHLIX_RELAY_TLS=1 here and HUB_RELAY_TLS=true on the hub. Independent of the server's public HTTP TLS. Config key relay.relay_tls. See Phlix\Hub\RelayConfig and config/relay.php. |
PHLIX_RELAY_TLS_VERIFY | true | Whether to verify the hub's presented relay TLS certificate. Default is secure (on). Set to 0/false to accept a self-signed hub relay cert (verify_peer=false + allow_self_signed=true, mirroring the hub's permissive relay server context). Production should use a CA-signed cert and leave verification on. Config key relay.relay_tls_verify. See Phlix\Hub\RelayConfig and config/relay.php. |
PHLIX_RELAY_TLS_CAFILE | /etc/ssl/certs/ca-certificates.crt | CA bundle used to verify the hub's relay TLS certificate when PHLIX_RELAY_TLS_VERIFY is on. Config key relay.relay_tls_cafile. See Phlix\Hub\RelayConfig and config/relay.php. |
PHLIX_RELAY_TUNNEL_HOSTNAME | empty | Public hostname assigned to this server's relay tunnel (e.g. my-server.phlix.media). See Phlix\Hub\RelayConfig and config/relay.php. |
PHLIX_RELAY_RECONNECT_DELAY | 5 | Seconds to wait before attempting to reconnect after the relay tunnel is disconnected. See Phlix\Hub\RelayConfig and config/relay.php. |
PHLIX_RELAY_PING_INTERVAL | 30 | Seconds between keep-alive ping frames sent over the relay tunnel. See Phlix\Hub\RelayConfig and config/relay.php. |
PHLIX_RELAY_PING_TIMEOUT | 10 | Seconds to wait for a pong response before considering the relay connection dead. See Phlix\Hub\RelayConfig and config/relay.php. |
Port forwarding / remote access
| Variable | Default | Description |
|---|---|---|
PHLIX_PORT_FORWARD_AUTO | 1 | When truthy (1, true, yes, on) enables automatic port forwarding via UPnP-IGD on startup. See Phlix\Network\PortForwardService and config/port-forward.php. |
PHLIX_EXTERNAL_PORT | 32400 | Port to use for automatic port forwarding. Both external and internal ports use this value by default. See Phlix\Network\PortForwardService and config/port-forward.php. |
PHLIX_EXTERNAL_HTTP_PORT | 8080 | External HTTP port for the web portal when accessed remotely. See config/port-forward.php. |
PHLIX_EXTERNAL_HTTPS_PORT | 8443 | External HTTPS port for the web portal when accessed remotely. See config/port-forward.php. |
PHLIX_UPNP_ENABLED | 1 | When truthy enables UPnP-IGD port mapping attempts. When falsy, only STUN-based external IP detection is used. See Phlix\Network\UpnpIgdClient and config/port-forward.php. |
PHLIX_STUN_SERVER | stun.l.google.com | STUN server hostname for discovering the server's public IP address. See Phlix\Network\StunClient and config/port-forward.php. |
PHLIX_STUN_PORT | 19302 | STUN server port. See Phlix\Network\StunClient and config/port-forward.php. |
The port-forwarding default does not match the port the server listens on
PHLIX_EXTERNAL_PORT defaults to 32400 and is used as both the external and the internal port of the UPnP/NAT-PMP mapping — the router is told to forward WAN 32400 to <server-lan-ip>:32400. phlix-server listens on 8096 (config/server.php server.port), so with both defaults left alone the mapping points at a port nothing is bound to and direct remote access will not work.
Set PHLIX_EXTERNAL_PORT to the port the server actually listens on:
PHLIX_EXTERNAL_PORT=8096The same value is what phlix portforward status advertises to the Hub as this server's hostname candidates, so getting it wrong makes those candidates unreachable too.
Server
| Variable | Default | Description |
|---|---|---|
PHLIX_PUBLIC_URL | unset | Public URL used in Hub relay and DLNA announcements. Must be set if the server is behind a reverse proxy. See Phlix\Server\Core\Application. |
PHLIX_LOG_LEVEL | info | Minimum log level for application logs written to .logs/app.log. Valid values (in order of verbosity): debug, info, notice, warning, error, critical, alert, emergency. See config/logger.php. |
PHLIX_HTTP_PORT does not exist — if you have it set, it is doing nothing
Earlier revisions of this page listed PHLIX_HTTP_PORT as the way to change the HTTP listen port. It was never implemented. The name appears nowhere in phlix-server — not in the current source and not in any commit in its history — so exporting it, or putting it in a .env file, has no effect whatsoever. The server is still listening on whatever config/server.php says.
There is no environment variable for the HTTP port. To change it:
- edit
server.portinconfig/server.php— the default is8096; or - pass
--http-port PORTtoscripts/install.shat install time, which writes that value into the config and into the generated HAProxy backend.
Both take effect on the next server restart.
Database
These are the production runtime connection parameters. config/database.php reads every one of them from the environment, each with a localhost default that targets a stock single-host install (the install.sh-managed phlix MySQL user on 127.0.0.1). A default install only needs to set DB_PASSWORD; override any of the others when your database is remote or renamed. See config/database.php.
| Variable | Default | Description |
|---|---|---|
DB_HOST | 127.0.0.1 | MySQL host consumed by config/database.php connections.mysql.host. |
DB_PORT | 3306 | MySQL port consumed by config/database.php connections.mysql.port. |
DB_DATABASE | phlix | Database name consumed by config/database.php connections.mysql.database. Legacy alias: DB_NAME (used only when DB_DATABASE is unset). |
DB_USER | phlix | Database username consumed by config/database.php connections.mysql.username. Legacy alias: DB_USERNAME (used only when DB_USER is unset). |
DB_PASSWORD | empty | Database password consumed by config/database.php connections.mysql.password via getenv('DB_PASSWORD'). Set this on every non-dev install. |
DB_POOL_ENABLED | 1 | The per-worker coroutine connection pool is on by default (Stream Quality/ABR step S9): each coroutine leases its own connection so independent queries within a worker run in parallel instead of serialising on one shared socket. Set DB_POOL_ENABLED=0 (or false/no/off) to opt back into the single-connection mutex path. See config/database.php. |
DB_POOL_SIZE | 8 | Per-worker connection-pool ceiling while DB_POOL_ENABLED is on (the default). The server-wide max is roughly (worker count × pool size); keep it under MySQL max_connections. See config/database.php. |
Test overrides:
phpunit.xml's<env>block overrides these sameDB_*vars for the integration test suite (e.g.DB_DATABASE=phlix_test,DB_USER=root, plusAPP_ENV=testing). Those values apply only when running the test suite — they are not the production defaults shown above.
PHLIX_DATABASE_* aliases
| Variable | Default | Description |
|---|---|---|
PHLIX_DATABASE_HOST | 127.0.0.1 | MySQL host; maps to config/database.php connections.mysql.host. |
PHLIX_DATABASE_PORT | 3306 | MySQL port; maps to config/database.php connections.mysql.port. |
PHLIX_DATABASE_NAME | phlix | Database name; maps to config/database.php connections.mysql.database. |
PHLIX_DATABASE_USER | phlix | Database username; maps to config/database.php connections.mysql.username. |
PHLIX_DATABASE_PASSWORD | empty | Database password. Alias for the DB_PASSWORD env var consumed by config/database.php. Prefer setting DB_PASSWORD to avoid confusion. |
Transcoding / Hardware acceleration
| Variable | Default | Description |
|---|---|---|
PHLIX_HWACCEL | none | Preferred hardware acceleration. Valid values: nvidia, vaapi, videotoolbox, qsv, amf, v4l2, none. Overridden by config/ffmpeg.php hwaccel.vendor_priority. See config/ffmpeg.php. |
Timezone
| Variable | Default | Description |
|---|---|---|
TZ | system TZ | PHP date_default_timezone_set() value. Controls timestamps in logs and EPG data. Set to your local timezone (e.g., America/New_York, Europe/London). |
The production database credentials live in
config/database.php(which readsDB_PASSWORDfrom the environment viagetenv('DB_PASSWORD')).