ETTIX STREAM is configured by one JSON file. There are no other configuration files, no include directives and no environment variables that change behaviour; everything the server does is described in that one document, and the server writes the same document back when you change something through the administration interface.
This page covers the parts an operator touches, the rules the validator enforces, and how a change reaches a running server. It is not a field-by-field reference of every setting that exists. Viewer authorization, publisher credentials, TLS and encryption have their own pages.
The default path is /etc/ettix-stream/config.json. Every
command that reads a configuration takes --config PATH, and
falls back to the ETTIX_STREAM_CONFIG environment variable
before it falls back to the default. The systemd unit installed by
install.sh names the path explicitly:
ExecStart=/usr/local/bin/ettix-stream serve --config /etc/ettix-stream/config.json
The installer creates the file with mode 0640, owner
root, group ettix-stream, inside a directory
created as 2770 root:ettix-stream. Those permissions are
worth understanding rather than copying, because two different things
need them.
The file is not world-readable because it holds live credentials:
RTMP stream keys, SRT passphrases, administration password hashes and
ingest tokens. Anyone who can read it can publish to your streams. The
directory is group-writable because the administration API replaces the
file by writing a temporary file beside it and renaming over the top —
an atomic replace needs write permission on the directory, not on the
file. When the server creates a configuration file that did not exist it
uses mode 0600; when it replaces one that did, it keeps
whatever mode the file already had, so a deliberate choice of yours is
never quietly widened.
The service account never needs to read the file as root. If you
tighten the permissions yourself, keep the ettix-stream
group able to read the file and write the directory, or the server will
start but every save from the administration interface will fail with a
read-only-filesystem or permission error.
It is strict JSON: UTF-8, no comments, no trailing commas. Two rules beyond that are worth knowing before you start editing.
Unknown fields are rejected. A misspelt key is an error, not a setting that silently does nothing. This is the single most useful property of the loader — a typo in a security-relevant field would otherwise leave you believing a control is in force when it is not:
$ ettix-stream config check --config /etc/ettix-stream/config.json
error: /etc/ettix-stream/config.json: config: parse: json: unknown field "admin_lister"
Durations and sizes are written as strings. A
duration is a Go duration string: "4s",
"500ms", "1m30s", "72h". A bare
number is read as seconds. A size is either a plain number of bytes or a
string with a unit — "512MB", "2GiB",
"64KB". Every unit is a binary multiple, so
MB and MiB mean the same thing here.
The top-level version field is the schema version and
must be 1. It is not the product version. Absent settings
take their defaults, and absent blocks take the defaults of every field
inside them, so a short file is a legitimate file.
ettix-stream config check loads a file, applies the
defaults and runs every validation rule, then exits without starting
anything. Run it after every edit, and run it with the new binary before
an upgrade.
ettix-stream config check --config /etc/ettix-stream/config.json
A file that passes prints a one-line summary:
/etc/ettix-stream/config.json: OK (1 streams, http :8080, rtmp :1935)
A file that fails prints every problem it found, each one naming the field by its path in the document, and exits non-zero. The validator does not stop at the first error, because fixing a configuration one error per run is how an outage gets longer:
$ ettix-stream config check --config bad.json
error: bad.json: config: 3 problems:
- http.admin_listen: "0.0.0.0:8081" is not loopback; bind it to 127.0.0.1, or set
http.admin_allow_remote and configure http.admin_auth once you have put a firewall,
VPN or authenticating proxy in front of it too
- streams[0] (channel1).source.rtmp.stream_key: still the example placeholder, which is
published in the documentation and in every release; anyone who has read it could
publish to this stream. Replace it
- streams[0] (channel1).hls.segment_duration: must be between 1s and 30s
The stream index and the stream id both appear, so
streams[0] (channel1) tells you which entry to edit in a
file with forty of them.
The exit code distinguishes the two ways a check can fail, which matters in a deployment script:
| Exit code | Meaning |
|---|---|
0 | The file is valid. |
3 | The document parsed, but one or more settings were refused. The problems are listed on standard error. |
1 | The file could not be read or is not valid JSON, including an unknown field. |
2 | The command was not recognised, or help was asked for. A mistyped flag exits 1. |
Two other commands help here.
ettix-stream config example prints a starter document
generated by the binary that will read it, so the two can never disagree
about the schema. ettix-stream config check --dvr-roots
prints the DVR destination directories, one per line, and nothing else —
that is what the installer uses to fill in
ReadWritePaths in the systemd unit.
Editing the file changes nothing by itself. A running server re-reads
it on SIGHUP, and the systemd unit maps
systemctl reload onto exactly that signal.
Work on the real file, or on a copy you move into place. Keep a backup: the file is often the only written record of a stream key.
ettix-stream config check --config /etc/ettix-stream/config.json
This is not optional courtesy. A reload that fails leaves the running server on its old configuration, which is safe, but it also means your change silently did not happen.
sudo systemctl reload ettix-stream
Without systemd, send the signal directly:
sudo kill -HUP $(pidof ettix-stream)
The server logs reload requested and then
reload complete with the number of streams added, removed
and restarted. A rejected file logs
reload rejected; keeping current configuration together
with the validation problems.
journalctl -u ettix-stream -n 30
A reload applies the whole document or none of it. It is validated completely before anything is swapped in, so a broken file cannot take half of your streams off the air.
Streams and the log level change while the server runs. Adding, removing or editing a stream is exactly what reload is for: only the streams that actually changed are restarted, and the rest keep their publishers and their viewers.
Settings that belong to a listener or to a process-wide subsystem cannot change under a running process. They are still written into the new configuration, and the server tells you it needs a restart rather than pretending:
configuration reloaded; listener, RTMP or cache settings changed and require a restart to take effect
| Block | Reload |
|---|---|
streams[] | Applied. Changed streams are restarted, untouched ones are left alone. |
log.level | Applied immediately. |
log.format | Restart. See the warning below. |
http (any field) | Restart. |
rtmp (any field) | Restart. |
hls.cache_max_bytes | Restart. |
server.data_dir | Restart. |
license, telemetry | Restart. |
log.format is the one setting that changes on reload
without being reported. The level is applied live and the format is read
only when the process starts, so a reload after switching
"text" to "json" succeeds, says nothing, and
leaves the log in the old format until you restart. If your log shipper
suddenly stops parsing, restart the service.
A restart is systemctl restart ettix-stream. It drops
publishers and viewers, who reconnect; it does not damage recordings.
The administration interface is not a read-only view. Saving a change
there — enabling a stream, adding a DVR destination, editing a stream
key — writes the configuration file on disk and then applies it through
the same code path SIGHUP uses. This is deliberate: a
running server whose settings have drifted away from the file it starts
from is one restart away from silently reverting.
The routes involved are on the administration listener:
| Route | What it does |
|---|---|
GET /api/v1/config | Returns the running document, its source path, a version fingerprint, and whether this server can write it back. |
PUT /api/v1/config | Validates a complete document, writes it to disk, then applies it. |
POST /api/v1/config/check | Validates a document without writing or applying anything. |
POST /api/v1/config/reload | Re-reads the file from disk, which is what SIGHUP does. |
GET|PUT|DELETE /api/v1/streams/{id}/config | The same mechanism applied to one stream's block. |
The document returned by GET /api/v1/config is
redacted and cannot be written back unchanged. Stream keys, SRT
passphrases, tokens and password hashes render as ***,
because a configuration reaches log files, diagnostic bundles and the
API. The response carries "redacted": true to say so. A
script that reads the configuration, edits one field and PUTs the result
has its write refused rather than applied: *** is three
characters where a stream key needs eight and an SRT passphrase ten,
and it is not a hash at all, so the document fails validation with
CONFIG_INVALID and nothing is written — the edit you meant
to make is lost with it.
Read the file on disk when you need the real values,
or use the single-stream routes, which edit the file as text and leave
untouched blocks exactly as you wrote them.
What this means for hand edits is straightforward. The file on disk
is the source of truth for secrets, and the API manipulates it as text
rather than round-tripping it through parsed values. But if you edit the
file by hand while someone else is working in the administration
interface, one of the two changes will be lost — the API replaces the
file wholesale. To guard against that, a write may carry the
version fingerprint it read; a document that changed
underneath is refused with CONFIG_CONFLICT rather than
merged, because merging two edits to a configuration is guessing.
Two more consequences of the file being writable by the server:
CONFIG_READ_ONLY instead of applying a change that would
vanish on restart."server": {
"name": "origin-1",
"data_dir": "/var/lib/ettix-stream"
}
name is a display name shown in the status output and in
telemetry; it defaults to the machine's hostname.
data_dir defaults to /var/lib/ettix-stream,
must be an absolute path, and is created with mode 0700 if
it is missing and checked for writability at startup. It holds the state
that makes this installation itself: the installation identity and
private key, the licence lease, and the encryption keys. Back it up, and
do not point two servers at the same directory — copying an identity is
exactly the cloning the licensing hardware fingerprint exists to
notice.
"log": { "level": "info", "format": "json" }
level is debug, info (the
default), warn or error.
format is text (the default) or
json, one JSON object per line, which is what you want when
a log shipper reads the journal. Remember that only the level changes on
reload.
debug is genuinely verbose on a busy server: a line for
every HTTP request, for every completed segment and for each step of an
RTMP connection. Raise it to diagnose something and then put it back —
the level changes with a reload and no restart, so this costs you
nothing but the log volume while it is on.
This is the block where a mistake is most expensive, so the defaults are deliberately timid.
"http": {
"listen": ":8080",
"admin_listen": "127.0.0.1:8081",
"cors_origins": ["https://player.example.com"],
"trusted_proxies": ["10.0.0.0/24"]
}
listen is the playback listener, default
":8080". HLS playlists, segments, DVR playback and the
/health and /ready probes are served here.
Anyone who can watch a stream can reach this listener.
admin_listen is the administration listener, default
"127.0.0.1:8081". The /api/v1/ routes, the
administration interface and these documentation pages are served here
and only here — they are never available on the playback
listener, whatever else you configure. The default is loopback because
this API can stop a broadcast, read operational detail, delete recorded
media and change the configuration file. The two addresses must
differ.
Binding the administration listener to anything other than loopback takes two separate acknowledgements, and the validator refuses the configuration until it has both:
"http": {
"listen": ":8080",
"admin_listen": "10.0.0.5:8081",
"admin_allow_remote": true,
"admin_auth": {
"users": [
{"username": "alice",
"password_hash": "$argon2id$v=19$m=19456,t=2,p=1$…salt…$…digest…",
"read_only": false}
],
"api_keys": [
{"id": "deploy", "hash": "$argon2id$v=19$m=19456,t=2,p=1$…salt…$…digest…",
"read_only": true}
],
"session_ttl": "12h",
"session_idle": "1h",
"max_sessions": 1024
}
}
admin_allow_remote says you meant to expose it.
admin_auth gives it a credential. Neither substitutes for
the other: a remote listener with only admin_allow_remote
is refused, and so is one with only admin_auth. There is no
default administrator and no bundled password, because a shipped
credential is a credential every installation in the world shares.
Passwords are never written in this file. Both
password_hash and an API key's hash hold an
Argon2id or bcrypt hash, and anything else is refused — plaintext most
of all. Produce one with:
ettix-stream admin hash --user alice
ettix-stream admin hash --api-key deploy --read-only
The first prompts for a password twice without echoing it and prints
the JSON object to paste into users. The second generates
the secret itself, prints the
Authorization: Bearer <id>.<secret> line once —
it is not stored and cannot be printed again — and prints the object to
paste into api_keys. read_only restricts a
credential to safe methods, which is the right setting for anything that
only reads status. Sessions default to a 12-hour lifetime and a 1-hour
idle timeout, and the session table is bounded at 1024 by default; there
is deliberately no unlimited setting for a table that anyone who can
reach the login endpoint can grow.
cors_origins lists the browser origins allowed to fetch
playback responses. Each entry is an origin like
https://player.example.com, or "*" for any
origin. The default is empty, which sends no CORS headers at all — that
is correct for a native player or a CDN, and wrong for a web page on
another domain, which will fail with an opaque browser error.
trusted_proxies lists the CIDR blocks whose
X-Forwarded-For header may be believed. A bare address is
accepted and treated as a single host. The default is empty, and empty
means the header is ignored entirely and the client is always the peer
that connected.
Get this one wrong in the unsafe direction and every IP-based rule in
the product stops working. Behind a CDN or reverse proxy the peer is the
proxy, so IP allow-lists and IP-bound tokens need this list to be
correct. But if the server believes the header from anybody, any client
can declare its own address and walk straight through those rules. That
is why an entry covering everything — 0.0.0.0/0 or
::/0 — is refused outright rather than accepted as a
working configuration. List every hop that can reach this server,
including the load balancer in front of the CDN. Listing too few makes
IP rules see the proxy address, which is visible and fixable; listing
too many hands the decision to the client, which is neither.
The remaining http fields are timeouts and limits with
defaults that suit a media server:
read_header_timeout 10s,
idle_timeout 60s,
write_timeout 30s (the deadline for sending
one response, so it must exceed the time a slow but legitimate viewer
needs for a single segment), and max_connections
10000 per listener. There is no unlimited setting for
connections.
"rtmp": { "listen": ":1935" }
The RTMP ingest listener, default ":1935", which is the
port encoders expect. It must differ from both HTTP listeners. The other
fields rarely need changing: chunk_size
4096 (128–65536), max_message_size
"16MB" (64KB–64MB, and a larger declared message closes the
connection), max_connections 1000,
handshake_timeout 10s,
read_timeout 30s — which is how long a silent
publisher is kept before it is disconnected — and
write_timeout 10s. All three timeouts must be
at least 1s.
This is the ingest listener for the whole server. Which stream a publisher lands on, and what credential it must present, is configured per stream.
"hls": { "cache_max_bytes": "512MB" }
One global setting: the RAM budget for completed segments across
every stream, default "512MB", minimum
"16MB". When the budget is exceeded, the oldest segments
that are outside their stream's live playlist window are evicted first;
segments still inside a live window are never evicted, because dropping
one would break playback for everyone watching. If the live windows
alone exceed the budget the server reports that in its status rather
than hiding a configuration error by dropping media, so size this
against the number of streams times
cache_segments times your segment size. Per-stream HLS
settings — segment duration, playlist length — live inside each stream,
not here.
Each entry describes one channel. id is required and
must match ^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$; it appears in
every URL and API path for the stream, and must be unique.
name defaults to the id, and enabled defaults
to true — a disabled stream refuses publishers and playback
but keeps all of its configuration.
{
"id": "channel1",
"name": "Channel 1",
"enabled": true,
"source": {
"type": "rtmp_push",
"rtmp": {
"app": "live",
"stream_key": "wZ4pEr6nQ2tKcXm8Ld0vBs1y",
"replace_policy": "reject",
"min_replace_interval": "2s"
}
},
"hls": {
"segment_duration": "4s",
"playlist_segments": 6,
"cache_segments": 12,
"program_date_time": true
}
}
With this configuration the encoder publishes to
rtmp://host:1935/live/channel1?key=wZ4pEr6nQ2tKcXm8Ld0vBs1y
and viewers play
http://host:8080/hls/channel1/index.m3u8.
app is the RTMP application name, default
live. stream_key is the publisher's
credential, at least 8 characters, presented as the
?key= query parameter. It is required unless you set
allow_anonymous (which accepts any publisher and is for
isolated networks only) or configure publisher credentials for the
stream. The two are mutually exclusive: a stream that has both a key and
allow_anonymous is refused, because there is no reading of
that pair that is not a mistake.
replace_policy decides what happens when a second
publisher arrives while one is live. reject, the default,
refuses the newcomer and protects the broadcast that is already on air.
replace disconnects the existing publisher and takes over,
which is what you want when an encoder that crashed reconnects with the
old connection still half-open. min_replace_interval,
default 2s, is the shortest gap between two takeovers:
inside the window replace behaves as reject.
That default exists because two encoders sharing one credential, both
set to reconnect, will otherwise take the stream from each other as fast
as TCP allows, and every takeover flushes a partial segment and inserts
a discontinuity in the playlist. The first publisher on an idle stream
is never a replacement and is never delayed.
max_publishers may only be 0 (which selects
1) or 1. A stream has one source and one HLS timeline, so a
second concurrent publisher has nowhere to go; a larger number is
refused rather than accepted and quietly ignored.
The HLS fields control the live window.
segment_duration is a target, default 4s,
allowed 1s–30s; actual cuts follow keyframes,
so the encoder's keyframe interval decides how close you get.
playlist_segments is how many segments the live playlist
offers, default 6, allowed 3–60 — this is the main lever on
latency, and 3 is the floor the HLS specification sets.
cache_segments is how many are kept in memory, default
12, and must be at least
playlist_segments so that a viewer slightly behind the edge
still finds what the playlist advertises. program_date_time
defaults to true.
An srt_pull stream is the opposite direction of travel:
this server dials out to remote SRT listeners and pulls MPEG-TS from
them. Sources are prioritised and the server fails over between them.
{
"id": "feed1",
"name": "Feed 1",
"source": {
"type": "srt_pull",
"srt": {
"sources": [
{"id": "primary", "host": "203.0.113.10", "port": 9000,
"priority": 1, "latency": "200ms",
"stream_id": "publish/feed1", "passphrase": "a-long-passphrase"},
{"id": "backup", "host": "203.0.113.20", "port": 9000,
"priority": 2, "latency": "200ms"}
],
"reconnect": {"delay": "1s", "max_delay": "30s", "multiplier": 2.0},
"failover": {
"mode": "return_to_primary",
"unhealthy_after": "5s",
"stability_period": "30s",
"warm_standby": false
}
}
},
"hls": { "segment_duration": "4s", "playlist_segments": 6 }
}
At least one source is required and at most eight are allowed. Each
needs an id (same grammar as a stream id, unique within the
stream), a host and a port. Two sources may
not share the same host, port and stream_id, since that is
one source written twice rather than two paths.
latency is the SRT receiver buffer, default
120ms, allowed 20ms–8s. It has to
cover the round-trip time of the path plus room to retransmit, so a
transcontinental feed needs considerably more than the default; too
small and the stream drops packets that could have been recovered.
stream_id is the SRT stream identifier the remote listener
expects. It is treated as a secret and never logged or returned by the
API, because it commonly carries an access token.
passphrase turns on SRT encryption and must be 10–79 bytes;
pbkeylen is 16, 24 or 32 and defaults to 16 when a
passphrase is set, and must be absent otherwise.
priority orders the sources — lower is preferred — and
defaults to the position in the list. Priorities must be unique within
the stream. failover.unhealthy_after (default
5s) is how long the active source may be unhealthy before
the server switches, and stability_period (default
30s) is how long a source must be continuously healthy
before it is eligible again. That second one is the anti-flap control: a
source that fails every minute would otherwise be reinstated the instant
it came back, and every switch is a discontinuity for viewers.
mode is return_to_primary (the default, which
goes back to the highest-priority source once it is stable) or
stay. warm_standby keeps every source
connected for a faster switch, at the cost of bandwidth and memory for
packets that are discarded.
An srt_pull stream must not carry a
publisher block, and the validator says so. Nobody connects
to this server for such a stream — it dials out — so ingest credentials
could never be presented. The credentials for the remote end are the
stream_id and passphrase on each source.
Recording is configured per stream. The DVR page covers the on-disk format and retention behaviour; this is the shape of the block.
"dvr": {
"enabled": true,
"mode": "always",
"destinations": [
{"id": "fast", "path": "/mnt/dvr1",
"max_age": "48h", "min_free_percent": 10, "sync": "interval"},
{"id": "archive", "path": "/mnt/dvr2",
"max_bytes": "4TB", "retention_interval": "60s"}
],
"export": { "enabled": true, "max_range": "2h" }
}
enabled turns recording on and mode decides
when: always (the default), schedule,
api or off. In schedule mode at
least one window is required, each with start and
end as "HH:MM" in the server's local time and
an optional list of days from mon to
sun; an end earlier than the start wraps past midnight.
Each destination needs a unique id and an absolute
path, and at most eight are allowed per stream. Give each
destination its own filesystem: free-space retention measures the
medium, so two destinations sharing one make that rule fight itself, and
two destinations of the same stream sharing a root are refused. The
retention rules — max_age, max_bytes,
min_free_percent, min_free_bytes — are all
optional, all combinable, and all disabled at 0.
Destinations are independent: one failing never affects another and
never stops live HLS.
sync selects durability. interval, the
default, runs a background syncer every sync_interval
(default 1s) and survives a process crash unharmed, risking
only the last second on sudden power loss. segment costs an
fdatasync per segment and is the right choice on a host
that can lose power without a battery-backed write cache.
none leaves it to the operating system.
export.enabled offers MP4 download of a recorded range
and is off by default, because one request produces one file from an
arbitrarily long span. max_range caps that span, default
4h and never more than 24h; without a cap a
single request can read the whole archive, which is a cheap way to
saturate the disks you are recording to.
The hardened systemd unit sets
ProtectSystem=strict, which makes the filesystem read-only
except for the paths listed in ReadWritePaths. A DVR root
that is not listed there fails at the first write with a permission
error that looks nothing like a configuration mistake. The installer
fills the list in from
ettix-stream config check --dvr-roots. If you add a
destination later, add its path to the unit and run
systemctl daemon-reload.
The whole block may be absent, and absent means the defaults. It never means "no licence required": every field says where the licensing authority is, which keys may sign for it, or how eagerly to renew. There is deliberately no switch that turns licensing off, because a product whose licensing can be disabled by editing a JSON file has none.
"license": {
"authority_url": "https://api.ettix.com",
"product": "ettix-stream",
"dir": "/var/lib/ettix-stream/license",
"identity_dir": "/var/lib/ettix-stream/identity",
"renew_interval": "6h",
"max_offline": "72h",
"grace": "60s",
"request_timeout": "20s"
}
Every value above is the default, so an installation talking to
Ettix.com needs none of them. authority_url must be HTTPS —
the only exception is a loopback address, for development — and must
carry no credentials or query string, because the installation
authenticates with its own key and ETTIX stores no reusable password.
dir and identity_dir both default under
server.data_dir, must be absolute, and must differ from
each other: the lease is rewritten constantly and can be replaced by
asking for another, while the identity directory holds the private key
that is this installation.
max_offline is how long the server keeps running without
reaching the authority. It defaults to 72 hours, and 72 hours is also
the ceiling — a shorter window may be configured, a longer one is
refused, and a lease claiming a longer life is honoured only this far.
renew_interval defaults to 6 hours and must be no more than
half of max_offline, so that a single failed renewal is
never the one that matters. Do not shorten max_offline
below an hour; the validator refuses it, because a window that short
turns an ordinary restart of the authority into an outage here.
trusted_keys and authority_pins exist for a
self-hosted authority. A trusted key can only be added: a key id that
collides with one compiled into the binary is refused, so no
configuration edit can substitute a different key for Ettix's own.
Changing anything in license requires a restart. A
reload will report success and change nothing, which is why the server
flags the restart explicitly rather than leaving you to compare an
authority URL against a symptom.
The fastest way to understand the rules is to read the things the validator will not accept. Each of these is refused at load time, so the server never runs with one of them in force.
version other than 1, or trailing data
after the JSON document.server.data_dir, a DVR destination path or a licence
directory that is not absolute.^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$, or two streams sharing
an id.http.admin_listen
must differ from http.listen, and
rtmp.listen from both.admin_listen without
admin_allow_remote. Exposing a privileged API should be a
decision somebody wrote down.admin_listen with
admin_allow_remote but no credential in
admin_auth. Reachable and anonymous is refused rather
than warned about, because this API can stop a broadcast, read every
stream key and delete recorded media.password_hash or API key hash that is
not an Argon2id or bcrypt hash. Plaintext passwords are not a
supported configuration, and argon2i and argon2d are rejected in
favour of argon2id.trusted_proxies entry that is not an address or
CIDR block, or one whose prefix covers every address. The second is
the dangerous one: it is identical to believing
X-Forwarded-For from anybody.cors_origins entry that is neither "*"
nor an http:// or https:// origin.rtmp_push stream with no
stream_key, no allow_anonymous and no
publisher credentials — that is a stream anybody can publish to, by
accident.stream_key shorter than 8 characters, or one that
is still the example placeholder
change-this-stream-key. That value is printed in the
documentation and shipped in every release, so anyone who has read the
manual could publish to your server. "Change it before going live"
printed by an installer is advice; refusing to start is a control.
ettix-stream config example --generate-stream-key
produces a starter file with a real random key instead.stream_key together with
allow_anonymous, or allow_anonymous together
with publisher credentials. Remove one to say which you meant.publisher block on an srt_pull stream,
an rtmp block on an srt_pull stream, or an
srt block on an rtmp_push stream.hls.segment_duration outside
1s–30s, playlist_segments
outside 3–60, or cache_segments smaller than
playlist_segments — a playlist advertising segments the
cache has already dropped.hls.cache_max_bytes below 16MB, or
max_publishers above 1.pbkeylen that is not
16, 24 or 32, or a pbkeylen with no passphrase.schedule mode with no windows, a weekday outside
mon..sun, or a window whose start equals its
end.license.max_offline above 72 hours or below one hour,
a renew_interval greater than half the offline window, a
non-HTTPS authority_url, or a
license.dir equal to license.identity_dir.Two rules cover most confusion. If a change appears to do nothing,
check whether it needs a restart rather than a reload. If the server
will not start or reload, run config check against the same
file and read the field paths it prints — the validator names the exact
setting, and it lists all of them at once.
The Troubleshooting page covers symptoms; the Security page covers viewer authorization, publisher credentials, TLS and encryption, all of which are configured in blocks this page has deliberately left out.