Complete configuration
Start from a maximal module composition or inspect every field accepted by the current binary.
How to use this page
The maximal composition is a practical deployment template. It enables every module that can run together and leaves configured outbound push targets empty because cascade input and static push-all targets cannot share one node yet.
The field reference below names every accepted configuration field. Commented alternatives are mutually exclusive with the active line beside them. Remove modules and optional values that the deployment does not need.
Private alpha: match this page to the binary version you deploy. Unknown fields fail startup, and configuration fields may still change before a stable release.
Maximal composition
This template combines SRT push, managed pull, demand-driven cascade input, HLS, LL-HLS, WebRTC, local thumbnails, recording, S3 publication, cascade output, push control, transcode repair and webhooks. Set every referenced environment value and provide the external services before starting the process.
{
"server": {
"operational": {"host": "0.0.0.0", "port": 9091},
"shutdownTimeout": "10s"
},
"input": {
"mediaTimeout": "15s",
"srt": {
"push": {
"enabled": true,
"listener": {
"host": "0.0.0.0",
"port": 6001,
"passphrase": "${INGEST_SRT_PASSPHRASE}"
},
"peerIdleTimeout": "5s"
},
"pull": {
"api": {"enabled": true},
"static": [{
"streamId": "preview",
"source": "source.internal:7001",
"srtStreamId": "preview",
"passphrase": "${SOURCE_SRT_PASSPHRASE}"
}],
"maxActivePulls": 8,
"maxTombstones": 100
},
"cascade": {
"enabled": true,
"staticEndpoint": "upstream.internal:6102",
"passphrase": "${CASCADE_UPSTREAM_PASSPHRASE}",
"viewerIdleTimeout": "1m",
"maxOnDemandStreams": 64
}
}
},
"output": {
"root": "/var/lib/dispergo",
"lowWaterMarkBytes": 5368709120,
"http": {
"listener": {
"host": "0.0.0.0",
"port": 8080,
"maxConnections": 2048
},
"hls": {"enabled": true, "segmentDuration": "6s"},
"llhls": {"enabled": true, "windowDuration": "5m"},
"webrtc": {
"enabled": true,
"network": {
"udpPort": 50000,
"tcpPort": 50001,
"ipFamilies": ["ipv4"],
"candidateMode": "direct"
},
"security": {
"mode": "bearer",
"credentials": [{
"principal": "browser-viewers",
"token": "${WHEP_TOKEN}",
"streams": ["*"]
}],
"allowedOrigins": ["https://player.example.com"]
},
"ice": {
"urls": ["turns:turn.example.com:5349?transport=tcp"],
"turnSharedSecret": "${TURN_SHARED_SECRET}",
"internetReady": true
},
"timeouts": {"bootstrap": "2s"},
"admission": {
"maxSessions": 256,
"maxSessionsPerStream": 64,
"maxBootstrap": 64,
"maxBootstrapPerStream": 16
},
"rates": {
"bootstrapGlobal": {"perSecond": 64, "burst": 128},
"bootstrapScoped": {"perSecond": 4, "burst": 8}
},
"tracing": {"sampleRate": 0.005},
"audio": {"ffmpegPath": "ffmpeg", "maxWorkers": 4}
}
},
"srt": {
"cascade": {
"enabled": true,
"listener": {
"host": "0.0.0.0",
"port": 6002,
"passphrase": "${CASCADE_DOWNSTREAM_PASSPHRASE}"
},
"registry": {
"redisAddress": "redis.internal:6379",
"advertiseEndpoint": "node-a.internal:6002",
"keyPrefix": "dispergo:provide:"
}
},
"push": {
"api": {"enabled": true},
"maxActivePushes": 8,
"maxTombstones": 100
}
},
"thumbnails": {
"enabled": true,
"publish": true,
"interval": "10s",
"resize": {"width": 400, "height": 225},
"sessionMaxBytes": 1073741824,
"sessionMaxArtifacts": 10000,
"retention": "0s",
"ffmpegPath": "ffmpeg",
"maxWorkers": 4
},
"recording": {
"enabled": true,
"chunkDuration": "6s",
"retention": "0s",
"publish": true
},
"s3": {
"enabled": true,
"bucket": "${S3_BUCKET}",
"prefix": "media",
"region": "${AWS_REGION}",
"maxConcurrentUploads": 64,
"maxConcurrentThumbnailUploads": 8
}
},
"transcode": {
"enabled": true,
"encoderProfile": "cpu",
"ffmpegPath": "ffmpeg",
"maxWorkers": 4,
"queueSize": 0,
"policy": {
"defaultMode": "auto",
"streams": {"tenant:legacy": "always"}
}
},
"webhook": {
"enabled": true,
"url": "${WEBHOOK_URL}",
"authToken": "${WEBHOOK_TOKEN}",
"requestTimeout": "5s",
"maxAttempts": 5,
"queueSize": 256,
"maxConcurrentDeliveries": 4,
"shutdownFlushTimeout": "2s"
}
}
All fields
This JSONC document makes every compatible field explicit. Values match safe example bounds or current defaults. The commented alternatives name fields that cannot be active beside the selected form. The full output.s3 object is also accepted as the legacy output.recording.s3 alias, but the two paths cannot coexist. That alias is recording-only and cannot enable thumbnail publication.
{
"server": {
"operational": {
"host": "0.0.0.0",
"port": 9091
},
"shutdownTimeout": "10s"
},
"input": {
"mediaTimeout": "15s",
"srt": {
"push": {
"enabled": true,
"listener": {
"host": "0.0.0.0",
"port": 6001,
"passphrase": "${INGEST_SRT_PASSPHRASE}"
// Alternative after removing passphrase:
// "allowAnonymous": true
},
"peerIdleTimeout": "5s"
},
"pull": {
"defaults": {
"connectionTimeout": "5s",
"backoffBase": "1s",
"backoffMultiplier": 2,
"backoffCap": "1m",
"retryDeadline": "5m"
},
"api": {
"enabled": true
},
"static": [{
"streamId": "preview",
"source": "source.internal:7001",
"srtStreamId": "preview",
"passphrase": "${SOURCE_SRT_PASSPHRASE}",
"connectionTimeout": "5s",
"backoffBase": "2s",
"backoffMultiplier": 2,
"backoffCap": "1m",
"retryDeadline": "5m"
}],
"maxActivePulls": 8,
"maxTombstones": 100
},
"cascade": {
"enabled": true,
"staticEndpoint": "upstream.internal:6102",
"passphrase": "${CASCADE_UPSTREAM_PASSPHRASE}",
"viewerIdleTimeout": "1m",
"maxOnDemandStreams": 64
// Alternative to staticEndpoint:
// "registry": {
// "redisAddress": "redis.internal:6379",
// "keyPrefix": "dispergo:resolve:"
// }
}
}
},
"output": {
"root": "/var/lib/dispergo",
"lowWaterMarkBytes": 5368709120,
"http": {
"listener": {
"host": "0.0.0.0",
"port": 8080,
"maxConnections": 2048
},
"hls": {
"enabled": true,
"segmentDuration": "6s",
"maxSegmentBytes": 67108864,
"maxSegments": 7,
"minSegments": 3,
"maxTotalBytes": 1073741824,
"bandwidth": 10000000
},
"llhls": {
"enabled": true,
"segmentTargetDuration": "1s",
"segmentHardDuration": "2s",
"partTargetDuration": "200ms",
"partHardDuration": "400ms",
"partPublishedTargetDuration": "340ms",
"partHoldBack": "680ms",
"windowDuration": "5m",
"maxSegments": 300,
"minSegments": 3,
"partWindowDuration": "15s",
"maxSegmentBytes": 67108864,
"maxPartBytes": 8388608,
"maxTotalBytes": 1073741824,
"bandwidth": 10000000,
"blockingReloadMaxWait": "8s",
"initialPlaylistMaxWait": "8s"
},
"webrtc": {
"enabled": true,
"network": {
"udpPort": 50000,
"tcpPort": 50001,
"udpBindIPs": ["10.0.0.10"],
"tcpBindIPs": ["10.0.0.10"],
"ipFamilies": ["ipv4"],
"interfaces": ["eth0"],
"localCIDRs": ["10.0.0.0/24"],
"candidateMode": "rewrite",
"addressRewrites": [{
"localCIDR": "10.0.0.0/24",
"externalIP": "198.51.100.10"
}],
"disconnectedTimeout": "5s",
"failedTimeout": "25s",
"keepAliveInterval": "5s",
"candidateGatherTimeout": "3s",
"tcpFirstStunTimeout": "5s",
"tcpUnknownUfragTimeout": "30s",
"viewerDrainTimeout": "5s",
"certificateRotationLead": "24h"
},
"security": {
"mode": "bearer",
"credentials": [{
"principal": "browser-viewers",
"token": "${WHEP_TOKEN}",
"streams": ["*"]
}],
"allowedOrigins": ["https://player.example.com"],
"trustedProxyCIDRs": ["10.0.0.0/8"]
},
"timeouts": {
"setup": "10s",
"bootstrap": "2s",
"body": "2s",
"patch": "2s",
"delete": "2s",
"idle": "30s",
"maxLifetime": "6h"
},
"admission": {
"maxSessions": 256,
"maxSessionsPerStream": 64,
"maxSessionsPerPrincipal": 32,
"maxSessionsPerAnonymous": 8,
"maxHalfOpen": 64,
"maxHalfOpenPerStream": 16,
"maxHalfOpenPerPrincipal": 8,
"maxHalfOpenPerAnonymous": 4,
"maxBootstrap": 64,
"maxBootstrapPerStream": 16,
"maxBootstrapPerPrincipal": 8,
"maxBootstrapPerAnonymous": 4
},
"rates": {
"postGlobal": {"perSecond": 64, "burst": 128},
"postScoped": {"perSecond": 4, "burst": 8},
"bootstrapGlobal": {"perSecond": 64, "burst": 128},
"bootstrapScoped": {"perSecond": 4, "burst": 8},
"patchGlobal": {"perSecond": 512, "burst": 1024},
"patchScoped": {"perSecond": 40, "burst": 80},
"patchSession": {"perSecond": 10, "burst": 20},
"deleteGlobal": {"perSecond": 256, "burst": 512},
"deleteScoped": {"perSecond": 20, "burst": 40},
"maxKeyedBuckets": 4096,
"bucketIdle": "10m"
},
"ice": {
"urls": ["turns:turn.example.com:5349?transport=tcp"],
"turnSharedSecret": "${TURN_SHARED_SECRET}",
"credentialTTL": "7h",
"allowedClockSkew": "30s",
"internetReady": true
},
"audio": {
"ffmpegPath": "ffmpeg",
"maxWorkers": 4,
"maxStartups": 2,
"gracePeriod": "10s",
"startupTimeout": "5s",
"restartBudget": 3,
"restartWindow": "1m",
"backoffBase": "250ms",
"backoffCap": "5s",
"maxSubscribers": 256,
"subscriberFrames": 8,
"inputQueueSamples": 8,
"inputQueueBytes": 262144,
"stallTimeout": "5s"
},
"tracing": {
"sampleRate": 0.005
}
}
},
"srt": {
"cascade": {
"enabled": true,
"listener": {
"host": "0.0.0.0",
"port": 6002,
"passphrase": "${CASCADE_DOWNSTREAM_PASSPHRASE}"
// Alternative after removing passphrase:
// "allowAnonymous": true
},
"registry": {
"redisAddress": "redis.internal:6379",
"advertiseEndpoint": "node-a.internal:6002",
"keyPrefix": "dispergo:provide:"
}
},
"push": {
"defaults": {
"connectionTimeout": "5s",
"backoffBase": "1s",
"backoffMultiplier": 2,
"backoffCap": "1m",
"retryDeadline": "5m",
"payloadSize": 1316,
"peerLatency": "120ms",
"receiverLatency": "120ms",
"sendBufferSize": 1048576,
"flowControlWindow": 8192
},
"api": {
"enabled": true
},
"static": [
// Remove input.srt.cascade before enabling this target.
// {
// "name": "archive",
// "address": "archive.internal:7001",
// "srtStreamId": "{streamId}",
// "passphrase": "${ARCHIVE_SRT_PASSPHRASE}",
// "connectionTimeout": "5s",
// "backoffBase": "2s",
// "backoffMultiplier": 2,
// "backoffCap": "1m",
// "retryDeadline": "5m",
// "payloadSize": 1316,
// "peerLatency": "120ms",
// "receiverLatency": "120ms",
// "sendBufferSize": 1048576,
// "flowControlWindow": 8192
// }
],
"maxActivePushes": 8,
"maxTombstones": 100
}
},
"thumbnails": {
"enabled": true,
"publish": true,
"interval": "10s",
"resize": {
"width": 400,
"height": 225
},
"sessionMaxBytes": 1073741824,
"sessionMaxArtifacts": 10000,
"retention": "0s",
"ffmpegPath": "ffmpeg",
"maxWorkers": 4
},
"recording": {
"enabled": true,
"chunkDuration": "6s",
"maxChunkBytes": 67108864,
"sessionMaxBytes": 10737418240,
"sessionMaxArtifacts": 10000,
"retention": "0s",
"publish": true
},
"s3": {
"enabled": true,
"bucket": "${S3_BUCKET}",
"prefix": "media",
"region": "${AWS_REGION}",
"endpoint": "https://s3.example.net",
"forcePathStyle": false,
"requestTimeout": "2m",
"multipartAttemptTimeout": "5m",
"multipartThresholdBytes": 134217728,
"multipartPartSizeBytes": 16777216,
"maxConcurrentUploads": 64,
"maxConcurrentThumbnailUploads": 8
}
},
"transcode": {
"enabled": true,
"encoderProfile": "cpu",
"ffmpegPath": "ffmpeg",
"gracePeriod": "10s",
"maxWorkers": 4,
"queueSize": 0,
"stallTimeout": "5s",
"noMediaTimeout": "8s",
"maxRuntime": "12h",
"restartBudget": 5,
"restartWindow": "5m",
"backoffBase": "1s",
"backoffCap": "1m",
"sourceBufferBytes": 262144,
"maxAddressSpaceBytes": 2147483648,
"maxOpenFiles": 256,
"policy": {
"defaultMode": "auto",
"streams": {
"tenant:legacy": "always",
"tenant:trusted": "never"
}
}
},
"webhook": {
"enabled": true,
"url": "${WEBHOOK_URL}",
"authToken": "${WEBHOOK_TOKEN}",
"requestTimeout": "5s",
"maxAttempts": 5,
"queueSize": 256,
"maxConcurrentDeliveries": 4,
"shutdownFlushTimeout": "2s"
}
}
Composition constraints
- Use either
passphraseorallowAnonymouson one SRT listener, never both. - Use either
input.srt.cascade.staticEndpointor itsregistry, never both. - Give cascade input and output registries distinct
keyPrefixvalues when both run in one process. - Do not combine cascade input with configured
output.srt.push.statictargets. The push API remains available. - Keep
transcode.stallTimeoutandtranscode.noMediaTimeoutbelowinput.mediaTimeout. - Enabling
output.s3requires at least one ofoutput.recording.publishoroutput.thumbnails.publish. Both flags default tofalseand require their own module plus the shared S3 destination to be enabled. S3 does not require recording when thumbnails publish. - Do not combine
output.s3with the legacyoutput.recording.s3object. An enabled legacy alias implies recording publication only; it does not enable thumbnail publication. - Thumbnail publication requires local push or managed pull input. Cascade-only publication is a startup error; combining local and cascade inputs is valid.
- Enabling HLS, LL-HLS, recording or thumbnail output requires
output.root. WebRTC does not useoutput.root. - The viewer-facing
output.http.listeneris required for WebRTC and every storage-backed HTTP output. It remains required even for thumbnail-only operation. - WebRTC requires
network.udpPort. Its UDP and optional TCP muxes must not overlap HTTP, operational, SRT ingest or cascade-output listeners on a shared bind address. - Anonymous WHEP is a closed-network mode. It cannot contain bearer credentials or TURN routes. Use bearer mode and explicit
allowedOriginswhen untrusted browser clients can reach the listener. - A TURN URL requires
turnSharedSecret.internetReadyalso requires aturns:route, andcredentialTTLmust outlive bootstrap, setup, maximum session lifetime and allowed clock skew.