Skip to main content

Configuration Reference

This page documents the environment variables and configuration values used by OPBX. The Default column shows the value in .env.example (or in docker-compose.yml for variables that only the compose file reads). Where the code falls back to a different value when the variable is unset, the description says so.

Environment Variables​

Core Application​

VariableDefaultDescription
APP_NAMEOPBX - Open Source Business PBXApplication name
APP_ENVproductionEnvironment mode
APP_KEY—Encryption key. Generate with php artisan key:generate
APP_DEBUGfalseEnable debug mode
APP_URLhttps://your-domain.comBase URL
APP_PORT80Host port for Nginx
OPCACHE_VALIDATE_TIMESTAMPS1Compose only. 1 makes PHP OPcache revalidate file timestamps in the PHP containers, so code changes are picked up without a restart; any other value keeps the image defaults
APP_LOCALEenDefault locale
APP_FALLBACK_LOCALEenFallback locale
APP_FAKER_LOCALEen_USLocale for factories and seeders

Application Mode​

VariableDefaultDescription
OPBX_APPLICATION_MODEdevelopmentdevelopment or production (SaaS)
OPBX_APPLICATION_WEBHOOK_BASEURL—Application-level webhook base URL. When set, it overrides every organization's cloudonix_settings.webhook_base_url

Database​

VariableDefaultDescription
DB_CONNECTIONmysqlDatabase driver
DB_HOSTmysqlDatabase host
DB_PORT3306Database port
DB_DATABASEopbxDatabase name
DB_USERNAMEopbxDatabase user
DB_PASSWORD—Database password
DB_ROOT_PASSWORD—MySQL root password
DB_EXPOSE_PORT3306Loopback host port for MySQL (dev only, needs docker-compose.ports.yml)
DB_CDR_ARCHIVE_DATABASEemptySchema for the warm call-record tier. See Call detail records

Redis​

VariableDefaultDescription
REDIS_CLIENTphpredisPHP Redis client
REDIS_HOSTredisRedis host
REDIS_PORT6379Redis port
REDIS_PASSWORD—Redis password
REDIS_DB0Default database
REDIS_EXPOSE_PORT6379Loopback host port for Redis (dev only, needs docker-compose.ports.yml)

Session / Sanctum​

VariableDefaultDescription
SESSION_DRIVERredisSession storage
SESSION_LIFETIME120Session lifetime (minutes)
SESSION_ENCRYPTfalseEncrypt session data
SESSION_PATH/Session cookie path
SESSION_DOMAINnullSession cookie domain
SANCTUM_STATEFUL_DOMAINSlocalhost:3000,127.0.0.1:3000Cookie auth domains

Broadcasting / WebSockets​

VariableDefaultDescription
BROADCAST_DRIVERpusherBroadcasting driver (config/broadcasting.php falls back to redis when unset)
BROADCAST_CONNECTIONpusherRedis connection name used only by the redis broadcaster (code default default)
PUSHER_APP_IDapp-idSoketi app ID
PUSHER_APP_KEYpbxappkeySoketi app key
PUSHER_APP_SECRET—Soketi app secret. Required: compose refuses to start Soketi without it
PUSHER_HOSTsoketiSoketi host
PUSHER_PORT6001Soketi port
PUSHER_SCHEMEhttp
PUSHER_APP_CLUSTERmt1Pusher cluster name (required by the client library; Soketi ignores it)
SOKETI_PORT6001Host port for Soketi
SOKETI_METRICS_PORT9601Host port for Soketi metrics
SOKETI_DEBUG0Soketi debug logging (compose only)

config/websockets.php (LARAVEL_WEBSOCKETS_*, WEBSOCKETS_REPLICATION_MODE) belongs to the beyondcode Laravel WebSockets package, which is not installed; nothing reads it. Soketi is the WebSocket server.

Frontend (Vite)​

VariableDefaultDescription
FRONTEND_PORT3000Host port for Vite dev server
FRONTEND_URLhttp://localhost:3000
VITE_API_BASE_URL/api/v1API base path
VITE_WS_HOSTlocalhostWebSocket host
VITE_WS_PORT6001WebSocket port
VITE_WS_SCHEMEhttpWebSocket scheme
VITE_ALLOWED_HOSTSlocalhost,.localhostHost names the Vite dev server accepts

S3 Storage (SeaweedFS)​

The MINIO_* variable names are kept for the S3 client. The S3 store is SeaweedFS; on AWS, point the endpoint and keys at real S3.

VariableDefaultDescription
MINIO_ACCESS_KEYemptyS3 access key (SeaweedFS identity). docker/scripts/generate-secrets.sh generates it. Required: compose refuses to start SeaweedFS without it, and the app entrypoint rejects weak values such as minioadmin
MINIO_SECRET_KEYemptyS3 secret key. Same rules as the access key
MINIO_REGIONus-east-1
MINIO_BUCKETrecordingsDefault bucket
MINIO_ENDPOINThttp://seaweedfs:8333S3 API endpoint
MINIO_USE_PATH_STYLEtrue
MINIO_EXPOSE_PORT—Expose the S3 API on loopback port 8333 (dev only, needs docker-compose.ports.yml)
MINIO_CONSOLE_EXPOSE_PORT—Expose the SeaweedFS master UI on loopback port 9333 (dev only, needs docker-compose.ports.yml)
CDR_ARCHIVE_BUCKETcdr-archivePrivate bucket for the cold call-record archive. Same credentials as above
STORAGE_WAIT_TIMEOUT120Seconds the app container waits at start for the S3 endpoint before it creates any missing bucket (recordings, cdr-archive) and verifies both

The MINIO_* names are kept for compatibility. Existing installations that used MinIO move their objects with scripts/migrate-minio-to-seaweedfs.sh, which scripts/upgrade.sh runs once by itself. The upgrade steps are in Updating OPBX.

Cloudonix CPaaS​

VariableDefaultDescription
CLOUDONIX_API_BASE_URLhttps://api.cloudonix.ioCloudonix API base URL
CLOUDONIX_API_TIMEOUT30API timeout
CLOUDONIX_SIP_SERVERsip.cloudonix.ioSIP server
CLOUDONIX_WEBHOOK_REQUIRE_AUTHfalsefalse: status, CDR and dialer webhooks from an organization with no webhook key are accepted and logged as webhook.unauthenticated. true: they are rejected with 401. Turn on after php artisan cloudonix:provision-webhook-keys
CXML_DEFAULT_TIMEOUT30Default <Dial> timeout (seconds) for extension and ring group dials
CLOUDONIX_API_TOKEN—Read into services.cloudonix.api_token, but no code uses it; API calls use each organization's domain_api_key
VOICE_WEBHOOK_TOKEN—Read into services.cloudonix.voice_webhook_token, but no code uses it

Per-organization credentials, including the webhook key, are stored in cloudonix_settings.

Webhooks / Rate Limiting​

VariableDefaultDescription
RATE_LIMIT_API60API requests per organization per window (rate_limit_org:api on the /api/v1 resource routes). Also the api named limiter, which no route applies
RATE_LIMIT_API_MINUTES1Window, in minutes, for RATE_LIMIT_API
RATE_LIMIT_DEFAULT / RATE_LIMIT_DEFAULT_MINUTES60 / 1Fallback per-organization limit for any other rate_limit_org:<type> (none is applied today)
RATE_LIMIT_SENSITIVE10Sensitive operations per minute (throttle:sensitive, and the sensitive-operations middleware for users who are not Owner or PBX Admin)
RATE_LIMIT_SENSITIVE_ADMIN60sensitive-operations limit for Owners and PBX Admins
RATE_LIMIT_SENSITIVE_DECAY60Seconds a sensitive-operations hit counts
RATE_LIMIT_AUTH5Auth attempts per minute per IP (throttle:auth)
WEBHOOK_IDEMPOTENCY_TTL86400Seconds a webhook idempotency key (idem:webhook:{key}) is kept
WEBHOOK_MAX_CACHE_SIZE102400Largest response (bytes) cached for a duplicate webhook
WEBHOOK_REPLAY_MAX_AGE300Webhooks with an older timestamp are rejected (CDRs are accepted with a log line)

Cloudonix webhook and voice routing routes are intentionally not rate-limited: with many organisations Cloudonix sends hundreds of events per second, and throttling them would drop calls and CDRs. They are protected by authentication and idempotency instead.

The values above are in config/rate_limiting.php. Other limits are fixed in code: registration (10 per hour per IP; config('rate_limiting.registration') has no key, so the fallback applies), embed (60 per minute per token and IP), dialer-worker (DIALER_WORKER_RATE_LIMIT, default 1000 per minute per IP), the public status page (throttle:60,1), and a few routes with inline throttle:<n>,1 values.

See Webhooks for the authentication flow.

Voice Routing​

VariableDefaultDescription
VOICE_CACHE_EXTENSION_TTL1800Extension routing cache TTL (seconds)
VOICE_CACHE_BUSINESS_HOURS_TTL900Business hours routing cache TTL (seconds)

The per-organization no-answer timeout is cloudonix_settings.no_answer_timeout.

Circuit Breaker​

VariableDefaultDescription
CIRCUIT_BREAKER_CLOUDONIX_THRESHOLD5Failure threshold
CIRCUIT_BREAKER_CLOUDONIX_TIMEOUT30Timeout (seconds)
CIRCUIT_BREAKER_CLOUDONIX_RETRY_AFTER60Retry after (seconds)
CIRCUIT_BREAKER_DEFAULT_THRESHOLD / _TIMEOUT / _RETRY_AFTER5 / 30 / 60Same settings for any other service (not in .env.example)

Dialer Worker​

VariableDefaultDescription
DIALER_WORKER_IDworker-1Worker identifier
DIALER_LARAVEL_API_URLhttp://nginxLaravel API URL
DIALER_WORKER_API_TOKEN—Worker auth token. Required: compose refuses to start without it
DIALER_WEBHOOK_PORT8081Worker webhook port (container)
DIALER_WEBHOOK_HOST_PORT8181Host port publishing the worker webhook endpoint
DIALER_POLL_INTERVAL10sCampaign poll interval
DIALER_WORKER_API_TOKEN_SECONDARY—Optional second token Laravel accepts, for rotation
DIALER_WORKER_RATE_LIMIT1000Requests per minute per IP on the worker API (throttle:dialer-worker)
DIALER_WORKER_HEALTH_URLhttp://dialer-worker:8081/webhooks/healthWorker health URL used by the campaign monitor
DIALER_WORKER_MAX_RETRIES / DIALER_WORKER_RETRY_DELAY3 / 60Read into services.dialer_worker, but no code uses them

Auto-dialer jobs use the auto-dialer queue connection: AUTO_DIALER_QUEUE_DRIVER (default redis, or sync when QUEUE_CONNECTION=sync), AUTO_DIALER_QUEUE_CONNECTION (default), AUTO_DIALER_QUEUE (auto-dialer) and AUTO_DIALER_QUEUE_RETRY_AFTER (7500 seconds). queue-worker works the auto-dialer and default queues; queue-worker-long works auto-dialer-long with a 7200-second job timeout.

AMD Worker​

VariableDefaultDescription
AMD_WORKER_API_TOKEN—Callback auth token. Required: compose refuses to start without it
AMD_MAX_CONCURRENT_STREAMS100Max streams
AMD_MIN_BEEP_START_MS2000Earliest point (ms after stream start) a beep may be accepted
AMD_LOG_LEVELinfoLog level
AMD_DUMP_AUDIOfalseDump audio to WAV
AMD_DUMP_AUDIO_PATH/tmp/amd-dumpsDump directory

ACD Worker​

VariableDefaultDescription
ACD_WORKER_API_TOKEN—Bearer token for Laravel → worker requests. Required: compose refuses to start without it
ACD_WORKER_URLhttp://acd-worker:8084Worker URL used by Laravel
ACD_LOG_LEVELinfoLog level

The worker reads Redis from ACD_REDIS_HOST, ACD_REDIS_PORT and ACD_REDIS_PASSWORD, which the compose file sets from the Redis service and REDIS_PASSWORD. See ACD Worker.

MCP Server​

VariableDefaultDescription
MCP_PORT8080Host port publishing the MCP server (container port 8080). The UI shows the MCP endpoint URL with this port
MCP_LOG_LEVELinfoMCP server log level (compose only)

ngrok​

VariableDefaultDescription
NGROK_AUTHTOKEN—ngrok authtoken
NGROK_DOMAIN—Custom domain (paid plans)

The ngrok service is in the dev profile: docker compose --profile dev up -d ngrok.

UserCheck​

VariableDefaultDescription
USERCHECK_ENABLEDtrueEnable email validation at registration and invitation
USERCHECK_API_TOKEN—API token
USERCHECK_BASE_URLhttps://api.usercheck.com
USERCHECK_TIMEOUT5Seconds
USERCHECK_BLOCK_DISPOSABLEtrueReject disposable addresses
USERCHECK_BLOCK_BLOCKLISTEDtrueReject blocklisted domains
USERCHECK_BLOCK_SPAMtrueReject spam domains
USERCHECK_BLOCK_ROLE_ACCOUNTStrueReject role accounts (for example info@)
USERCHECK_BLOCK_RELAY_DOMAINSfalseReject email relay domains
USERCHECK_BLOCK_PUBLIC_DOMAINSfalseReject public mail providers

SaaS Sign-in (Auth0) and Invitations​

VariableDefaultDescription
OPBX_SAAS_ENABLEDfalseEnable Auth0 social sign-in
AUTH0_DOMAIN—Auth0 tenant domain
AUTH0_CLIENT_ID—
AUTH0_CLIENT_SECRET—
AUTH0_REDIRECT_URIhttps://app.opbx.com/ui/auth/callbackOAuth callback URL
AUTH0_PROVIDERSgoogle,facebook,microsoft,github,xEnabled social providers. They map to the Auth0 connections google-oauth2, facebook, windowslive, github and twitter
OPBX_INVITE_TOKEN_TTL_HOURS24Hours an invitation link is valid
OPBX_INVITE_RATE_LIMIT_PER_HOUR10Invitations a user can send per hour

Invitation links use FRONTEND_URL (falling back to APP_URL).

Embeddable Dialer​

VariableDefaultDescription
EMBED_ALLOW_INSECURE_FRAMINGfalseAllow embedding the dialer on http:// sites
EMBED_WIDGET_URL/embed/assets/embed-widget.jsWidget script URL served by the loader
EMBED_WIDGET_CSS_URL/embed/assets/embed-widget.cssWidget stylesheet URL

Security Headers​

Set in config/security.php and applied by the SecurityHeaders middleware. See Security.

VariableDefaultDescription
HSTS_MAX_AGE31536000Strict-Transport-Security max-age. The header is sent only when APP_ENV=production
HSTS_INCLUDE_SUBDOMAINStrueAdd includeSubDomains
HSTS_PRELOADfalseAdd preload
CSP_REPORT_URI—CSP report-uri
CSP_CONNECT_DOMAINSemptyComma-separated extra origins for the CSP connect-src

Recordings​

Set in config/recordings.php.

VariableDefaultDescription
RECORDINGS_MAX_SIZE_KB5120Largest audio file you can upload (MP3 or WAV)
RECORDINGS_MAX_DOWNLOAD_BYTES209715200Largest call recording DownloadCallRecordingJob fetches (200 MB)
RECORDINGS_STORAGE_DISKrecordingsFilesystem disk for recordings
RECORDINGS_URL_TIMEOUT10Seconds to wait when fetching a remote audio URL
RECORDINGS_ALLOWED_DOMAINSempty (all)Comma-separated domains allowed for remote audio URLs
RECORDINGS_ACCESS_TOKEN_EXPIRY30Minutes a recording download token is valid
RECORDINGS_SECURE_DELETEtrueSecure deletion of recording files
RECORDINGS_QUEUE_PROCESSINGtrueProcess uploads on the queue

RECORDINGS_LOCAL_PATH, RECORDINGS_ALLOW_HTTP, RECORDINGS_QUEUE_CONNECTION, RECORDINGS_AUDIT_LOGGING, RECORDINGS_RETENTION_DAYS, RECORDINGS_CACHE_ENABLED and RECORDINGS_CACHE_TTL are read by the config file but not used by any code. Recording expiry is RECORDING_RETENTION_MONTHS (see Data Retention).

reCAPTCHA​

VariableDefaultDescription
RECAPTCHA_ENABLEDfalseEnable reCAPTCHA v3
RECAPTCHA_SITE_KEY—
RECAPTCHA_SECRET_KEY—
RECAPTCHA_MIN_SCORE0.5

Transactional Email​

VariableDefaultDescription
EMAIL_PROVIDERmailgunEnabled provider
EMAIL_QUEUEdefaultQueue for email jobs
EMAIL_TRACK_OPENStrue
EMAIL_TRACK_CLICKStrue
MAIL_MAILERsmtpMail driver
MAIL_HOSTmailpitSMTP host
MAIL_PORT1025SMTP port
MAIL_FROM_ADDRESSnoreply@your-domain.com
MAIL_FROM_NAME${APP_NAME}

Only one provider may be enabled. Provider-specific variables:

ProviderVariables
MailgunMAILGUN_ENABLED (false), MAILGUN_DOMAIN, MAILGUN_SECRET, MAILGUN_ENDPOINT (api.mailgun.net), MAILGUN_REGION (us or eu)
MailjetMAILJET_ENABLED (false), MAILJET_APIKEY, MAILJET_APISECRET
MailerLiteMAILERLITE_ENABLED (false), MAILERLITE_API_KEY
SendInBlue/BrevoSENDINBLUE_ENABLED (false), SENDINBLUE_API_KEY

MAIL_USERNAME and MAIL_PASSWORD (null) are the SMTP credentials.

Logging​

VariableDefaultDescription
LOG_CHANNELstack
LOG_STACKsingle,jsonChannels in the stack channel. In the app, queue and scheduler containers compose sets it from OPBX_LOG_STACK (default single,json)
LOG_LEVELinfo
LOG_DEPRECATIONS_CHANNELnull

Log Stack (profile logs)​

VariableDefaultDescription
LOG_STACK_ENABLEDfalseTells docker/scripts/validate-env.sh that the log stack is used, so it checks GRAFANA_ADMIN_PASSWORD
GRAFANA_ROOT_URLhttp://localhost/logs/Set to <APP_URL>/logs/
GRAFANA_CSRF_TRUSTED_ORIGINSempty
GRAFANA_ADMIN_USERadmin
GRAFANA_ADMIN_PASSWORD—Required by compose even when the logs profile is not started
LOKI_RETENTION_HOURS168Log retention in Loki

Laravel Framework Settings​

.env.example also sets QUEUE_CONNECTION=redis, CACHE_STORE=redis and FILESYSTEM_DISK=local. The other variables read by the stock Laravel config files (config/app.php, auth.php, cache.php, database.php, filesystems.php, logging.php, mail.php, queue.php, session.php, services.php: for example CACHE_PREFIX, REDIS_PREFIX, SESSION_SECURE_COOKIE, SESSION_SAME_SITE, AWS_*, POSTMARK_*, RESEND_*, SLACK_*, MEMCACHED_*, SQS_*) keep their Laravel defaults and are described in the Laravel documentation.

Data Retention​

Retention periods are set in days (or hours where noted) and enforced by the scheduler. Set a variable to an empty value to turn pruning off for that data. The pruning runs daily at 03:00 (model:prune; previous list versions at 03:15); the CDR tiers and recording expiry run at 04:00 and 04:30 UTC.

VariableDefaultData
RETENTION_SESSION_UPDATES_DAYS30Live call-state updates
RETENTION_CALL_NOTIFICATION_LOGS_DAYS90Call notification delivery logs
RETENTION_EMAIL_LOGS_DAYS90Email logs
RETENTION_BLOCKED_CALL_LOGS_DAYS90Blocked-call logs
RETENTION_QUEUE_CALLS_DAYS183Call-queue call records (deleted, not archived)
RETENTION_AUTO_DIALER_CALL_SESSIONS_DAYS183Auto-dialer call sessions (deleted)
RETENTION_AUTO_DIALER_LIST_VERSIONS_DAYS30Previous versions of distribution lists, counted from the upload that replaced them (distribution-lists:prune-versions, daily at 03:15). Also removes list backup files left by older releases once they are this old. The value applies to versions replaced after it is set
RETENTION_PLATFORM_AUDIT_LOGS_DAYS365Platform audit log
PLATFORM_HEALTH_RETENTION_HOURS24Platform Status history, in hours

Call detail records (tiered)​

Call records are not pruned. They move through three tiers, by session time in UTC:

VariableDefaultMeaning
CDR_HOT_MONTHS6Months kept in the main database
CDR_WARM_MONTHS12Age at which a record leaves the warm archive table and is written to a cold archive file
CDR_COLD_RETENTION_YEARS7Years a cold (compressed CSV) archive file is kept
RECORDING_RETENTION_MONTHSemptyMonths a call recording is kept. Empty means the cold retention period. A larger value is reduced to the cold retention period

The warm tier is the archived_call_detail_records table on the cdr_archive connection. Without DB_CDR_ARCHIVE_DATABASE it lives in the main schema. To use a separate schema, create it, grant the app user, and set DB_CDR_ARCHIVE_DATABASE (and optionally DB_CDR_ARCHIVE_HOST, _PORT, _USERNAME, _PASSWORD; each falls back to the main database setting). The cold tier is written to the bucket named by CDR_ARCHIVE_BUCKET (default cdr-archive). See Call Logs.

Platform Status​

VariableDefaultMeaning
PLATFORM_STATUS_TIMEOUT2.5Seconds before a component probe fails
PLATFORM_STATUS_SLOW_MS1500Responses slower than this are degraded
PLATFORM_STATUS_QUEUE_PENDING500Pending jobs above this are degraded
PLATFORM_STATUS_QUEUE_FAILED50Failed jobs at or above this are degraded
PLATFORM_STATUS_CLOUDONIX_SLOW_MS3000Cloudonix answers slower than this count as slow
PLATFORM_STATUS_CLOUDONIX_TIMEOUT5Seconds before a Cloudonix check counts as no answer
PLATFORM_STATUS_CLOUDONIX_CONSECUTIVE3Consecutive bad checks before the status changes

Each probe URL can be overridden with PLATFORM_STATUS_*_URL. See Platform Management.

Health Check Endpoints​

EndpointAuthPurpose
GET /api/healthNoneAPI liveness: {"status":"ok","timestamp":...}
GET /api/v1/public/statusNone (cached 30 s, 60/min)Public platform status, grouped by service
GET /api/voice/healthNoneVoice routing health
GET /api/storage/healthSanctumWrites, reads and deletes a test object in the recordings bucket
GET /api/websocket/healthSanctumSends a test event through Soketi
GET /api/v1/dialer/worker/healthDialer worker tokenLaravel side of the dialer worker API
GET dialer-worker:8081/webhooks/healthNone (internal)Dialer worker health
GET amd-worker:8083/healthNone (internal)AMD worker health and detection counters
GET acd-worker:8085/healthNone (internal)ACD worker health; 503 when Redis does not answer
GET mcp-server:8080/healthNone (internal)MCP server health
GET soketi:9601/readyNoneSoketi readiness

The internal endpoints are what the Docker healthchecks call. Platform Status (platform:health-check, every minute) probes all components and stores the results in platform_health_checks.

Docker Services​

ServiceContainerPurpose
appopbx_appLaravel PHP-FPM
nginxopbx_nginxReverse proxy
frontendopbx_frontendVite dev server
mysqlopbx_mysqlMySQL 8.4 LTS (mysql_native_password enabled for compatibility)
redisopbx_redisCache, sessions, queues
seaweedfsopbx_seaweedfsS3-compatible object storage (SeaweedFS 4.47) for recordings, IVR audio and the cold CDR archive
soketiopbx_websocketWebSocket server
queue-workeropbx_queue_workerQueue worker
queue-worker-longopbx_queue_worker_longQueue worker for long-running jobs
scheduleropbx_schedulerTask scheduler
dialer-workeropbx_dialer_workerGo dialer worker (single replica)
amd-workeropbx_amd_workerJava AMD worker
acd-workeropbx_acd_workerJava call-queue (ACD) worker
mcp-serveropbx_mcpMCP server for AI assistants
ngrokopbx_ngrokWebhook tunnel. Profile dev only
loki, alloy, grafana, docker-socket-proxyopbx_loki, opbx_alloy, opbx_grafana, opbx_docker_socket_proxyLog stack. Profile logs only

Services are started with docker compose up -d. Optional services need a profile:

ProfileStarts
devngrok tunnel for local webhook testing
logsLoki, Alloy, Grafana and the docker-socket-proxy. See Centralized Logs

Host ports for the database, Redis and SeaweedFS are not published by default. docker-compose.ports.yml adds loopback ports for development.