Skip to content

Config Files Reference

Since: 0.18.0

Reference for every config/*.php file in phlix-server and what each key controls. All connection parameters in database.php are environment-variable-driven — see the Database section for the full list.

For environment variables that control runtime behaviour, see /reference/env-vars.


config/server.php

Core server and worker settings.

php
'server' => [
    'name' => 'Phlix Media Server',   // Displayed in the web UI header
    'host' => '0.0.0.0',               // Bind address — 0.0.0.0 = all interfaces
    'port' => 8096,                    // Main HTTP port
    'context' => [],                   // PHP stream context options (rarely needed)
],

Worker

php
'worker' => [
    'count'        => 'auto',          // 'auto' = CPU core count; integer for fixed count
    'stdout_file'  => '/var/phlix/logs/stdout.log',  // Workerman process stdout
    'pid_file'     => '/var/run/phlix/pid',          // PID file path
],

Process

php
'process' => [
    'reloadable' => true,   // SIGHUP reloads worker without full restart
    'reuse_port' => true,   // Enables SO_REUSEPORT for better multi-process throughput
],

Coroutine runtime

php
'coroutine' => [
    'enabled'    => true,               // Use Swoole event loop (required for DB pool)
    'hook_flags' => null,              // null = safe curated flags; override only if needed
],

The curated hook allowlist enables socket, sleep, and stream hooks only. SWOOLE_HOOK_ALL caused crashes with PHP 8.5 / Swoole 6.2.1 / kernel io_uring — do not re-enable it without testing.

HLS streaming

php
'hls' => [
    'segment_dir'         => sys_get_temp_dir() . '/phlix_hls',  // Where .m3u8/.ts files are written
    'base_url'            => 'http://localhost:8096',             // Used for absolute playlist URLs (casting)
    'segment_seconds'     => 6,                                  // Target segment duration (Apple default)
    // SV-1.9: free-space floor for the segment-cache filesystem. Below this the
    // server sweeps the cache and returns 503 + Retry-After: 3 instead of ENOSPC.
    'min_disk_space_bytes' => (int) (getenv('HLS_MIN_DISK_SPACE_BYTES') ?: 500 * 1024 * 1024),
],

segment_dir is the single source of truth — both the transcode writer and the HLS streamer read from the same path. min_disk_space_bytes is overridable via HLS_MIN_DISK_SPACE_BYTES (default 500 MiB).

Rate limiting (SV-4.15)

Per-surface auth rate limits for the previously-unlimited surfaces (register, refresh, WebAuthn start+finish, public JWKS, WS-connect on :8097). Over-limit HTTP surfaces reply 429 Too Many Requests + Retry-After (body {"error":"Too Many Requests","code":"rate_limited"}); WS-connect rejects the handshake. login keeps its own DB-backed limiter (migration 074) and is not configured here.

php
// Each surface's {max, window} is env-overridable via
// RATE_LIMIT_<SURFACE>_MAX / RATE_LIMIT_<SURFACE>_WINDOW, falling back to
// Phlix\Common\RateLimit\RateLimitProfiles::defaults():
//   register 5/600, refresh 30/60, webauthn_start 10/60,
//   webauthn_finish 10/60, jwks 120/60, ws_connect 30/60
'rate_limit' => [ /* built from RateLimitProfiles::defaults() + env overrides */ ],

// Reverse-proxy hops used to derive the REAL client IP (X-Forwarded-For /
// X-Real-IP) for rate-limit keys. Default = loopback only. Override with a
// comma-separated IP/CIDR list via TRUSTED_PROXIES when a NON-loopback proxy
// fronts the server, or IP-keyed limits will bucket every request under the
// proxy address.
'trusted_proxies' => \Phlix\Common\Http\TrustedProxyResolver::configuredProxies(),

Register/refresh/WebAuthn are backed by the shared DB table migrations/085_rate_limit_buckets.sql (true-global across all HTTP workers); JWKS and WS-connect are worker-local in-memory. Run migration 085 on deploy. See /reference/env-vars and /advanced/reverse-proxy.

WebSocket (SyncPlay)

php
'websocket' => [
    'host'                     => '0.0.0.0',
    'port'                     => 8097,        // Separate from HTTP port
    'stale_connection_timeout' => 300,          // Seconds before a stale WS connection is dropped
    'stale_group_timeout'      => 3600,         // Seconds before an idle SyncPlay group is dissolved
],

Included sub-configs

php
'ffmpeg' => require __DIR__ . '/ffmpeg.php',   // Transcoding config (paths, HW accel, profiles)
'hub'    => require __DIR__ . '/hub.php',     // Hub pairing and heartbeat settings
'relay'  => require __DIR__ . '/relay.php',    // Relay tunnel settings

config/database.php

All connection parameters are environment-variable-driven with safe localhost defaults for a stock single-host install. Any or all of these can be overridden — a remote or renamed database only needs the relevant DB_* variables set.

Connection

Env varLegacy aliasDefaultDescription
DB_HOST127.0.0.1MySQL host address
DB_PORT3306MySQL port
DB_DATABASEDB_NAMEphlixDatabase name
DB_USERDB_USERNAMEphlixDatabase username
DB_PASSWORD(empty)Database password

Connection pool

Env varDefaultDescription
DB_POOL_ENABLEDtrueOn by default (Stream Quality/ABR step S9): each coroutine gets its own leased connection for intra-worker DB parallelism. Set to 0/false/no/off to fall back to the single-connection coroutine mutex.
DB_POOL_SIZE8Per-worker pool ceiling; total server maximum ≈ worker_count × pool_size — keep under MySQL max_connections
php
'connections' => [
    'mysql' => [
        'host'         => getenv('DB_HOST') ?: '127.0.0.1',
        'port'         => (int) (getenv('DB_PORT') ?: 3306),
        'database'     => getenv('DB_DATABASE') ?: (getenv('DB_NAME') ?: 'phlix'),
        'username'     => getenv('DB_USER') ?: (getenv('DB_USERNAME') ?: 'phlix'),
        'password'     => getenv('DB_PASSWORD') ?: '',
        'charset'      => 'utf8mb4',
        // DB_POOL_ENABLED defaults ON (unset env var -> '1'); DB_POOL_ENABLED=0
        // is the explicit opt-out back to the single-connection mutex path.
        'pool_enabled' => filter_var(
            getenv('DB_POOL_ENABLED') === false ? '1' : getenv('DB_POOL_ENABLED'),
            FILTER_VALIDATE_BOOLEAN
        ),
        'pool_size'    => (int) (getenv('DB_POOL_SIZE') ?: 8),
        'timeout'      => 5,
    ],
],

config/logger.php

Rotating file log handlers. All paths are relative to the phlix-server root.

php
'default' => 'file',   // Handler used when no channel is specified

Available handlers

HandlerPath keyLevelPurpose
filepathdebugGeneral application log — all channels, every level (.logs/app.log)
errorpatherrorError aggregation — all channels, error-and-above (.logs/error.log)
eventspathdebugPSR-14 event dispatch trace — only the EVENTS channel, and only when PHLIX_DEBUG_EVENTS is truthy (.logs/events.log)
pluginspathdebugPlugin lifecycle events (install / enable / disable / uninstall) — only the PLUGINS channel (.logs/plugins.log)

Each handler may declare two optional gates that decide whether it attaches to a given per-channel logger: channels (a list of channel names it serves; absent or empty = attach to every channel) and env (an environment variable that must be truthy — 1/true/yes/on — for it to attach; absent = always attach). Without these gates every handler attaches to every channel, which previously duplicated each record across app.log, events.log, and plugins.log and polluted the two subsystem logs with unrelated records. app.log and error.log are intentionally left unrestricted so no diagnostic coverage is lost.

php
'file' => [
    'type'       => 'rotating_file',
    'path'       => __DIR__ . '/../.logs/app.log',
    'max_files'  => 30,      // Keep 30 rotated files
    'level'      => 'debug',
],

config/ffmpeg.php

FFmpeg binary paths, transcoding limits, hardware acceleration, and codec profiles.

php
'ffmpeg_path'              => '/usr/bin/ffmpeg',
'ffprobe_path'            => '/usr/bin/ffprobe',
'transcode_dir'            => '/var/transcodes',   // Working directory for transcode jobs
'segment_dir'              => '/var/segments',      // HLS/DASH segment output (see server.php hls.segment_dir)
'max_concurrent_transcodes' => 4,                   // Global transcode job limit
'transcode_timeout'       => 7200,                  // Seconds before a transcode job is abandoned

Hardware acceleration

php
'hwaccel' => [
    'enabled'         => true,
    'prefer_hardware' => true,          // Try GPU encoders before software encoders
    'vendor_priority' => [              // Lower number = higher priority
        'nvenc'     => 0,   // NVIDIA NVENC
        'vaapi'     => 1,   // Intel VAAPI / Quick Sync
        'qsv'       => 2,   // Intel Quick Sync (legacy path)
        'videotoolbox' => 3, // macOS VideoToolbox
        'amf'       => 4,   // AMD AMF
        'v4l2'      => 5,   // Linux V4L2
    ],
],

See /advanced/hardware-transcoding for how to configure and verify hardware acceleration.

Hardware acceleration profiles

php
'hwaccel_profiles' => require __DIR__ . '/hwaccel_profiles.php',

Defines per-quality-tier encoder settings (bitrate, preset, profile). Rarely needs manual changes.

Loudness normalization (SV-3.3)

php
'loudness' => [
    'enabled' => false,   // OFF by default
    'I'       => -16,     // Integrated loudness target (LUFS)
    'LRA'     => 11,      // Loudness range
    'TP'      => -1.5,    // True-peak ceiling (dBTP)
],

When enabled, an EBU R128 loudnorm audio filter (I/LRA/TP) is applied to re-encoded audio on every segment-assembly path. Common targets: Podcast/online -16/11/-1.5, Broadcast -23/7/-2.0, Streaming -27/2.0/-2.0.

Operator note: loudnorm applies only to re-encoded audio. Any rung that copies the source stream (-c:a copy, e.g. the original variant) and every direct-play session bypass normalization by design — you cannot filter a copied (non-decoded) stream. To normalize such content it must be transcoded.

Subtitles

php
'subtitles' => require __DIR__ . '/subtitles.php',

Controls embedded subtitle extraction and burn-in behaviour.

DASH streaming

php
'dash' => [
    'enabled'       => true,
    'segment_dir'   => '/var/segments',
    'default_codecs' => [
        'video' => 'avc1.64001f',   // H.264 High Profile Level 4.1
        'audio' => 'mp4a.40.2',     // AAC-LC
    ],
],

config/hub.php

Hub pairing, heartbeat, and public hostname configuration.

php
'hub_url'            => getenv('PHLIX_HUB_URL') ?: null,         // Hub base URL
'hub_jwks_url'       => getenv('PHLIX_HUB_JWKS_URL') ?: null,     // Hub JWKS endpoint
'heartbeat_interval' => (int)(getenv('PHLIX_HUB_HEARTBEAT_INTERVAL') ?: 60),  // Seconds (30–3600)
'enrollment_token_ttl' => 7 * 86400,                               // 7 days
'jwks_cache_ttl'     => 900,                                       // 15 minutes
'key_path'           => __DIR__ . '/hub-server-key.pem',            // Server identity key (Ed25519)
'config_dir'         => __DIR__,
'subdomain_auto_claim' => (bool)(getenv('PHLIX_SUBDOMAIN_AUTO_CLAIM') ?: true),
'tls_enabled'        => $tlsEnabled,                                // Derived from PHLIX_TLS_ENABLED env
'domain'             => $domain ?: 'phlix.media',                   // Base domain for *.phlix.media subdomains
'public_url'         => /* derived */ '',                           // Advertised public hostname during pairing

hub-server-key.pem may be either the app's native -----BEGIN ED25519 PRIVATE KEY----- format or a standard PKCS#8 Ed25519 key (-----BEGIN PRIVATE KEY-----, e.g. from openssl genpkey -algorithm Ed25519); both are accepted by the JWKS reader (see JWKS API).

See /hub/what-is-the-hub and /hub/remote-access.


config/relay.php

Relay tunnel settings for the Hub connection.

php
'enabled'          => true,
'hub_wss_url'      => 'wss://hub.example.com/api/v1/servers/{id}/relay',  // Legacy template (not used if hub_relay_ws_url set)
'hub_relay_ws_url' => '',                                       // Explicit endpoint; empty = derive from enrollment (highest-precedence override when set)
'relay_tls'        => false,                                    // PHLIX_RELAY_TLS — TLS (wss://) for the DERIVED scheme; default plaintext ws:// (mirrors hub HUB_RELAY_TLS)
'relay_tls_verify' => true,                                     // PHLIX_RELAY_TLS_VERIFY — verify hub relay cert; set false to accept a self-signed cert
'relay_tls_cafile' => '/etc/ssl/certs/ca-certificates.crt',    // PHLIX_RELAY_TLS_CAFILE — CA bundle used when verifying
'local_http_address' => '127.0.0.1:8096',                      // Where relay pipes relayed bytes locally
'local_address'    => '127.0.0.1:0',                           // Local bind address for the tunnel
'tunnel_hostname'  => '',                                       // Optional override
'reconnect_delay'  => 5,                                        // Seconds between reconnection attempts
'ping_interval'   => 30,                                        // Keep-alive ping interval
'ping_timeout'    => 10,                                        // Seconds to wait for pong before dropping

The relay tunnel's TLS is independent of the server's public HTTP TLS and is off by default — the derived relay scheme is plaintext ws://, matching the hub's plaintext-by-default relay listener (:8802). A TLS relay tunnel requires relay_tls/PHLIX_RELAY_TLS=1 on the server together with HUB_RELAY_TLS=true on the hub. An explicit hub_relay_ws_url (or PHLIX_RELAY_HUB_WS_URL) wins over the derived scheme; when it is a wss:// URL, pair it with PHLIX_RELAY_TLS=1. See Remote Access → Relay tunnel TLS.


config/plugins.php

Plugin catalog sources and auto-update behaviour.

php
'catalog' => [
    'default_source' => 'https://github.com/detain/phlix-plugins',  // Official plugin repository
    'sources'        => [],                                          // Extra operator-defined catalogs
    'fetch_timeout'  => 10,                                          // Seconds before a catalog fetch is abandoned
],
'auto_update' => false,    // When true, periodically reinstalls plugins if a newer version is in any configured catalog

See /plugins/manifest and /dev/plugin-sdk for plugin development.


config/process.php

Managed background worker processes started alongside the HTTP worker.

php
'library-scan' => [
    'enabled'      => true,
    'count'        => 1,        // Atomic claimNext() means count=1 is sufficient
    'poll_seconds' => 5,         // How often the scanner checks for new/changed files
],
'plugin-auto-update' => [
    'enabled'      => true,
    'count'        => 1,
    'poll_seconds' => 86400,    // Daily cadence; only does work when auto_update is enabled in plugins.php
],

These are started by start.php alongside the HTTP worker. Running the scan worker as a standalone service via scripts/run-library-scan-worker.php is safe and mutually exclusive with the in-process worker (atomic job claiming).


config/filesystem.php

Filesystem browse roots for the admin path picker.

php
'browse_roots' => ['/home', '/mnt', '/media', '/data'],

The admin GET /api/v1/admin/fs/browse endpoint is jailed to these roots. Override via PHLIX_BROWSE_ROOTS (comma-separated, replaces defaults):

bash
PHLIX_BROWSE_ROOTS=/home,/mnt/media,/srv/media php public/index.php

See /reference/env-vars for the full variable.

BSD-3-Clause