Appearance
Configuration reference
Every setting Quikcast accepts, in the order you will meet them in a real file. Each entry tells you what the setting does, its type, its default, the values it accepts, and whether a SIGHUP reload applies it or it needs a restart.
New to Quikcast? Start with the quickstart for a working local setup, then come back here when you need to know exactly what a line does. A copy-ready file with every option commented lives at config/quikcast.reference.toml in the repository; config/quikcast.example.toml is the short starting point.
How to read this page
Each setting is introduced like this:
Type
integer· Default4096· Range1–65,536· Applies restart
| Field | Meaning |
|---|---|
| Type | The TOML type: string, integer, boolean, array of strings, table, or array of tables. |
| Default | The value used when the key is omitted. "none" means the feature is off or the key is required. |
| Range / Values | What is accepted. Anything else stops startup with a diagnostic that names the key. |
| Applies | reload means kill -HUP applies it to new work without dropping connections. restart means the process must be restarted. |
Rules that apply to the whole file
- Unknown keys are rejected. A typo such as
listner_limitstops startup instead of being ignored. - Root keys come first.
listen,admin_listenandlog_filtermust appear before the first[table]header, or TOML assigns them to that table. - Paths are relative to the config file. A
*_filevalue such assecrets/radio-sourceresolves beside the configuration file, not the working directory. Absolute paths stay absolute.~and$VARIABLESare not expanded. - Size limit. The configuration file itself may be at most 128 KiB.
- A failed reload changes nothing. If SIGHUP finds any error, the running configuration stays active and the error is logged.
TIP
Validate any change before applying it:
sh
quikcast --config quikcast.toml --check--check runs the complete startup validation, including reading every secret, list, certificate and database, and then exits without binding a port. Exit code 0 means the file is good.
Secret values
Secrets are never defaults, so you must supply them. Where a setting offers both an inline value and a _file form, prefer the file.
| Kind | Length | Characters | Used by |
|---|---|---|---|
| Mount / HLS password | 16–512 bytes | any bytes; a password may contain : | secret, secret_file on mounts, [sources], [hls] |
| Usernames | 1–64 bytes | ASCII graphic, no whitespace, no : | every username |
| Management token | 32–512 bytes | ASCII graphic, no whitespace | [management] |
| Upstream relay password | 1–512 bytes | ASCII graphic, no whitespace | [[relays]] |
| Signed-URL key | 32–4,096 bytes | any bytes | listener_auth |
| UI password | at least 15 characters, at most 512 bytes | UTF-8 | [ui] |
A secret file contains only the secret. One trailing newline is removed; nothing else is trimmed. Do not wrap the value in quotes or write password=.... Files are read at startup and on SIGHUP, so editing a file takes effect only after a successful reload or restart.
Generate a good value with:
sh
openssl rand -hex 32 > secrets/radio-sourceRoot settings
These keys sit at the top of the file, above every table.
listen
Type
string(host:port) · Default"127.0.0.1:8000"· Applies restart
The address of the public listener. Players, encoders, HLS clients and the operator console all connect here.
The default only accepts connections from the same machine, which is right for local testing or when a reverse proxy runs beside Quikcast. To accept outside connections, bind a public address such as "0.0.0.0:8000". Inside Docker you must use 0.0.0.0, otherwise the published port has nothing to reach.
toml
listen = "0.0.0.0:8000"Use port 0 only in automated tests; the operating system then picks a free port.
admin_listen
Type
string(host:port) · Default"127.0.0.1:9090"· Applies restart
The address of the private listener. It serves /health/live, /health/ready, /metrics and the native /api management routes.
This address must be a loopback address (127.0.0.1 or ::1). Quikcast refuses to start with a public admin address, because these routes can disconnect listeners and drain the server. Reach it from outside through an SSH tunnel or an internal-only proxy, never by publishing the port.
log_filter
Type
string· Default"quikcast=info"· Applies restart
A tracing filter that decides how much is logged. Examples: "quikcast=debug" for connection-level detail, "quikcast=warn" for problems only.
The QUIKCAST_LOG environment variable overrides this value when set. Logs go to standard output through a bounded, non-blocking writer, so the output must be a terminal or a pipe that something is reading. Redirecting straight to a regular file is rejected. Logging currently requires a Unix system.
[tls]
Serve HTTPS directly from Quikcast. Omit the table to serve plain HTTP, which is the right choice when a proxy such as Traefik, nginx or Cloudflare already terminates TLS.
toml
[tls]
cert_file = "secrets/tls/fullchain.pem"
key_file = "secrets/tls/privkey.pem"TLS applies to the public listener only. The admin listener always stays plain and loopback. Quikcast offers TLS 1.2 and 1.3 with ALPN http/1.1. A plain HTTP request sent to a TLS port is closed without a response, and the TLS handshake counts against header_timeout_ms and max_handshakes.
INFO
Enabling or disabling [tls] needs a restart. Renewing a certificate does not: replace the files and send SIGHUP. New connections use the new certificate, existing listeners are not interrupted, and a bad file leaves the old certificate in place.
cert_file
Type
string(path) · Default none (required inside[tls]) · Max size 256 KiB · Applies reload
A PEM file containing the certificate chain, leaf certificate first, with one to eight certificates. A Let's Encrypt fullchain.pem is exactly the right file.
key_file
Type
string(path) · Default none (required inside[tls]) · Max size 64 KiB · Applies reload
A PEM file with the matching private key (PKCS#8, PKCS#1 or SEC1). A key that does not match the certificate stops startup or the reload with an error.
[trusted_proxies]
Tells Quikcast which network peers are your own proxies, so it can report real listener addresses instead of the proxy's address. The real address feeds IP rules, country rules, listener statistics and logs. By default no proxy is trusted and Quikcast uses the connecting address as is.
toml
[trusted_proxies]
ips = ["127.0.0.1", "10.0.0.0/8"]
providers = ["cloudflare"]
proxy_protocol = falseWARNING
Only list infrastructure you control. Anyone you trust here can claim any client address, which weakens every IP and country rule.
ips
Type
array of strings· Default[]· Max 64 entries · Applies reload
IP addresses or CIDR networks (IPv4 or IPv6) of your proxies. When a connection comes from one of these, Quikcast reads the X-Forwarded-For header and walks it right to left, stopping at the first address that is not itself trusted. Bare addresses mean a single host (/32 or /128). Hostnames and ports are rejected. Errors name the entry, for example trusted_proxies.ips[1].
providers
Type
array of strings· Default[]· Values"cloudflare"· Applies reload
Adds the built-in list of Cloudflare's published address ranges. A connection that comes directly from Cloudflare may then supply the client address in CF-Connecting-IP. The ranges ship inside Quikcast and are never fetched at runtime; they are refreshed by upgrading Quikcast.
proxy_protocol
Type
boolean· Defaultfalse· Applies reload (new connections)
Use this behind a layer-4 load balancer (HAProxy in TCP mode, nginx stream, a cloud network load balancer) that cannot add HTTP headers. When true, every public connection must begin with a PROXY protocol v1 or v2 header, sent before TLS and HTTP.
- It needs
ipsorproviders. Only peers that match them may send the header; a connection from anyone else is closed without a reply. - The client address in the header replaces the connecting address for all access rules and statistics.
- A header marked
LOCALorUNKNOWN, which is what health checks send, keeps the connecting address. - A missing or malformed header closes the connection.
- The admin listener never uses PROXY protocol.
toml
[trusted_proxies]
ips = ["10.0.0.0/8"]
proxy_protocol = trueWARNING
Turning this on means every public connection, including your own health checks and direct curl tests, has to arrive through the proxy.
[management]
Enables the native management API on the admin listener (/api/server, /api/stats, /api/mounts, /api/listeners, /api/history, and others).
The API is fail-closed: without this table every /api request is rejected. Mount and HLS passwords do not unlock it.
toml
[management]
token_file = "secrets/management-token"Call it by sending the token as a Bearer credential to the admin address:
sh
curl -H "Authorization: Bearer $(cat secrets/management-token)" \
http://127.0.0.1:9090/api/servertoken_file
Type
string(path) · Default none · Applies reload
Path to a file holding the token, 32–512 ASCII graphic characters. Set exactly one of token_file and token. Editing the file rotates the token after the next successful reload.
token
Type
string· Default none · Applies reload
The token written directly in the config. It works, but keeps a secret in a file you may commit or share. Prefer token_file.
[ui]
The embedded operator console, served at /ui on both listeners. It is compiled into the binary, so there is nothing else to install. It is off by default.
toml
[ui]
enabled = true
mode = "private"
username = "admin"
password_file = "secrets/ui-password"The console has one operator identity, separate from source, HLS and management credentials. Any change to the effective UI settings, including a password change, signs out existing sessions after the next reload.
enabled
Type
boolean· Defaultfalse· Applies reload
When false, every /ui URL, including its assets, returns 404.
mode
Type
string· Default"private"· Values"private","public"· Applies reload
"private" shows a login form and, after sign-in, the full console. "public" shows only a small sanitized status page and offers no login or controls. /ui/status is readable without signing in in both modes.
username
Type
string· Default""· Range 1–64 ASCII graphic bytes · Applies reload
The operator login name. Required in private mode.
password_file
Type
string(path) · Default none · Applies reload
Path to a file holding the password: at least 15 characters excluding surrounding whitespace, at most 512 bytes. Obvious values (a single repeated character, common passwords) are rejected. Required in private mode. The file is re-read on every reload.
secure_cookie
Type
boolean· Defaulttrue· Applies reload
Marks the session cookie Secure, so browsers only send it over HTTPS. Leave it on in production, including when a proxy terminates TLS. Set it to false only for throwaway testing over plain HTTP.
public_mounts
Type
array of strings· Default[]· Applies reload
The mount names the public status page may show. Each entry must be a configured mount. Listing a mount is your explicit consent to publish its availability, codec, content type and listener count. Leave the list empty to disclose no mount at all. Listing a mount here does not give anyone access to play it.
[limits]
Capacity and timing controls that protect the server. Every value has a sensible default, so most stations never need this table. All of [limits] needs a restart.
toml
[limits]
max_listeners = 2000
listeners_per_mount = 2000Startup checks that the memory you reserve fits retained_audio_budget. If it does not, you get retained audio budget cannot cover admission reservations; lower max_listeners or raise the budget.
| Setting | Default | Range |
|---|---|---|
ring_bytes | 4194304 | positive |
ring_entries | 4096 | 1–65,536 |
join_bytes | 65536 | 0 to ring_bytes |
max_connections | 12000 | 1–100,000 |
max_handshakes | 128 | 1 to max_connections |
max_listeners | 10000 | 1 to max_connections |
listeners_per_mount | 10000 | 0 to max_listeners |
retained_audio_budget | 1073741824 | enough for the reservation formula |
relay_allocation_budget | 33554432 | at least 2 MiB per relay |
header_timeout_ms | 5000 | 1–3,600,000 |
source_timeout_ms | 10000 | 1–3,600,000 |
write_timeout_ms | 10000 | 1–3,600,000 |
shutdown_timeout_ms | 10000 | 1–3,600,000 |
flac_initialization_bytes | 65536 | 42–262,144 |
flac_frame_bytes | 1048576 | 16–4,194,304 |
ring_bytes
The most encoded audio kept in memory for each live stream, in bytes. It is the catch-up buffer new listeners join from, and the line a slow listener must stay inside: a listener who falls further behind than the ring is disconnected, so one slow client never holds back everyone else. Default 4 MiB.
ring_entries
The most separate chunks of audio the ring can hold at once. Whichever of ring_bytes and ring_entries fills first decides when the oldest audio is dropped. A chunk is a block of published data, not one second or one codec frame.
join_bytes
How much recent audio a new listener may start from, in bytes (not milliseconds). Codec rules still decide the exact safe starting point. 0 means "start at the live edge".
max_connections
The most simultaneous TCP connections on the public listener, counting encoders, players, the console and HLS downloads. The admin listener has its own fixed reserve of 32 connections, so a flood of public clients can never lock you out of health checks or management. Connections beyond the limit are refused, not queued.
max_handshakes
The most connections that may be waiting to send their request headers (or finish a TLS handshake) at one time. This is the main defence against slow-connection attacks. The admin listener has a separate reserve of 16.
max_listeners
The most simultaneous audio listeners across all mounts. HLS downloads are ordinary requests and do not count here. This value also sizes the memory reservation, even when individual mounts are capped lower.
listeners_per_mount
The default listener cap for each mount. 0 means no per-mount cap beyond max_listeners. A mount's own listener_limit replaces this value rather than being combined with it.
retained_audio_budget
The total memory, in bytes, Quikcast promises it can use for audio rings, parser buffers and listener references. Startup calculates what your mounts and max_listeners require and refuses to start if that exceeds the budget. Default 1 GiB. The exact formula is in Capacity and limits.
relay_allocation_budget
Memory set aside for relay workers, at least 2 MiB per configured relay (up to 16). Disabled relays still count. Default 32 MiB.
header_timeout_ms
How long a client may take to send its request headers before the connection is dropped. Also bounds the TLS handshake.
source_timeout_ms
How long an encoder may send nothing before it is disconnected. A healthy stream can run forever; this only catches stalls.
write_timeout_ms
How long a listener's socket may refuse data before the listener is disconnected as a slow consumer. A quiet stream with nothing to send is not a violation.
shutdown_timeout_ms
How long a graceful shutdown waits for connections to finish after you stop the server. Anything still running afterwards is aborted.
flac_initialization_bytes
The most data Quikcast accepts for the start of a native FLAC stream: the fLaC marker plus every metadata block, including cover art. Raise it if your encoder embeds large artwork.
flac_frame_bytes
The most bytes allowed for a single unfinished native FLAC frame. This is a size limit in bytes, not a sample or block-size setting.
[[mounts]]
A mount is one stream path, such as /radio.mp3, that an encoder feeds and listeners play. Every [[mounts]] header starts a new mount; the settings under it, and its sub-tables, belong to it.
Configure 1–64 mounts. Zero mounts is allowed only if you enable [hls] or [sources].
toml
[[mounts]]
path = "/radio.mp3"
username = "source"
secret_file = "secrets/radio-source"Only one encoder can own a mount at a time. A second one is refused with 409 Source already connected; there is no takeover or priority. When the encoder disconnects, its listeners disconnect too, and players reconnect to the next encoder session.
path
Type
string· Default none (required) · Applies restart to add or remove a mount
The URL path listeners use. It must start with /, be at most 2,048 bytes, and use only ASCII letters, digits and / . _ - ~. Names are case-sensitive, and nested names such as /stations/jazz are fine.
Not allowed: / alone, a trailing /, //, . or .. parts, spaces, Unicode, or any path under the reserved roots /api, /admin, /internal, /health, /metrics and /hls.
The extension does not choose the codec. /radio.mp3 can carry any supported format; the encoder's content type decides.
username
Type
string· Default"source"· Applies reload
The username the encoder uses for HTTP Basic authentication (and for metadata updates).
secret_file
Type
string(path) · Default none · Applies reload
Path to the encoder password file. Set exactly one of secret_file and secret. If you set neither, the mount inherits the whole global [sources] credential.
secret
Type
string· Default none · Applies reload
The encoder password written directly in the config. Prefer secret_file.
fallback
Type
string· Default none · Applies reload
Another configured mount to serve when this one has no source. The listener keeps its original URL; Quikcast attaches it internally, with no redirect. Chains may be up to eight mounts long including the first. Unknown targets, a mount pointing at itself, loops and chains that are too long stop startup. Compatible MP3 and AAC listeners return to the original mount automatically when its source comes back. See Fallback mounts.
toml
[[mounts]]
path = "/live.mp3"
fallback = "/automation.mp3"
secret_file = "secrets/live-source"
[[mounts]]
path = "/automation.mp3"
secret_file = "secrets/automation-source"listener_limit
Type
integer· Default inheritlimits.listeners_per_mount· Range 0 tomax_listeners· Applies restart
The most listeners this mount allows. 0 removes the per-mount cap (the global limits still apply). A listener served through fallback counts against the mount that actually serves it.
[mounts.listener_auth]
Protect this mount with signed, expiring URLs created by your own application. Omit the table, or use type = "none", to keep the mount public. Quikcast only verifies; it has no accounts or sessions. See Signed URLs for the exact signing recipe.
toml
[mounts.listener_auth]
type = "signed_url"
secret_file = "secrets/radio-signing-key"
max_ttl_seconds = 86400
clock_skew_seconds = 0| Key | Default | Range | Meaning |
|---|---|---|---|
type | "none" | "none", "signed_url" | "none" leaves the mount public. |
secret_file | none | 32–4,096 bytes | HMAC key shared with your application. Required for signed_url. |
max_ttl_seconds | 86400 | 1–604,800 | The furthest in the future a link may expire. Longer links are refused. |
clock_skew_seconds | 0 | 0–60 | Extra time after expiry a link is still accepted, to tolerate clock drift. |
[mounts.access]
Restrictions on who may listen. They are added to the global [access] rules: a listener must pass both. The fields are identical to [access] except that geoip_database is only allowed in the global table.
toml
[mounts.access]
countries_allow = ["GH", "NG"]
ip_deny_file = "lists/blocked.txt"[mounts.source_access]
Restrictions on which IP addresses may connect an encoder to this mount. Credentials are still required. There is no global version, and no User-Agent or country rules here.
toml
[mounts.source_access]
ip_allow = ["203.0.113.10", "2001:db8::/48"]| Key | Type | Meaning |
|---|---|---|
ip_allow | array of strings | Only these IPs or CIDRs may connect. Empty means no restriction. |
ip_allow_file | path | A list file merged with ip_allow. It must contain at least one rule. |
ip_deny | array of strings | These IPs or CIDRs are always refused. |
ip_deny_file | path | A list file merged with ip_deny. An empty file is fine. |
Up to 10,000 distinct rules per list, counting inline and file entries together. Changes apply on reload.
[access]
Global restrictions on who may listen, applied to every mount, in addition to each mount's own [mounts.access]. Omit it for open playback.
A listener is allowed only if it passes every category you configure: IP, User-Agent and country. A deny rule always wins over an allow rule in the same category. Everything here applies on reload.
toml
[access]
ip_deny_file = "lists/blocked.txt"
user_agent_deny = ["exact:bad-monitor"]
countries_allow = ["GH", "NG"]ip_allow and ip_deny
Type
array of strings· Default[]· Limit 10,000 distinct rules together with the file
IPv4 or IPv6 addresses or CIDR networks, for example "203.0.113.9" or "198.51.100.0/24". With a non-empty ip_allow, only matching listeners are admitted. ip_deny always refuses matches. Equivalent entries are merged; overlapping networks are kept as written. IPv4-mapped IPv6 addresses (::ffff:192.0.2.1) match their IPv4 form.
ip_allow_file and ip_deny_file
Type
string(path) · Default none · Max size 1 MiB
A text file of rules, one IP or CIDR per line. Blank lines and lines starting with # are ignored, and a leading UTF-8 byte-order mark is tolerated. The file is merged with the inline list and re-read on every reload, so you can update a blocklist without restarting.
text
# blocked.txt
203.0.113.0/24
198.51.100.7
2001:db8:bad::/48Errors name the file and line, such as ip_deny_file: lists/blocked.txt:2: invalid IP/CIDR "not-an-ip".
WARNING
An ip_allow_file with no rules is rejected, because an empty allow list would mean "allow everyone". An empty ip_deny_file is fine.
user_agent_allow and user_agent_deny
Type
array of strings· Default[]· Limit 256 rules · Rule length 1–256 bytes
Match the player's User-Agent header. Each rule is mode:text, where the mode is one of:
| Mode | Matches when the User-Agent... |
|---|---|
exact: | equals the text exactly. |
prefix: | starts with the text. |
substring: | contains the text. |
Matching is case-sensitive. A rule without a mode, or with an unknown mode such as regex:, stops startup.
toml
user_agent_allow = ["prefix:RadioPlayer/"]
user_agent_deny = ["substring:unsupported-build"]INFO
User-Agent is chosen by the client and trivially faked. Use it for compatibility and convenience, not security.
countries_allow and countries_deny
Type
array of strings· Default[]· Limit 249 codes
Two-letter ISO 3166-1 country codes (and XK for Kosovo). Lowercase is accepted and normalised. countries_deny refuses listeners in those countries; countries_allow, when non-empty, admits only those countries. Addresses whose country cannot be determined, such as local ones, pass a deny-only policy but fail an allow list.
geoip_database
Type
string(path) · Default the built-in database · Max size 128 MiB
Country lookups use a database embedded in Quikcast, so country rules work with no setup. Set this to use your own MaxMind-format country database instead, for example "/etc/quikcast/GeoLite2-Country.mmdb". If you set it, it is opened and validated at startup even when no country rules are active, and re-opened on every reload. Quikcast never downloads or updates databases.
[sources]
Lets encoders connect to paths you did not list under [[mounts]]. The mount is created when the encoder connects and disappears when its last listener leaves. Nothing is saved to the config. Listed mounts always win over dynamic ones.
toml
[sources]
allow_dynamic_mounts = true
username = "source"
secret_file = "secrets/dynamic-source"
[sources.defaults]
name = "Quikcast Stream"
genre = "Various"
public = falseEven with allow_dynamic_mounts = false, setting credentials here makes every [[mounts]] that omits its own secret inherit them. A server with global credentials may have no [[mounts]] at all.
allow_dynamic_mounts
Type
boolean· Defaultfalse· Applies reload
When true, a correctly authenticated encoder may create a new mount just by connecting to a free path. It requires the credentials below, and the retained-audio budget must have room for at least one more stream.
username and secret_file / secret
Type
string· Default username"source"· Applies reload
The shared encoder credential for dynamic mounts and for configured mounts that have none of their own. Set exactly one of secret_file or secret.
[sources.defaults]
Descriptive station metadata used when an encoder sends none. An encoder's own ice-name, ice-description, ice-genre, ice-url and ice-public headers always win. These fields describe the station only; they cannot change limits, security or routing.
| Key | Type | Meaning |
|---|---|---|
name | string | The station name shown to players. |
description | string | A short description. |
genre | string | The genre. |
url | string | The station's website. |
public | boolean | Whether the stream may be listed publicly. |
[[relays]]
A relay pulls an existing internet stream and republishes it on one of your mounts. Use it to rebroadcast another station, build an edge server, or run a backup feed.
The target mount must exist under [[mounts]]. Once a mount has a relay, the relay owns it: an encoder cannot connect there, even while the relay is stopped. You can have up to 16 relays, one per mount. Relay settings apply on reload; changing any of them restarts that relay.
toml
[[mounts]]
path = "/remote.mp3"
secret_file = "secrets/remote-local"
[[relays]]
mount = "/remote.mp3"
upstreams = [
{ url = "https://primary.example/live" },
{ url = "https://backup.example/live" },
]mount
Type
string· Default none (required)
The local mount the relay feeds. It is the path your listeners use, not part of the upstream URL.
url, urls and upstreams
Type
string,array of strings, orarray of tables· Default none · Choose exactly one
Where to pull audio from. Use one of the three forms:
url = "https://origin.example/live"is a single upstream.urls = ["https://a.example/live", "https://b.example/live"]lists 1–8 upstreams that share the relay's credentials.upstreams = [{ url = "..." }, ...]lists 1–8 upstreams where each may have its own credentials.
All forms need absolute http:// or https:// URLs of at most 2,048 bytes. Queries are fine. Fragments, user:pass@ in the URL, whitespace and other schemes are rejected. Pointing a relay at this same server is rejected. Duplicates are rejected.
The upstream may be a plain stream (MP3, AAC, Ogg, FLAC), an old SHOUTcast server, or an audio-only HLS playlist: see Relay another station.
The first entry is the primary. If it fails, Quikcast tries the next entry immediately. After the last entry fails, it waits for the backoff delay and starts again at the primary. A working backup stays selected until it fails: there is no automatic return to the primary.
TIP
New configurations should prefer upstreams, since it can express everything url and urls can. The older forms still work.
toml
upstreams = [
{ url = "https://primary.example/live", username = "primary-user", secret_file = "secrets/primary-password" },
{ url = "https://backup.example/live" },
]username and secret_file
Type
string· Default none (anonymous) · Password 1–512 ASCII graphic bytes
Basic-auth credentials sent to the upstream. Set both or neither. There is no inline secret field for relays. With upstreams, these act as the default for entries that set neither of their own fields; an entry with its own username and secret_file overrides them.
enabled
Type
boolean· Defaulttrue
Whether the relay starts with the server. false starts it stopped (the mount still stays reserved); you can start it later through the management API.
max_redirects
Type
integer· Default0· Range 0–8
How many upstream redirects (301, 302, 303, 307, 308) to follow per attempt. 0 treats any redirect as a failure. Credentials are never sent to a different host than the one you configured: they are dropped at the first change of scheme, host or port.
hls_variant
Type
string· Default"highest"· Values"highest","lowest"· Applies reload (restarts the relay)
When a relay's upstream is an HLS master playlist offering several bitrates, this chooses which audio-only variant to follow. "highest" gives the best quality; "lowest" uses the least bandwidth. Variants that contain video are never chosen. It has no effect on non-HLS upstreams.
toml
[[relays]]
mount = "/hls-station.aac"
url = "https://stream.example/live/master.m3u8"
hls_variant = "lowest"
max_redirects = 3Timeouts and retry
All values are milliseconds from 1 to 3,600,000 unless stated.
| Key | Default | Meaning |
|---|---|---|
connect_timeout_ms | 10000 | One overall deadline for DNS, TCP connect and TLS. |
header_timeout_ms | 5000 | How long the upstream may take to send response headers. |
audio_timeout_ms | 10000 | How long without new audio before the attempt is abandoned. For HLS upstreams the limit is the longer of this and three segment lengths, and it also bounds each playlist or segment download. |
initial_backoff_ms | 1000 | First wait after every upstream failed. Must not exceed max_backoff_ms. |
max_backoff_ms | 60000 | Longest wait between retry cycles. |
stable_connection_ms | 30000 | A connection that lasts this long resets the backoff. |
jitter_percent | 20 | Random variation applied to waits, 0 to 50. |
Outbound HTTPS is always verified against the bundled public certificate roots. There is no option to disable verification, add a private CA or use client certificates.
[hls]
A separate subsystem that serves HLS (.m3u8 playlists with segment files) to browsers and mobile players. Quikcast does not encode: your own tool, such as FFmpeg, pushes finished, correctly packaged segments to the private ingest API, and Quikcast stores and serves them from memory. HLS is not derived from your normal mounts.
Everything is held in memory and lost on restart. Passwords, CORS and signed-URL rules reload; capacity settings need a restart.
toml
[hls]
username = "hls"
secret_file = "secrets/hls-ingest"
cors_origin = "https://player.example.com"See HLS ingest API for the ingest API and supported formats.
username
Type
string· Default"hls"
The ingest username (Basic authentication).
secret_file / secret
Type
string· Default none · Password 16–512 bytes
The ingest password. Set exactly one. It must differ from every mount password.
cors_origin
Type
string· Default none (CORS off) · Max length 256 bytes
The website allowed to play your HLS from a browser. Use "*" or exactly one origin such as "https://player.example.com" (no path, no trailing slash, no list). Without it, browsers on other sites cannot fetch your playlists, although non-browser players are unaffected. Preflight requests allow Range, If-None-Match and If-Range.
Capacity settings
| Key | Default | Range | Meaning |
|---|---|---|---|
max_streams | 16 | 1–64 | Most HLS streams. |
renditions_per_stream | 8 | 1–16 | Most quality levels per stream. |
max_renditions | 64 | 1–128 | Most quality levels overall. |
max_segment_bytes | 2097152 | 188–16,777,216 | Largest single uploaded segment. A safety ceiling, not a target size. |
max_segments | 1024 | 16–4,096 | Segment slots per rendition. |
max_uploads | 4 | 1–16 | Uploads accepted at the same time. |
rendition_budget | 134217728 | up to allocation_budget | Memory reserved per rendition. |
allocation_budget | 536870912 | up to 4 GiB | Total memory for HLS. |
[hls.listener_auth."/hls/NAME"]
Protect one HLS stream with signed URLs. The key is the stream's public path, /hls/ followed by 1–64 letters, digits, _ or -. A stream with no entry is public; up to 64 entries are allowed. One signature covers the playlists and every segment of that stream. The fields are the same as [mounts.listener_auth].
toml
[hls.listener_auth."/hls/private"]
type = "signed_url"
secret_file = "secrets/hls-private-key"Putting it together
A small station with HTTPS, a private console, a country allow list and a backup feed:
toml
listen = "0.0.0.0:8443"
admin_listen = "127.0.0.1:9090"
[tls]
cert_file = "secrets/tls/fullchain.pem"
key_file = "secrets/tls/privkey.pem"
[management]
token_file = "secrets/management-token"
[ui]
enabled = true
username = "admin"
password_file = "secrets/ui-password"
[access]
countries_allow = ["GH", "NG"]
ip_deny_file = "lists/blocked.txt"
[[mounts]]
path = "/radio.mp3"
fallback = "/backup.mp3"
secret_file = "secrets/radio-source"
[[mounts]]
path = "/backup.mp3"
secret_file = "secrets/backup-local"
[[relays]]
mount = "/backup.mp3"
upstreams = [{ url = "https://backup.example/live" }]What can be reloaded
Send SIGHUP to apply changes without dropping connected listeners or encoders:
sh
kill -HUP "$(pidof quikcast)"| Applies on reload | Needs a restart |
|---|---|
[tls] certificate and key files | listen, admin_listen, log_filter |
[trusted_proxies] (all keys) | Enabling or disabling [tls] |
[management], [ui] | Every [limits] setting |
[access] and every list file | Adding or removing a [[mounts]] entry, and listener_limit |
Mount credentials, fallback, listener_auth, access, source_access | HLS capacity settings |
[sources] (credentials, dynamic mounts, defaults) | |
[[relays]] (a changed relay restarts) | |
[hls] credentials, cors_origin, listener_auth |
A reload is all or nothing. Existing listeners and encoders stay connected, and rotated credentials affect new connections and updates. For the full behaviour, see Operations: reload, drain and troubleshooting.