Skip to content

Server configuration (.env)

A single .env file at the repo root configures the entire stack: docker-compose.yml interpolates ${VAR} into the redis, livekit, ingress, egress and caddy services, and passes the whole file to the core container via env_file: .env. The template with inline comments lives at .env.example.

install.sh writes .env for you with strong random secrets and sane values derived from the domain you give it. You normally never hand-edit it on a fresh install:

Terminal window
curl -fsSL https://www.streamhub.studio/install.sh | sudo bash -s -- \
--non-interactive --domain media.example.com --email you@example.com

Re-running the installer is safe (--dry-run prints the plan without changing anything). See Quick install.

Variable Default Description
HOST 127.0.0.1 Local bind address of the core NestJS process. The reverse proxy targets this — never bind it to a public interface.
PORT 3020 Core HTTP port.
NODE_ENV development (code default) — install.sh/.env.example set production Node environment; gates isProduction behavior.
LOG_LEVEL info pino log level: fatal|error|warn|info|debug|trace.
Variable Default Description
STREAMHUB_DOMAIN streamhub.example.com The single public domain Caddy/nginx fronts (dashboard + API + LiveKit /rtc). Needs a DNS-only A record — do not proxy it through a CDN, WebRTC media is UDP and won’t traverse an HTTP proxy. Use localhost for a local self-signed test. Read by Caddy/nginx/install.sh, not by the core process itself.
ACME_EMAIL admin@example.com Let’s Encrypt / ACME registration email (real domains only).
PUBLIC_WS_URL (empty) The ws/wss URL handed to browser clients in join links and tokens. Real domain: wss://<domain> (rides the single TLS vhost via /rtc). Localhost: ws://127.0.0.1:7880.
RTMP_PUBLIC_HOST (empty) Public host advertised in RTMP publish URLs handed to clients.
PUBLIC_BASE_URL (empty) Absolute base URL used to build HLS/player/embed/sample links (and the yolo plugin’s public base). Empty = derive from the incoming request host. Takes precedence over STREAMHUB_PUBLIC_URL wherever both are checked.
STREAMHUB_PUBLIC_URL (empty) Fallback base URL (samples, the cluster join-command hint) used when PUBLIC_BASE_URL is unset. install.sh sets this to https://<domain>.
STREAMHUB_APP_URL https://app.streamhub.studio Public base URL of the dashboard SPA, used to build magic-link, invite and password-reset URLs in outgoing email.

Shared by the core, ingress and egress services.

Variable Default Description
LIVEKIT_URL ws://127.0.0.1:7880 How core reaches LiveKit’s control plane locally.
LIVEKIT_API_KEY (none — required) LiveKit API key. Generate with livekit-server generate-keys, or let install.sh generate one.
LIVEKIT_API_SECRET (none — required) LiveKit API secret.
LIVEKIT_REDIS_ADDRESS (unset) Edge nodes only (written by install.sh --join): host:port of the origin’s coordination Redis.
LIVEKIT_REDIS_PASSWORD (unset) Edge nodes only: password for the origin’s Redis.
LIVEKIT_WEBHOOK_URL http://127.0.0.1:3020/api/v1/webhooks/livekit Where LiveKit posts room webhooks. Edge nodes point this at the origin’s core — edges run no core of their own.
Variable Default Description
REDIS_URL redis://127.0.0.1:6379 Redis used by LiveKit ingress/egress coordination and BullMQ job queues.
REDIS_BIND 127.0.0.1 Space-separated list of IPs the redis container binds to. Add a private IP here to serve edge nodes (paired with a password).
REDIS_PASSWORD (empty = auth disabled) Password for the local Redis container (--requirepass).
STREAMHUB_CLUSTER_REDIS_URL (unset) Redis URL handed back to a joining edge node in the /cluster/join response, so it attaches to the same LiveKit coordination Redis. Unset → returned as null (the edge keeps its local default).
Variable Default Description
STREAMHUB_JWT_SECRET (none — required) Signs the dashboard login JWT (POST /api/v1/auth/login).
ADMIN_USER (empty) Break-glass dashboard admin username. Login is disabled if either ADMIN_USER or ADMIN_PASS is empty.
ADMIN_PASS (empty) Break-glass dashboard admin password.
STREAMHUB_API_TOKEN sk_change_me Global API bearer token, seeded into the DB by deploy/seed-token.js (the DB keeps only a hash). Kept in .env only so re-running the installer stays idempotent.
STREAMHUB_AUTHZ_ENFORCE on RBAC + quota + per-app-token isolation mode: off | log | on. on (secure-by-default) enforces; log runs the same checks but only logs what would be blocked (useful when migrating a deployment that ran unenforced); off disables checks entirely. Global sk_ tokens and the break-glass admin always bypass RBAC.
STREAMHUB_ALLOW_SIGNUP (unset = off) Public self-signup (POST /auth/signup + “Create account” in the dashboard). 1/true/on/yes = anyone can sign up and get their own free-plan tenant. Unset = invite-only: a brand-new email gets 403; an invited pending user can still complete signup.
STREAMHUB_SUPERADMIN_EMAIL info@streamhub.studio Email that becomes the superadmin principal the moment it signs in via magic-link.
AUTH_RATE_LIMIT_MAX 10 Max attempts per client IP per window on the sensitive auth routes only (login, magic-link, magic/verify, reset-request, reset). The rest of the API, including dashboard polling, is not limited.
AUTH_RATE_LIMIT_WINDOW_MS 900000 (15 min) Rate-limit window for the routes above.

Magic-link login, invites and password resets. Without STREAMHUB_SMTP_HOST and STREAMHUB_SMTP_PASS set, sends are skipped — logged, never crash the request.

Variable Default Description
STREAMHUB_SMTP_HOST mail.wipermax.online SMTP server. The built-in default is the vendor’s own mail host — set your own in production.
STREAMHUB_SMTP_PORT 587 587 → STARTTLS; 465 → implicit TLS.
STREAMHUB_SMTP_USER no-reply@streamhub.studio SMTP auth user.
STREAMHUB_SMTP_PASS (unset — required to actually send mail) SMTP password. Secret, never logged.
STREAMHUB_SMTP_FROM StreamHub <no-reply@streamhub.studio> From: header on outgoing mail.

Used when a box joins an existing control plane via POST /api/v1/cluster/join (install.sh --join). On a single-node install, leave this unset.

Variable Default Description
STREAMHUB_CLUSTER_TOKEN (unset = joining disabled) Shared secret an edge node must send as X-Cluster-Token to POST /cluster/join and /cluster/heartbeat. The /cluster/join endpoint returns 503 while this is unset. Must be long and random in production.

See also STREAMHUB_CLUSTER_REDIS_URL (Redis section above) and LIVEKIT_REDIS_ADDRESS / LIVEKIT_REDIS_PASSWORD / LIVEKIT_WEBHOOK_URL (LiveKit section above), which are the values an edge node actually receives from a successful join.

Variable Default Description
DATA_DIR /data (inside the containers) Where core (and egress) read/write data/streamhub.db, apps/<name>/{recordings,hls,snapshots,samples}, logs/, sdk/. Must be identical for core and egress — egress writes MP4s exactly where core looks to upload them to S3.
STREAMHUB_HOST_DATA_DIR ./data Host path bind-mounted to DATA_DIR in both the core and egress containers (Compose only).
SDK_DIR <DATA_DIR>/sdk Where core serves the /sdk/* browser SDK from.
STREAMHUB_SNAPSHOT_SOURCE (auto) Override for the on-demand snapshot capture source.
Variable Default Description
METRICS_TOKEN (unset = disabled) Guards GET /metrics. Default-deny: unset → 404 (never leaks). Set it and /metrics requires a matching Authorization: Bearer <token> (or ?token=).
METRICS_DEFAULT_METRICS on Set to off to disable the default process_* / nodejs_* Prometheus collectors.

LiveKit, ingress and egress also expose their own native Prometheus metrics on fixed host ports (not env-tunable) — see Ports & firewall.

Variable Default Description
LOG_MAX_BYTES 10485760 (10 MB) Rotating log file size cap.
LOG_MAX_FILES 10 Number of rotated log files kept (count cap).
LOG_RETENTION_DAYS 30 Retention window for operational logs: purges server_logs DB rows and rotated log files older than this. 0 disables age-based purging (the file count cap still applies). The purge runs ~1 minute after boot and every 6 hours.
Variable Default Description
TRANSCODING_HWACCEL auto Hardware-accel preference: auto (GPU when the node has one, else CPU) | gpu (force GPU, falls back to CPU if none) | cpu (always software). Can be overridden per app — see Per-app config.yaml.
GPU_DISABLE (unset) Set to true to force CPU-only and skip GPU detection entirely.

These are not read by core — they are Compose mem_limit/cpus keys applied directly to the egress and ingress containers, the hard caps that stop a runaway session from taking down the host.

Variable Default Description
EGRESS_MEM_LIMIT 2g RAM cap for the egress container. Egress drives a headless Chrome to composite rooms — it is the single biggest OOM risk on a small host.
EGRESS_CPUS 2 CPU cap for egress.
INGRESS_MEM_LIMIT 1g RAM cap for the ingress container.
INGRESS_CPUS 1.5 CPU cap for ingress.

See Limitations & capacity for measured RAM cost per recording and why the default EGRESS_MEM_LIMIT matters.

Consumed by deploy/backup.sh (cron / systemd timer), not by core itself.

Variable Default Description
BACKUP_DATA_DIR (unset) Host data dir to back up (streamhub.db + apps/<app>/app.db + secrets.json).
BACKUP_LOCAL_DIR <BACKUP_DATA_DIR>/backups Where local tarballs are kept.
BACKUP_RETENTION_DAYS 30 Prune local + remote copies older than this many days.
BACKUP_S3_BUCKET (empty = local-only) S3 (or S3-compatible) target for off-box copies.
BACKUP_S3_ENDPOINT (empty = AWS) Custom endpoint, e.g. https://s3.wasabisys.com.
BACKUP_S3_PREFIX streamhub-backups Key prefix inside the bucket.
BACKUP_S3_REGION us-east-1 Bucket region.
BACKUP_S3_ACCESS_KEY_ID / BACKUP_S3_SECRET_ACCESS_KEY (unset) Credentials for the backup target.
STREAMHUB_DOMAIN=streamhub.example.com
ACME_EMAIL=admin@example.com
LIVEKIT_API_KEY=API… # generate-keys
LIVEKIT_API_SECRET=… # long random
PUBLIC_WS_URL=wss://streamhub.example.com
RTMP_PUBLIC_HOST=streamhub.example.com
REDIS_URL=redis://127.0.0.1:6379
STREAMHUB_JWT_SECRET=… # long random
ADMIN_USER=admin
ADMIN_PASS=… # strong
STREAMHUB_API_TOKEN=sk_… # long random
STREAMHUB_AUTHZ_ENFORCE=on
DATA_DIR=/data
STREAMHUB_HOST_DATA_DIR=./data
LOG_LEVEL=info
NODE_ENV=production