Configuration reference
The file
Section titled “The file”The configuration is deliberately a plain, readable text file rather than a database. It can be backed up with cp, diffed against last week’s copy, committed to version control, and edited by hand over SSH when the web interface is unreachable — which is exactly when you most need to change something. There is no separate state store to keep in step with it.
Castmux rewrites the file whenever a change is made in the web interface, so hand edits and UI edits are the same operation on the same file. Editing by hand while the service is running risks having your change overwritten by the next UI action; stop the service, edit, start it again.
On upgrade, keys added by a newer release are backfilled with their defaults automatically, so an older file continues to work. A .pre-migration copy of the original is written before anything is changed, which is the file to restore if a migration turns out to disagree with you.
Command-line flags
Section titled “Command-line flags”—config
Path to the configuration file. Defaults to config.yaml in the working directory. Container deployments pass an explicit path into a mounted volume, normally /config/config.yaml.
—port
TCP port for the web interface and the HTTP API. Defaults to 8080. The API is not on a separate port; there is one listener.
-debug
Verbose logging, including full engine command lines and per-restart detail. Useful when diagnosing a channel that will not start. It is noisy; do not leave it on permanently on an appliance with a small log partition.
Environment variables
Section titled “Environment variables”CASTMUX_WEB_USERNAME
Username for the web interface and API.
CASTMUX_WEB_PASSWORD
Password for the web interface and API.
A complete annotated example
Section titled “A complete annotated example”A working file for a small site: two channels, one from HLS and one from SRT, both carried on one DVB-C multiplex as well as on multicast.
global: default_engine: tsduck # pass-through unless a channel overrides it udp_device: 192.168.10.4 # source address, NOT the egress interface startup_stagger_seconds: 3 hls_live_start_index: -1 on_demand_enabled: true on_demand_fail_open: true # viewer detection broken => keep streaming igmp_active_query: true igmp_grace_seconds: 90 igmp_linger_seconds: 30 igmp_query_interval_seconds: 30 igmp_interfaces: - eth0 - eth1 enable_dummy_listeners: true overwrite_srt_latency: true overwrite_pkt_size: true ping_hosts: - 192.168.10.1 # the site gateway - origin.example.net # the content provider location_name: Seaview Court re_seller: Northern AV ts_monitoring: true ts_pcr_jitter_max_ms: 200 licence: identity_dir: /config/identity # must be on a persistent volume site_ref: seaview-court
streams: - id: 1 name: Example One # also the DVB service name and metric label description: Main lounge line-up active: true source: type: hls url: https://origin.example.net/one/master.m3u8 destination: address: 239.10.0.1 port: 5000 force_always_on: false # on-demand: runs when somebody joins engine: tsduck epg_id: example-one.uk # the channel's id in the XMLTV guide resolution_profile: hd # closest rung wins; not an exact-match filter min_output_kbps: 500 pdt_shift_ms: 0 # 0 disables the PDT drift restart
- id: 2 name: Example Two description: Contribution feed from the studio active: true source: type: srt url: srt://feed.example.net:9001?streamid=two destination: address: 239.10.0.2 port: 5000 force_always_on: true # always on air, viewers or not engine: tsduck resolution_profile: "" # SRT has no ladder; nothing to select min_output_kbps: 500
dvb_muxes: - id: 1 name: Mux A device: 0 # modulator device index frequency: 474000000 # Hz, not MHz qam: 256 symbol_rate: 6900 network_id: 1 ts_id: 1 network_name: Seaview Court active: true channel_ids: [1, 2] # order decides slot, PID block, service IDGlobal settings
Section titled “Global settings”Everything under global applies to every channel unless the channel overrides it. A change made through the web interface is applied to running channels without a restart, so these are live settings, not boot-time ones.
| Key | Default | Purpose |
|---|---|---|
| Engines and output | ||
| default_engine | tsduck | Which engine channels use when they do not name one themselves. The pass-through engine preserves original PIDs, audio tracks and language descriptors; the remux engine rewrites them, which some downstream equipment requires. See Streaming engines. |
| udp_device | empty | Source address for outbound UDP. Note carefully that this binds the source address and does not choose the egress interface — the kernel still consults the routing table to decide which port the packets leave by. If multicast is going out of the wrong interface, fix the route, not this. |
| startup_stagger_seconds | 3 | Gap between channel starts at boot. Every channel opens its source at once otherwise, and eight simultaneous HLS starts on a 100 Mbit uplink saturate it during the initial segment burst, so all eight fail rather than one. Set 0 to disable if your uplink genuinely does not care. |
| hls_live_start_index | -1 | Which segment of the live playlist to begin at, counted from the live edge. -1 is the last segment. Closer to the edge means lower latency and less buffer to absorb origin jitter; further back is more tolerant and further behind live. |
| overwrite_pkt_size | true | Force full 1316-byte UDP datagrams on HLS channels — seven transport packets per datagram, which is what set-top boxes expect. Left alone, some sources produce short datagrams and waste a noticeable share of the wire. |
| overwrite_srt_latency | true | Derive the SRT latency setting from the measured round-trip time to the sender rather than using a fixed figure. A hand-set latency that was right for one path is usually wrong for the next site. |
| On-demand and IGMP | ||
| on_demand_enabled | true | Master switch for viewer-driven streaming. With it off, every channel marked active runs continuously regardless of whether anything is watching. |
| on_demand_fail_open | true | Keep channels streaming when viewer detection is unavailable — capture cannot start, the interface disappears, the watcher dies. Leave this on. The failure mode with it off is a building of black screens caused by a monitoring component, which is worse in every way than a little wasted bandwidth. |
| igmp_active_query | true | Send our own IGMP general queries. Clients report unsolicited only at the moment they join and then stay silent until asked, so without querying a restart loses all knowledge of existing viewers. Turn this off only if the segment already has a real querier; two queriers on one segment is untidy but not fatal. |
| igmp_grace_seconds | 90 | How long a viewer may stay silent before it is treated as gone. Must comfortably exceed igmp_query_interval_seconds, or a single lost report evicts a viewer who is still watching. |
| igmp_linger_seconds | 30 | How long a channel keeps running after the last viewer leaves. This is what makes channel surfing bearable: returning within the linger window is instant instead of paying a cold start. |
| igmp_query_interval_seconds | 30 | Interval between general queries. Lower means faster detection of a set that has been switched off at the wall, at the cost of more control traffic. |
| igmp_interfaces | all with an address | Which interfaces to capture membership on. The default covers every interface that has an address configured. Name them explicitly on a box with several NICs where only one faces the televisions. |
| enable_dummy_listeners | true | Keep a local consumer attached to each group. Without one, packets sent to a group nobody has joined accumulate against socket buffers and eventually cause overruns that look like source faults. |
| Monitoring and site identity | ||
| ts_monitoring | true | Measure each channel's transport-stream quality — bitrate and its spread, PCR timing, transport and continuity errors — for the per-channel graphs and the exported metrics. On a pass-through channel the measurement runs inside the channel's own pipeline; on a remux channel a second, read-only process listens to the channel's multicast group, so it measures what a receiver actually gets. Both only observe and never alter the stream. Set false to save the small per-channel CPU cost on a heavily loaded appliance. |
| ts_pcr_jitter_max_ms | 200 | How far, in milliseconds, a programme clock reference may be from the real timing of its packet before it counts as a PCR jitter event. This is a delivery-burst tolerance, not the MPEG-2 accuracy figure: SRT origins commonly interleave in bursts of around 160 ms, and a relay that repackages HLS delivers whole segments at once. 200 lets ordinary interleaving pass and flags the bursts a receiver's buffer would actually have to ride out. Raise it if a known-bursty origin floods the graph with events nobody can act on. |
| ping_hosts | empty list | Extra hosts for the reachability monitor, on top of what is derived from the configuration. The useful entries are the site gateway and the content origin: when a channel fails, the first question is whether the site lost the internet or the provider lost the channel, and this answers it. |
| location_name | empty | The site this appliance serves, as you would name it — shown as Location in Settings. It labels exported metrics so a fleet of appliances can be told apart in one dashboard, it is reported to the licence server as the site name, and it is the DVB-C network name set-top boxes display unless one is set explicitly. This key used to be called hotel_name; an older file is migrated automatically on load. |
| re_seller | empty | The partner or installer responsible for the site. Also a metric label, for filtering a dashboard down to one reseller's estate. |
Engine parameters
Section titled “Engine parameters”These keys hold raw argument strings handed to the underlying programs: FFmpeg for remux channels, TSDuck for pass-through channels, and mpv for the HDMI output. They are deliberately not in the web interface. They can be changed by editing the file or through PUT /api/settings, and saving any of them restarts every active channel.
The reason for keeping them out of the interface is blast radius. Each string applies to every channel using that engine. An engine that will not start because of one malformed argument takes the whole line-up off air at once, not one channel. A field that can do that to a building should not be one keystroke away from an operator who is trying to rename a channel.
| Key | Default | Purpose |
|---|---|---|
| ffmpeg_params | shipped value | Options placed at the very start of every remux channel's FFmpeg command line, ahead of the input — timestamp repair, probe size and buffering. The shipped value is tuned for HLS origins; its 15 MB probe size is the reason startup_stagger_seconds exists. If the key is empty, a minimal -fflags +genpts -rw_timeout 10000000 is used instead. |
| ffmpeg_udp_params | shipped value | Query string appended to every remux channel's udp:// output URL: TTL, datagram size, socket and FIFO buffers. Two parts are shared or overridden. The ttl here is also used by pass-through channels, so both engines put the same TTL on the wire. And with overwrite_pkt_size on, any pkt_size is removed from HLS channels, because the application sets the datagram size itself. |
| ffmpeg_preview_params | shipped value | Extra options for the browser preview's FFmpeg process, inserted before its input. They affect previews only, never what goes on the wire. |
| tsduck_params | empty | Extra tsp options and plugins, added to every pass-through channel after the built-in processing and before the quality measurement and the output. Two built-in steps give way to it: if it names the svrename or sdt plugin, the automatic DVB service naming is skipped rather than doubled; and if it picks an HLS rung itself (for example --lowest-bitrate), the default highest-bitrate selection is not added, because tsp refuses two selectors. |
| mpv_params | shipped value | Extra options for the HDMI output's player, added after the display and audio choices made on the HDMI page. mpv honours the last occurrence of an option, so a flag here overrides the matching dropdown. The shipped value names a specific display context and audio card; on hardware other than the one it was written for, remove those flags and use the HDMI page's selections instead. |
HDMI output
Section titled “HDMI output”The hdmi_stream block under global configures the HDMI output. Everything in it is set from the HDMI page.
| Key | Default | Purpose |
|---|---|---|
| hdmi_stream.name | empty | Label for the output in the status display and the logs. |
| hdmi_stream.stream_id | none | The id of the channel to play. The player joins that channel's multicast group, using the udp_device address if one is set. 0, or the id of a deleted channel, leaves the output with nothing to play, and the log says which. |
| hdmi_stream.active | false | Starts and stops playback. |
| hdmi_stream.vo | player default | The player's video output, such as gpu, gpu-next or drm. The two gpu outputs are given a DRM/KMS context automatically, since an appliance has no desktop to draw into. |
| hdmi_stream.drm_device | player default | The display device, as a path such as /dev/dri/card0. Needed on hardware with more than one display controller, where the default can be the wrong connector. |
| hdmi_stream.audio_device | player default | The audio output device, or none for a silent picture. Audio device names differ from one board to the next, so a value copied from another appliance is the usual reason the player will not start. |
Licence
Section titled “Licence”The licence block under global configures how the appliance identifies itself to the licence server. The server’s address and signing key are built into the release and are not settings. server_url or token_public_key in this block are accepted but ignored, and a warning is logged at startup saying so.
| Key | Default | Purpose |
|---|---|---|
| licence.identity_dir | /config/identity | Where the appliance keeps its device key, certificate and last licence state. This must be on persistent storage. Without it, every container re-creation generates a new identity, which appears to the licence server as a new, unapproved device, and streaming stays blocked until it is approved again. |
| licence.enroll_key | empty | Optional pre-shared enrolment key, supplied with a batch of appliances. Treat it like a password: it is never written to the logs, but it is readable by anyone who can read this file. |
| licence.site_ref | empty | A free-text reference, such as an order or site number, sent at enrolment to help recognise the appliance when approving it. It is a hint for people, not a credential. |
| licence.agent_version | the running version | The software version reported to the licence server. Leave it unset. |
Per-channel settings
Section titled “Per-channel settings”Each entry under streams is one channel: one source, one destination, one set of supervision rules.
| Key | Default | Purpose |
|---|---|---|
| Identity | ||
| id | assigned | Numeric identifier, unique within the file. It is the identifier used in API paths and in channel_ids on a multiplex. Identifiers are reused after a delete, so do not treat one as a permanent handle for an external system. |
| name | required | Display name. This is not cosmetic: it also becomes the DVB service name shown in the television's channel list, and the label attached to this channel's exported metrics. Renaming a channel changes what viewers see and what your dashboard groups by. |
| description | empty | Free text for the operator. Not transmitted anywhere. |
| epg_id | empty | This channel's identifier in the XMLTV programme guide — the guide's own channel id, typically something like rtl.de. It is set on the EPG page, and it is what makes a guide sync store programmes for this channel. Empty means no guide data for the channel. See Programme guide (EPG). |
| managed_by | empty | Who owns the channel. Empty means an operator added it, and it is never changed by anything else. licence means the licence server supplied it: it may replace the channel's source and destination on each refresh, so the web interface will not edit or delete it. Do not set licence by hand on a channel you created — the next refresh may overwrite it. |
| active | true | Whether the channel is configured to run at all. An inactive channel is kept in the file but never started, which is how you retire a channel without losing its settings. |
| Source | ||
| source.type | hls | One of hls, srt, rtmp or mpegts. mpegts is a plain transport stream, over UDP (udp://group:port) or HTTP (http(s)://…) — see MPEG-TS sources. The type governs more than the fetch: only HLS has a ladder, so resolution profiles apply to HLS alone. |
| source.url | required | The origin URL. For HLS this should normally be the master playlist, not a variant — give Castmux the ladder and let the resolution profile choose, so the channel survives the origin reorganising its rungs. |
| Destination | ||
| destination.address | required | IPv4 destination. Use the administratively scoped multicast range 239.0.0.0/8 for distribution inside a building. Unicast is accepted for point-to-point links. |
| destination.port | required | UDP destination port. Distinct groups may share a port; the same group and port used by two channels is a duplicate sender and produces unwatchable pictures on both. |
| Behaviour | ||
| force_always_on | false | Run this channel continuously, ignoring viewer detection. Use it for a channel that must be instantly available, or one carried on a DVB-C multiplex where there is no IGMP membership to observe in the first place. |
| engine | inherits | Override default_engine for this channel. The usual reason is a source the pass-through engine cannot read, or one downstream device that insists on remuxed output. |
| resolution_profile | empty | One of 4k, fullhd, hd, sd, mobile, or empty for the origin's own choice. Selection is closest height wins, ties going to the higher bitrate — a channel that streams 480p when nobody publishes 576p is better than a channel that refuses to start. The choice is applied per start and never written back to this file. See Resolution profiles. |
| Supervision — configuration file only | ||
| min_output_kbps | 500 | The floor below which this channel is considered not to be producing, and is restarted. Lower it for radio and other audio-only services: a 128 kbps radio channel measured against a video-sized threshold is permanently judged silent and restarted in a loop. Not exposed in the web interface. |
| pdt_shift_ms | 0 | Restart threshold for programme-date-time drift, in milliseconds: if the source's declared timeline jumps by more than this, the channel is restarted rather than left to run on a broken clock. 0 disables the check, which is the default because a legitimately re-stamping origin would otherwise cause restarts. Not exposed in the web interface. |
DVB-C multiplex settings
Section titled “DVB-C multiplex settings”Each entry under dvb_muxes is one RF carrier: a set of channels assembled into a single transport stream and modulated onto the coaxial network. See DVB-C multiplexing for how to choose the RF parameters.
| Key | Value | Purpose |
|---|---|---|
| Identity | ||
| id | assigned | Numeric identifier for the multiplex, used in API paths. |
| name | required | Operator-facing label for the multiplex. Not transmitted. |
| device | index or path | Which modulator hardware this multiplex is transmitted on, identified by index or device path depending on the card fitted. One multiplex per device. |
| RF parameters | ||
| frequency | Hz | Centre frequency of the carrier, in hertz, not megahertz. 474000000, not 474. Must match the plan for the building's existing cable network and must not collide with a carrier already present on it. |
| qam | 16 / 32 / 64 / 128 / 256 | Modulation order. Higher carries more bitrate in the same 8 MHz and tolerates less noise. On healthy in-building coax 256-QAM is normal; drop it if the cabling is old, heavily split or of unknown quality. |
| symbol_rate | ksym/s | Symbol rate of the carrier. With the modulation order this fixes the total bitrate available to every service in the multiplex, so it is the number that decides how many channels fit. |
| Signalling | ||
| network_id | integer | Network identifier signalled to receivers. Keep it consistent across every multiplex at one site, so sets treat them as one network rather than several. |
| ts_id | integer | Transport stream identifier. Must be unique among the multiplexes at the site; duplicates cause sets to overwrite one line-up with another during a scan. |
| network_name | text | Network name signalled to receivers. This is the text a guest sees during tuning, so it is normally the name of the building. |
| active | true | Whether the multiplex is transmitted. Setting it false stops the carrier; televisions will show the whole line-up as missing, not as one dead channel. |
| Contents | ||
| channel_ids | ordered list | The channels carried, by id. The order is significant. Position in this list determines the slot, the PID block allocated to the service, and the service identifier. Reordering the list therefore re-identifies services, and televisions that have already scanned may need a re-tune to agree with you again. Add to the end where you can. |
Backing up and restoring
Section titled “Backing up and restoring”Back up the whole configuration directory, not only config.yaml. As well as the configuration, it holds the appliance’s licence identity and its programme guide data:
| In the directory | What it is |
|---|---|
| config.yaml | The configuration described on this page. |
| config.yaml.pre-migration | The file as it was before the last upgrade changed it. The one to restore if a migration disagrees with you. |
| identity/ | The appliance's device key and certificate for the licence server. Lose it and the restored appliance appears to the licence server as a new, unapproved device, and does not stream until it is approved again. |
| epg.db | Stored programme guide data. It can be rebuilt by syncing the guide sources again, so it is convenient to keep rather than essential. |
The paths depend on which side of the container you are on. With the installation described in this manual, the directory is /opt/castmux/config on the host, and the container sees it as /config. So the configuration file is /opt/castmux/config/config.yaml from a shell on the appliance, and /config/config.yaml inside the container. Backups are taken from the host:
# on the appliance (host), as root: the files are owned by rootsudo cp -a /opt/castmux/config /backup/seaview-court-$(date +%F)Copying while the service runs is normally fine, because config.yaml is only rewritten when a setting is saved. Do not save changes in the web interface while the copy runs. For a copy that is guaranteed consistent, including epg.db in the middle of a guide sync, stop the container first.
To restore, stop the container, put the directory back and start it again. Replacing files under a running container does not work: the running service writes its own copy of config.yaml over yours at the next change.
# on the appliance (host)sudo docker stop castmuxsudo rm -rf /opt/castmux/configsudo cp -a /backup/seaview-court-2026-04-11 /opt/castmux/configsudo docker start castmuxWith Docker Compose, use sudo docker compose stop and sudo docker compose start in /opt/castmux instead. If you bind-mounted a different host directory, substitute it for /opt/castmux/config throughout.
Because the file is plain YAML, a backup’s config.yaml also serves as the starting point for a second site: change the addresses, the names and the RF parameters, and the rest carries over. Copy only config.yaml for this, never identity/. Two appliances with the same identity are the same device as far as the licence server is concerned. That is a manual process today.
One-click export and import from the web interface, and cloning a configuration across a fleet of sites, are roadmap.