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:
- Go to Dashboard > Plugins > OpenWatchParty
- Set Session Server URL to your custom URL:
wss://session.example.com/ws - 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
- Security - Security hardening
- Deployment - Production deployment
- Troubleshooting - Common issues