Configuration Guide

For most installations, use owpctl configure or the plugin health dashboard. Directly editing both sides is an advanced fallback because authentication parameters must remain synchronized.

Plugin Configuration

Access the plugin configuration page at Dashboard > Plugins > OpenWatchParty.

Settings

Setting Default Description
JWT Secret (empty) Secret key for signing tokens. Required unless insecure development is explicitly enabled.
Allow insecure unauthenticated development disabled Explicit local-development override; never enable in production.
JWT Audience OpenWatchParty Audience claim in generated tokens
JWT Issuer Jellyfin Issuer claim in generated tokens
Token TTL 3600 Token lifetime in seconds (1 hour default)
Invite TTL 3600 Invite link lifetime in seconds
Session Server URL (empty) Absolute ws:// or wss:// URL. Empty requires explicit trust of same-host port 3000 auto-detection.
Trust automatic session server disabled Allows tokens to be sent to ws(s)://[host]:3000/ws when URL is empty.

JWT Secret Guidelines

For production use:

  • Base64/Base64URL generated from at least 32 random bytes
  • Use a cryptographically random string
  • Never reuse secrets across environments

Generate a secure secret:

openssl rand -base64 32

Session Server Configuration

Environment Variables

Variable Default Description
PORT 3000 Port to listen on
HOST 0.0.0.0 Address to bind to
ALLOWED_ORIGINS http://localhost:8096,https://localhost:8096 CORS allowed origins (comma-separated)
JWT_SECRET required Secret for validating tokens
JWT_AUTH_MODE hs256 hs256, hybrid, or asymmetric; new owpctl installs pair then use asymmetric mode
JWT_TRUST_STORE_PATH /var/lib/openwatchparty/trust-store.json Versioned public-key trust store managed by owpctl
ALLOW_INSECURE_NO_AUTH false Explicit development-only override when JWT_SECRET is empty
AUTH_TIMEOUT_SECONDS 10 Time allowed to authenticate a JWT WebSocket connection; disabled in insecure mode
MAX_CONNECTIONS 256 Maximum concurrent WebSocket connections
MAX_CONNECTIONS_PER_IP 32 Maximum concurrent WebSocket connections per effective client IP
TRUSTED_PROXIES (empty) Comma-separated proxy IPs/CIDRs allowed to supply X-Forwarded-For
LOG_LEVEL info Log level: error, warn, info, debug, trace

Docker Compose Example

services:
  session-server:
    image: owp-session-server
    ports:
      - "3000:3000"
    environment:
      - ALLOWED_ORIGINS=https://jellyfin.example.com
      - JWT_SECRET=${JWT_SECRET}
      - ALLOW_INSECURE_NO_AUTH=false
      - LOG_LEVEL=info
    restart: unless-stopped

CORS Configuration

For security, specify allowed origins instead of using wildcard (*):

# Single origin
ALLOWED_ORIGINS=https://jellyfin.example.com

# Multiple origins
ALLOWED_ORIGINS=https://jellyfin.example.com,http://localhost:8096

# Development (not for production!)
ALLOWED_ORIGINS=*

Warning: Using * for ALLOWED_ORIGINS logs a security warning and is not recommended for production.

Reverse Proxy Trust

The server ignores X-Forwarded-For unless the direct peer address belongs to TRUSTED_PROXIES. Configure only reverse proxies you operate, using exact addresses or narrow CIDRs. When trusted, the first valid address in X-Forwarded-For is used for the per-IP connection limit; otherwise the direct peer IP is used.

TRUSTED_PROXIES=127.0.0.1/32,10.20.0.0/16

WebSocket messages and individual frames are capped at 64 KiB by Warp before message assembly. Connections exceeding either concurrent connection limit receive HTTP 429; clients exceeding the application message rate or the JWT authentication deadline are closed with WebSocket policy code 1008.

Client Configuration

The client gets its configuration from the plugin. Most settings are automatic, but you can customize the WebSocket URL.

Custom WebSocket URL

If the session server is on a different host or port:

  1. Go to Dashboard > Plugins > OpenWatchParty
  2. Set Session Server URL to your custom URL:
    wss://session.example.com/ws
    
  3. Save and refresh

URL Format

The value is normalized when saved and must be an absolute ws:// or wss:// URL with a hostname. Sub-paths and IPv6 hosts are supported. Credentials (user:password@), query strings, and fragments are rejected. An HTTPS Jellyfin page requires wss://; browsers must never downgrade it to ws:// mixed content.

Saving a destination whose hostname or effective port differs from the Jellyfin page requires explicit confirmation. The client repeats these checks on the Token response and immediately before constructing the WebSocket, so invalid or tampered configuration fails closed.

Scheme When to Use
ws:// HTTP/unencrypted (development only)
wss:// HTTPS/encrypted (production)

Advanced Configuration

Sync Tuning

The client has built-in constants that control synchronization behavior. These are not configurable at runtime but can be modified in the source code:

Constant Default Description
SUPPRESS_MS 2000 Anti-feedback lock duration (ms)
SEEK_THRESHOLD 1.0 Position difference to trigger seek (s)
STATE_UPDATE_MS 1000 State update interval (ms)
SYNC_LEAD_MS 300 Latency compensation (ms)
DRIFT_DEADZONE_SEC 0.04 No-correction zone (s)
DRIFT_SOFT_MAX_SEC 2.0 Forced seek threshold (s)
PLAYBACK_RATE_MIN 0.85 Minimum catchup speed
PLAYBACK_RATE_MAX 2.0 Maximum catchup speed
DRIFT_GAIN 0.5 Speed adjustment gain

Server Tuning

Server constants in src/server/src/ws/constants.rs:

Constant Default Description
PLAY_SCHEDULE_MS 1000 Delay before play broadcast (ms)
CONTROL_SCHEDULE_MS 300 Delay before pause/seek broadcast (ms)
MAX_READY_WAIT_MS 2000 Max wait for ready clients (ms)
MIN_STATE_UPDATE_INTERVAL_MS 500 Min state update interval (ms)
POSITION_JITTER_THRESHOLD 0.5 HLS noise filter (s)
COMMAND_COOLDOWN_MS 2000 Cooldown after commands (ms)
MAX_MESSAGE_SIZE 65536 Max message size (bytes)

Configuration Examples

Minimal Setup (Development)

# docker-compose.yml
services:
  session-server:
    image: owp-session-server
    ports:
      - "3000:3000"
    environment:
      - ALLOW_INSECURE_NO_AUTH=true

Plugin settings:

  • JWT Secret: (empty)
  • Allow insecure unauthenticated development: enabled explicitly
  • Session Server URL: (empty)
  • Trust automatic same-host port 3000 session server: enabled explicitly

Existing installations with an empty plugin secret are intentionally blocked after upgrading. Configure the same secret in the plugin and session server, or explicitly opt into insecure mode for local development only.

Production Setup

# docker-compose.yml
services:
  session-server:
    image: owp-session-server
    ports:
      - "127.0.0.1:3000:3000"  # Only localhost
    environment:
      - ALLOWED_ORIGINS=https://jellyfin.example.com
      - JWT_SECRET=${JWT_SECRET}
      - LOG_LEVEL=warn
    restart: unless-stopped

Plugin settings:

  • JWT Secret: your-secure-32-char-secret
  • Session Server URL: wss://jellyfin.example.com/ws (via reverse proxy)

Multi-Instance Setup

For high availability or multiple Jellyfin instances:

services:
  session-server:
    image: owp-session-server
    deploy:
      replicas: 1  # Single instance (stateful)
    environment:
      - ALLOWED_ORIGINS=https://jellyfin1.example.com,https://jellyfin2.example.com

Note: The session server is stateful (in-memory rooms), so only one instance should run. For scaling, consider adding persistence (planned feature).

Validating Configuration

Check Plugin Config

curl -H "Authorization: MediaBrowser Token=\"YOUR_API_KEY\"" \
  "http://localhost:8096/System/Configuration/Plugin/0f2fd0fd-09ff-4f49-9f1c-4a8f421a4b7d"

Check Server Health

curl http://localhost:3000/health

Test JWT Token Generation

curl -H "Authorization: MediaBrowser Token=\"YOUR_API_KEY\"" \
  "http://localhost:8096/OpenWatchParty/Token"

Expected response:

{
  "token": "eyJ...",
  "auth_enabled": true,
  "expires_in": 3600
}

Next Steps


Back to top

OpenWatchParty - Synchronized watch parties for Jellyfin