Reference

Configuration

Every setting in telltale.toml, generated from the same schema the binary validates against, so it can't drift from the code.

  • Every key is optional: anything you leave out uses the default shown here.
  • [name] is a table (set once); [[name]] is a list of tables (repeat it for each entry).
  • Check a file before using it: telltale config check telltale.toml. Unknown keys are errors, with the path to the mistake.
  • Editor completion: telltale config schema > telltale.schema.json and point your TOML extension at it.
  • Environment variables override the file: TELLTALE_CACHE_MAX_TTL=3600 sets [cache] max_ttl.
  • Changes apply on reload (SIGHUP) without dropping queries, except [node], [cache], [telemetry], and [filter], which need a restart.

Sections: [node] · [[listen]] · [[upstream]] · [[upstream_group]] · [[route]] · [[list]] · [filter] · [[group]] · [[client]] · [clients] · [[record]] · [local] · [access] · [ratelimit] · [special] · [cache] · [telemetry] · [api] · [auth] · [cluster] · [agents] · [alerts] · [dns] · [dnssec] · [drill] · [exclusions] · [identify] · [kubernetes] · [probe] · [[router]] · [[rule]] · [[schedule]] · [simulate] · [slo] · [updates] · [upstream_check] · [[zone]]

Top level

KeyTypeDefaultMeaning
config_versioninteger1Schema version of this file (currently 1).

[node]

This node's role and local settings.

KeyTypeDefaultMeaning
data_dirstring"/var/lib/telltale"The only writable directory (snapshots, state.db, query log).
drain_delay_secsinteger0REQ: OPS-007 — on shutdown, keep answering this many seconds after reporting not ready, so load balancers and Kubernetes Services stop sending queries first (the Helm charts set 5). 0 stops as soon as in-flight lookups finish. At most 60.
maintenance_default_secsinteger3600REQ: OPS-010 (T13.4) — how long maintenance lasts when a request doesn't say, in seconds: 60 to 86400 (a day). Maintenance itself is started from the Cluster page, telltale ctl maintenance start, or the API; it's the node's state, not configuration.
namestring""Node name; empty means the hostname.
role"all" | "resolver" | "controller""all"Process role.
workersinteger0UDP worker threads; 0 means min(cgroup CPU limit, cores).

[[listen]]

KeyTypeDefaultMeaning
addrstringAddress and port to bind.
max_connectionsintegerConnections open at once on this listener (default 1,024); more are refused. Not for udp.
max_connections_per_addressintegerConnections open at once from one client address, an IPv6 /64 counting as one (default 32; 0 = no limit), so one host can't take every slot. The address is the client's: from the PROXY header when proxy_protocol is on. Behind a load balancer that hides client addresses (no PROXY protocol, source NAT), raise it or set 0. Not for udp.
pathstringURL path for DoH (default /dns-query; /dns-query/{client-id} also matches).
proto"udp" | "tcp" | "dot" | "doh" | "doh3" | "doq"udp, tcp, dot, doh, doh3, or doq.
proxy_protocoltrue/falsefalseAccept PROXY protocol v2 (TCP-based listeners only).

[listen.tls]

Certificate and key (required for dot/doh/doh3/doq; reloaded on change).

KeyTypeDefaultMeaning
certstring
keystring

[[upstream]]

One upstream resolver (spec/04 §1–2).

KeyTypeDefaultMeaning
argslist of stringREQ: UPS-011 (T7.16) — exec:// only: the plugin program's arguments.
bootstraplist of stringDNS servers used to resolve a hostname URL (UPS-009); default: the system resolvers from /etc/resolv.conf, minus our own listeners. To pin the server address instead, put the IP in the URL and set tls_server_name.
doh_method"post" | "get"REQ: UPS-011 (T9.9) — DoH request method: post (default) or get (RFC 8484 §4.1: the query in ?dns=, which some providers and HTTP caches prefer).
ecsstringREQ: DNS-015 (T7.23) — EDNS Client Subnet: strip (default: clients' subnets are never sent), a subnet to send instead (203.0.113.0/24), so CDNs answer for that area without learning client addresses, or (T9.9) client: each client's own /24 (IPv4) or /56 (IPv6), for public clients only, with answers cached per subnet.
headersobjectExtra HTTP headers for DoH (e.g. an auth token).
http_version"2" | "3" | "auto""auto"DoH HTTP version: "auto", "2", or "3".
idle_timeout_msinteger30000Idle connection timeout in milliseconds.
labelslist of stringFree-form labels.
namestringUnique name referenced by upstream groups.
pool_sizeinteger4Max pooled connections for TCP/DoT/DoH.
proxystringsocks5://host:port or http://host:port proxy (P1).
relaystringREQ: UPS-003 (T9.9) — an anonymized DNSCrypt relay for a DNSCrypt (sdns://) upstream: a relay stamp (sdns://g…) or ip:port. The relay sees who asks but not what; the resolver sees what is asked but not by whom.
spki_pinslist of stringBase64 SHA-256 SPKI pins: the server's key must match one (see docs/running.md, "Pinning and client certificates", for how to get a server's pin).
timeout_msinteger400Per-attempt timeout in milliseconds.
tls_castringREQ: UPS-011 (T7.16) — a PEM file of CA certificates to trust for this upstream.
tls_client_certstringA client certificate and key (PEM files) for servers that require one (mTLS).
tls_client_keystring
tls_insecure_skip_verifytrue/falsefalseDisable certificate verification (dangerous; logs a warning).
tls_server_namestringTLS SNI / certificate name override.
urlstringScheme selects the protocol: udp://, tcp://, tls://, https://, h3://, quic://, sdns://, recursive://, unix://, exec://.
weightinteger1Weight for the weighted strategy.

[upstream.recursive]

REQ: DNS-012 (T7.15) — recursive:// only: how the resolver asks the root servers and below.

KeyTypeDefaultMeaning
case_randomizationtrue/falsefalse0x20: random letter case in queries, checked in answers (spoofing defense; servers that don't echo the case are asked again without it).
ipv6true/falsefalseAsk servers over IPv6 too (needs an IPv6 route to the Internet).
qname_minimizationtrue/falsetrueRFC 9156: each server sees only as much of the name as it needs.

[[upstream_group]]

KeyTypeDefaultMeaning
memberslist of stringUpstream names, in priority order for failover.
namestringUnique group name. Queries that match no route use the group named default.
parallel_fanoutinteger2How many members to race (strategy parallel only).
strategy"failover" | "round_robin" | "weighted" | "fastest" | "parallel""failover"Upstream selection strategy (UPS-005).

[[route]]

Conditional forwarding / routing rule (UPS-007, DNS-017).

KeyTypeDefaultMeaning
dnssec_ntatrue/falsefalseAdd a DNSSEC negative trust anchor for the suffixes (local zones are usually unsigned).
match_grouplist of stringClient group names this route applies to (empty = all).
match_qtypelist of stringQuery types this route applies to, e.g. ["PTR"] (empty = all).
match_suffixlist of stringDomain suffixes (subtree match), e.g. corp.example.com, 168.192.in-addr.arpa.
upstream_groupstringTarget upstream group.

[[list]]

A filter list (spec/05 §1). Exactly one source: url, path, or rules.

KeyTypeDefaultMeaning
enabledtrue/falsetrue
kind"block" | "allow""block"Whether a list's rules block or allow (spec/05 §1). Exception rules (@@) allow regardless.
match"subtree" | "exact""subtree"subtree (default) or exact.
max_bytessize (bytes or "32MiB")Overrides [filter] max_list_bytes for this list.
mode"enforce" | "shadow"REQ: OBS-018 (T11.5) — enforce (the default) blocks; shadow compiles the list but never blocks with it: what it would have blocked is counted (Lists page), so a new list can be judged before it's turned on. Block lists only.
namestringStable ID: lowercase letters, digits, - and _, at most 64 characters. Used in file names, metrics, and the API, so renaming a list re-downloads it.
pathstringLocal file, re-read on every refresh.
refresh_secsintegerOverrides [filter] refresh_secs for this list.
ruleslist of stringInline rules, one per entry, in any supported syntax.
stale_after_daysintegerREQ: OBS-023 (T12.3) — overrides [filter] stale_after_days for this list (0: never stale, for a list that rarely changes on purpose).
urlstringhttps:// (or http://) source, refreshed every refresh_secs.

[filter]

List download and compile settings.

KeyTypeDefaultMeaning
allow_link_local_urlstrue/falsefalseAllow list URLs on link-local addresses (169.254.0.0/16, fe80::/10). Off: they're refused, like cloud-metadata addresses, so a list URL (from the UI, the API, or an agent's pre-save check) can't read what a cloud instance serves there.
compile_memorysize (bytes or "32MiB")"128MiB"Memory for sorting list entries before spilling to disk (spec/05 §3.4).
compile_threadsinteger0Threads for compiling lists (at low CPU priority). 0 = auto: half the cores, between 1 and 4 (2 on a Pi 4, which compiles 2M names in about 6 s).
fetch_concurrencyinteger4Lists downloaded at the same time.
fetch_retriesinteger3Extra attempts after a failed download (network errors, HTTP 5xx, 429).
fetch_timeout_secsinteger120Time limit for one download attempt, including the body.
fsynctrue/falsetrueSync a compiled snapshot's files and directory to disk before it counts as published, so a power cut can't leave a snapshot whose files are short. Costs a few hundred milliseconds per large compile on an SD card, off the query path.
max_invalid_percentinteger50A download is refused, and the previous copy kept, when more than this percentage of its rule-bearing lines is invalid (a JSON error page, a compressed body, or a mirror that changed format parses mostly to invalid lines). Cosmetic and unsupported rules don't count as invalid. 50 = refuse when invalid lines outnumber rules; 0 = refuse any list with an invalid line; 100 = accept anything that isn't empty or HTML.
max_list_bytessize (bytes or "32MiB")"64MiB"Downloads larger than this fail and the previous copy is kept.
max_regexesinteger1000Most regex rules a compiled snapshot keeps (default 1,000; 0 = no limit). Every query that no exact or suffix rule settles is tried against all of them, about a microsecond per thousand, and a few thousand that share a prefix can overwhelm the matcher's automaton: in a synthetic test of patterns sharing a prefix, 1,500 cost 1.6 µs per query and 41 MiB, while 2,000 grew past 5 GiB in a minute. The rules past the limit, in list order, are left out and reported on the Lists page (and by telltale lists compile).
max_shrink_percentinteger50REQ: FLT-004 (ADR-147) — a URL list's download that has lost more than this percentage of the stored copy's lines (a truncated mirror, a source mid-rewrite) isn't used yet: the previous copy stays until a later refresh brings the same content, or a manual refresh accepts it. Lists under 1,000 lines, files, and inline rules aren't held. 100 = off.
refresh_secsinteger86400How often URL and file lists are refreshed (default 24 h, minimum 15 min).
stale_after_daysinteger30REQ: OBS-023 (T12.3) — a URL list whose content hasn't changed in this many days is "stale": its source may be abandoned while still answering (health reason list_stale, alert rule list_stale). 0 = never. Per list: stale_after_days.

[[group]]

A client group (FLT-005, spec/05 §1).

KeyTypeDefaultMeaning
block_answer_ipslist of stringAnswers inside these networks are blocked, whatever the name.
block_ipslist of stringAddresses for block_mode = "custom_ip" (IPv4 for A, IPv6 for AAAA).
block_mode"nxdomain" | "nodata" | "refused" | "null_ip" | "custom_ip""null_ip"How blocked queries are answered (FLT-008).
block_ttlinteger60TTL of block answers (and of the SOA that lets clients cache a negative block).
blocked_serviceslist of stringREQ: FLT-012 (T7.9) — services this group blocks (tiktok, fortnite, ...: see telltale services list), on top of its lists.
colorstringColor for this group in charts and chips (#rrggbb); one is picked when unset.
device_classeslist of "phone" | "tablet" | "laptop" | "desktop" | "tv" | "streaming" | "speaker" | "console" | "camera" | "doorbell" | "plug" | "bulb" | "thermostat" | "hub" | "vacuum" | "printer" | "nas" | "network" | "appliance" | "wearable" | "car" | "other" | "unknown"REQ: OBS-025 (T13.3) — kinds of device that belong in this group (camera, plug, ...): a device identified as one of them, and not in this group yet, gets this group suggested. Nothing moves on its own. A kind can be claimed by one group only.
dns64true/falseREQ: DNS-016 (T7.21) — DNS64 (RFC 6147): IPv6-only devices behind NAT64 get AAAA answers made from A records when a name has no AAAA.
dns64_excludelist of stringREQ: DNS-016 (T9.10) — the exclusion set (RFC 6147 §5.1.4): AAAA records in these IPv6 networks count as missing (so the name gets made-up ones), and A records in these IPv4 networks are never made into AAAA. ::ffff:0:0/96 is always excluded.
dns64_prefixstringThe NAT64 prefix (a /96; default the well-known 64:ff9b::/96).
ede"blocked" | "filtered""blocked"Extended DNS Error on block answers: blocked (15) or filtered (17, RFC 8914: at the client's request, e.g. parental controls).
ede_texttrue/falsetrueInclude which list blocked the name in the EDE text.
filter_aaaatrue/falseREQ: FLT-016 (T12.5) — answer AAAA questions with no data, so the group's devices use IPv4 (a network with broken IPv6). Local records and zones still answer; names blocked stay blocked. Not with dns64.
listslist of stringLists (by name) this group uses. Omitted = every enabled list.
namestringUnique name; default applies to clients that match nothing else.
networkslist of stringNetworks whose devices belong to this group (ADR-050), as IPs or CIDRs, e.g. ["192.168.2.0/24"] for a VLAN of smart-home devices. A device gets the group of the most specific matching network; a [[client]] entry with its own groups still wins.
priorityinteger0When a client is in several groups, the union of their lists applies and other settings come from the highest-priority group.
rebinding_allowlist of stringDomains allowed to answer with private addresses (your own domain, plex.direct). Domains with a [[route]] (conditional forwarding) always are.
rebinding_protectiontrue/falseREQ: FLT-015 (T7.20) — DNS rebinding protection: answers in private, loopback, link-local, or shared (CGNAT) ranges are blocked for names that aren't yours.
safe_searchtrue/falseREQ: FLT-011 (T7.11) — safe search: Google, Bing, DuckDuckGo, Yandex, and Pixabay answer with their safe-search service, and YouTube with Restricted Mode.
scheduleslist of stringREQ: FLT-010 (T7.10) — [[schedule]] names that apply to this group.
upstreamsstringREQ: UPS-007 (T9.25) — the [[upstream_group]] this group's queries go to (instead of default). A [[route]] for a domain, or one that names the group, still wins.
youtube_restrict"strict" | "moderate" | "off"With safe_search: YouTube's restriction (strict, moderate, or off).

[[group.rewrite]]

REQ: FLT-014 (T7.20) — one rewrite.

KeyTypeDefaultMeaning
answerstringAn address (answered as A or AAAA) or a name (answered as a CNAME to it, with its addresses).
domainstringThe name (nas.example.com), or *.example.com for every name under it.

[[client]]

A known device (FLT-006).

KeyTypeDefaultMeaning
groupslist of stringGroups it belongs to, highest priority first. Empty (the default): the group of the device's network ([[group]] networks), else default (ADR-050).
kind"phone" | "tablet" | "laptop" | "desktop" | "tv" | "streaming" | "speaker" | "console" | "camera" | "doorbell" | "plug" | "bulb" | "thermostat" | "hub" | "vacuum" | "printer" | "nas" | "network" | "appliance" | "wearable" | "car" | "other" | "unknown"REQ: OBS-025 (T13.3) — what kind of device it is (camera, tv, ...), which wins over any guess; unknown stops the guessing.
matchlist of stringHow to recognize it: an IP (192.168.1.20), a CIDR (10.0.5.0/24), a MAC (aa:bb:cc:dd:ee:ff), or a client ID from DoH/DoT (id:kids-tablet).
namestringDevice name, shown everywhere and usable in $client= rules.

[clients]

Client identification settings.

KeyTypeDefaultMeaning
infrastructurelist of string[]Addresses that are infrastructure, not devices: Kubernetes node and pod networks, a Docker bridge, a forwarding router. When > 90% of queries come from ≤ 3 of them, the UI warns that client IPs appear masked (OPS-003). Loopback, this host's default gateways, and TELLTALE_NODE_IPS (set by the Helm chart) are always included.
mdnstrue/falsefalseREQ: T8.3 — name unnamed devices by the <name>.local they announce over mDNS (listen only). Needs to be on the LAN: a native install or host networking.
mdns_portinteger5353The mDNS port (5353; shared with avahi or the OS responder).
neighbor_refresh_secsinteger60How often the neighbor table is re-read.
neighbor_tabletrue/falsetrueRead the kernel neighbor table (ARP/NDP) to recognize clients by MAC. Needs host networking to see real devices (inside a bridge network only the gateway shows up).
trust_edns_mac_fromlist of string[]Forwarders whose EDNS MAC option (dnsmasq add-mac) is trusted. Empty = ignore the option: any client could send it and claim another device's identity.

[[record]]

A local DNS record (DNS-010). Value formats by type: A/AAAA: an address; CNAME/PTR: a domain name; TXT: text; MX: "<preference> <exchange>"; SRV: "<priority> <weight> <port> <target>".

KeyTypeDefaultMeaning
namestringOwner name; a leading *. makes it a wildcard for names below it.
node_onlytrue/falseKeep this record on this node only (CLU-006): a cluster primary doesn't share it, and a replica keeps it next to the cluster's records.
ttlintegerTTL in seconds (default: [local] default_ttl).
typestringA, AAAA, CNAME, PTR, TXT, MX, or SRV.
valuestring

[local]

Local data settings (hosts files, PTR generation).

KeyTypeDefaultMeaning
auto_ptrtrue/falsetrueGenerate PTR records for every local A/AAAA record.
default_ttlinteger300TTL for local records without their own.
hosts_fileslist of string[]Hosts files (IP name [alias...] per line) to import as A/AAAA records.

[access]

Who may query (refuse everyone else).

KeyTypeDefaultMeaning
allowed_networkslist of string[…]Clients outside these networks get REFUSED. Default: private (RFC 1918), CGNAT / Tailscale (100.64/10), ULA, link-local, and loopback.

[ratelimit]

Per-client query rate limits (DNS-014).

KeyTypeDefaultMeaning
action"refused" | "drop""refused"What to do with a rate-limited query.
enabledtrue/falsetrue
exemptlist of string["127.0.0.0/8", "::1/128"]Clients never limited.
ipv4_prefixinteger32Count IPv4 clients per /N (32 = per address).
ipv6_prefixinteger64Count IPv6 clients per /N (64 groups a device's rotating privacy addresses).
queriesinteger1000Queries allowed per window_secs per client (token bucket; bursts up to this).
window_secsinteger60

[special]

Special-name handling (RFC 6761 etc.).

KeyTypeDefaultMeaning
block_firefox_canarytrue/falsetrueuse-application-dns.net → NXDOMAIN, so Firefox doesn't switch to its own DoH.
localhosttrue/falsetruelocalhost and *.localhost → 127.0.0.1 / ::1 (RFC 6761 §6.3).
private_ptr_nxdomaintrue/falsetrueReverse lookups for private addresses → NXDOMAIN instead of asking public upstreams, unless a local record or route covers them (Pi-hole "bogus-priv").
refuse_chaostrue/falsetrueCHAOS-class version.bind, id.server, ... → REFUSED.

[cache]

Response cache.

KeyTypeDefaultMeaning
max_bytessize (bytes or "32MiB")"32MiB"Memory budget for cached responses.
max_entriesinteger0Optional cap on entry count (0 = limited by bytes only).
max_ttlinteger86400Maximum TTL served, seconds.
min_ttlinteger0Minimum TTL served, seconds.
negative_ttl_maxinteger3600Cap for negative-answer TTLs, seconds (RFC 2308).
persisttrue/falsefalseDump the cache on shutdown and reload it on start (DNS-009).
prefetchtrue/falsetrueRefresh hot entries before they expire (DNS-008).
prefetch_min_hitsinteger3Minimum hits before an entry is prefetched.
prefetch_threshold_pctinteger10Prefetch when remaining TTL drops below this percentage.
serve_staletrue/falsetrueServe expired answers when upstreams fail (RFC 8767, DNS-007).
servfail_ttlinteger5How long SERVFAIL is cached, seconds (RFC 9520).
stale_answer_client_timeout_msinteger1800Answer from stale data if upstreams haven't replied within this many ms.
stale_answer_ttlinteger30TTL on stale answers, seconds.
stale_max_ageinteger86400How long expired entries are kept for serve-stale, seconds.
warm_namesinteger300REQ: CLU-011 (T8.1) — in a cluster, resolve this many of the cluster's hot names when this node starts, so its first clients find them cached (0: off).

[telemetry]

Telemetry, query log, and metrics.

KeyTypeDefaultMeaning
mode"local" | "ship""local"Where detailed query events go (spec/12 CLU-007).
ring_slotsinteger4096Event ring size per producing thread, in events of ~128 bytes (power of two, at least 1024). The default, 4096 (512 KiB), holds about 170 ms of a fully loaded worker; a full ring drops events (counted), never queries (ADR-026).

[telemetry.anomaly]

Per-device anomaly detection (OBS-013): alert-only, deterministic, explainable.

KeyTypeDefaultMeaning
enabledtrue/falsetrue
ignore_domainslist of string[]Registrable domains never reported (e.g. connectivity checks you expect).
learning_daysinteger7Days a device is watched before it can raise a finding.
max_clientsinteger1024Devices with state; the least recently seen are evicted first.
nxdomain_per_minuteinteger30REQ: OBS-009 (T7.14) — an NXDOMAIN storm: at least this many NXDOMAIN answers to one device in a minute...
nxdomain_percentinteger50...that are at least this percentage of its queries in that minute.
sensitivity"normal" | "low" | "high""normal"How readily the anomaly engine reports (spec/06 §7.1).

[telemetry.dnstap]

REQ: OBS-007 (T7.18) — client queries and responses as dnstap.

KeyTypeDefaultMeaning
addressstringOr a TCP reader: tcp://host:port.
bufferinteger10000Copies held while the reader is slow; beyond, they're dropped.
forwardertrue/falsefalseREQ: OBS-007 (T9.19) — also copy queries to upstreams and their answers (FORWARDER_QUERY, FORWARDER_RESPONSE), sampled the same way.
sample_everyinteger1One query in this many is copied (1 = every query).
socketstringA Unix socket a dnstap reader listens on (/run/dnstap.sock).

[telemetry.metrics]

Prometheus endpoint (OBS-005).

KeyTypeDefaultMeaning
enabledtrue/falsetrue
exemplarstrue/falsetrueREQ: OBS-017 (T11.6) — a scraper that asks for OpenMetrics gets it, with an exemplar (a recent query's trace ID) on each telltale_query_duration_seconds bucket. false: always the plain Prometheus text format.
listenstring"0.0.0.0:9153"
per_clienttrue/falsefalseExport per-client series (high cardinality; capped).
per_client_capinteger100At most this many clients get their own series (default 100); the rest are "other".

[telemetry.otlp]

REQ: OBS-006 (T7.17) — metrics pushed to an OpenTelemetry collector.

KeyTypeDefaultMeaning
endpointstringThe collector's OTLP/HTTP base URL (http://otel-collector:4318); metrics go to <endpoint>/v1/metrics. Unset: no export.
headersobjectExtra headers (an API key for a hosted collector).
interval_secsinteger60Seconds between exports.
traces_sample_everyinteger0REQ: OBS-017 (T11.6) — send 1 in this many queries as a trace to <endpoint>/v1/traces (0: none). Needs endpoint.
traces_slow_msinteger0REQ: OBS-017 (T11.6) — also send every query that took at least this long (ms; 0: none).

[telemetry.qlog]

Query-log store (spec/06 §4).

KeyTypeDefaultMeaning
enabledtrue/falsetrue
flush_interval_secsinteger10Flush the in-progress block after this many seconds.
fsynctrue/falsefalsefsync each block flush.
privacy_levelinteger00 full; 1 hide domains; 2 hide domains + clients; 3 aggregates only.
retention_bytessize (bytes or "32MiB")"2GiB"Delete oldest segments beyond this total size.
retention_daysinteger30Delete segments older than this many days.

[telemetry.ship]

Ship mode ([telemetry] mode = "ship", CLU-007, ADR-055).

KeyTypeDefaultMeaning
buffer_bytessize (bytes or "32MiB")"64MiB"Most query log kept here while it can't be delivered; the oldest goes first beyond it. Put [node] data_dir on tmpfs (or accept the writes) on an SD card.
interval_secsinteger300Close and ship the query log at least this often, in seconds (it's searchable from this node until then, through the cluster).
tostring(none)The node that stores this node's query log: a node ID or site. Default: the primary.

[[telemetry.sink]]

REQ: OBS-010 (T7.13, spec/06 §5) — one event sink: every query event (after the query log's privacy level) as one JSON object, written off the query path; a sink that can't keep up drops events (counted), never slows DNS.

KeyTypeDefaultMeaning
addressstringsyslog: udp://host:514, tcp://host:514, or (T9.11) tls://host:6514 (RFC 5425).
batchinteger500webhook: events per batch, and the most seconds an event waits for one.
facilityinteger16syslog: the facility number (16 = local0).
flush_secsinteger5
format"json_lines" | "json_array" | "otlp_logs""json_lines"webhook: json_lines (Loki-style collectors, Vector, Fluent Bit) or json_array.
keepinteger3file: rotated files kept.
max_bufferinteger10000Events held while the destination is slow or down; beyond it, events are dropped.
max_bytessize (bytes or "32MiB")"100MiB"file: rotate when the file reaches this size (to <path>.1, ...).
namestringUnique name (logs and metrics).
pathstringfile: the file to append to.
spill_max_bytessize (bytes or "32MiB")REQ: OBS-010 (T9.11) — webhook: keep batches the collector refused (after the retries) on disk, up to this size, and send them once it's back. Off by default.
statuseslist of stringOnly these statuses (blocked, cached, ...); empty means all.
tls_castringsyslog over tls://: a PEM file of CAs trusted besides the public roots (a private collector's CA).
token_filestringwebhook: a file holding a token sent as Authorization: <token_scheme> <token>.
token_schemestring"Bearer"webhook: the scheme for the token (Bearer; Splunk HEC uses Splunk).
type"file" | "syslog" | "webhook"file (JSON lines, rotated), syslog (RFC 5424), or webhook (batched HTTP POST).
urlstringwebhook: where batches are posted (newline-delimited JSON, or a JSON array with format = "json_array").

[api]

The REST API (and, later, the web UI).

KeyTypeDefaultMeaning
enabledtrue/falsetrue
listenstring"0.0.0.0:8053"Where the API listens (default 0.0.0.0:8053).
trusted_proxieslist of string[]Reverse proxies or ingress controllers in front of the API (e.g. the pod network, 10.0.0.0/8). For requests from them, the client address comes from X-Forwarded-For (sign-in lockouts, break-glass networks, the audit log), and X-Forwarded-Proto: https counts as HTTPS (HTTP Basic, the Secure cookie flag); from anyone else that header is ignored. Loopback always counts for X-Forwarded-Proto, so a TLS terminator on the same host needs nothing here. Default: none (the connection's address is the client).

[api.tls]

REQ: API-003 (ADR-149) — serve the web UI and API over HTTPS on listen: with { self_signed = true } a certificate is made on first start and kept in the data directory (browsers warn until you trust it); with { cert = "...", key = "..." }, your own PEM files (re-read within 10 s of a change, like the DNS listeners'). A plain http:// request on the same port is redirected to https://, except the health probes. Default: none (plain HTTP, for a TLS proxy or ingress in front).

KeyTypeDefaultMeaning
certstringYour certificate chain (PEM, the server's first). With key; not with self_signed.
keystringIts private key (PEM).
self_signedtrue/falsefalseMake a self-signed certificate on first start (<data_dir>/api-tls/), for this host's name and addresses. Delete that directory to make a new one.

[auth]

Sign-in: sessions, HTTP Basic, two-factor policy (API-003).

KeyTypeDefaultMeaning
allow_insecure_basictrue/falsefalseAccept HTTP Basic over plain HTTP. Off: Basic only behind HTTPS (a proxy that sends X-Forwarded-Proto: https).
audit_retention_daysinteger365Days the audit log keeps entries (default 365; 0 keeps them forever). Older entries are removed hourly; the rest still verifies from a checkpoint, and each trim is itself recorded (audit.trim).
session_idle_hoursinteger24... or after this long unused (default 24).
session_ttl_hoursinteger168A UI session ends this long after sign-in (default 168 = 7 days).
totp_required_roleslist of "viewer" | "operator" | "admin"[]Roles that must use two-factor sign-in (TOTP), e.g. ["admin"].

[auth.oidc]

Sign-in through OpenID Connect providers (Keycloak, Authentik, ...; API-004).

KeyTypeDefaultMeaning
allowed_admin_networkslist of string[…]Where break-glass admin sign-in is allowed. Default: private networks and loopback.
disable_local_logintrue/falsefalseTurn off password sign-in (and HTTP Basic) except for admins signing in from allowed_admin_networks (break-glass, for when the provider is down).
mcp_audiencestring""The audience (aud) MCP access tokens must carry. Default: <public_url>/mcp.
mcp_providerstring""REQ: AGT-008 (T7.4) — MCP clients (AI assistants) sign in through this provider (its id) with OAuth 2.1: TelltaleDNS accepts the provider's JWT access tokens issued for mcp_audience, as an agent with the scopes the user consented to. Empty: off.
public_urlstring""Where people open the UI, e.g. https://dns.example.com or http://192.168.1.2:8053. Each provider's redirect URI is <public_url>/api/v1/auth/oidc/<id>/callback, and after sign-out the provider returns to <public_url>/; register both with it.

[[auth.oidc.provider]]

One OpenID Connect provider.

KeyTypeDefaultMeaning
client_idstring
client_secretstring(none)The client secret, or ...
client_secret_filestring(none)... a file holding it (a mounted Kubernetes Secret). Neither: a public client (PKCE).
default_role"viewer" | "operator" | "admin"(none)Role for users whose groups match no rule. Unset: they can't sign in.
groups_claimstring"groups"Claim listing the user's groups or roles (groups, roles, realm_access.roles). Default groups.
idstringShort ID used in URLs: lowercase letters, digits, and - (keycloak, authentik).
issuerstringIssuer URL (discovery is <issuer>/.well-known/openid-configuration).
namestring""Button label ("Sign in with ..."). Default: the ID.
require_verified_emailtrue/falsefalseRefuse users whose provider says their email isn't verified.
scopeslist of string["openid", "profile", "email"]Scopes to request. Default: openid, profile, email.
username_claimstring"preferred_username"Claim that becomes the TelltaleDNS username (dotted paths reach into objects). Default preferred_username; falls back to email, then sub.

[[auth.oidc.provider.role]]

Users in group get role.

KeyTypeDefaultMeaning
groupstring
role"viewer" | "operator" | "admin"What a user may do (spec/08 §6).

[cluster]

Cluster identity (full cluster settings arrive with M5).

KeyTypeDefaultMeaning
bootstrap_secret_filestring(none)A shared join secret, in a file (e.g. a Kubernetes Secret). A node holding the cluster key accepts it like a join token that never expires; a node with join_url joins with it on first start.
config_sourcestring"file"How this node's own configuration is managed (ADR-048): file (edited on the node or in its UI) or gitops (rendered from Git, e.g. by the Helm chart under Argo CD). In a cluster whose config authority is gitops, only gitops nodes may become primary.
eligibletrue/falsetrueWhether this node may become the cluster primary.
ephemeraltrue/falsefalseJoin as an ephemeral member (CLU-009): never primary, never a voter, dropped from the registry ephemeral_ttl_secs after it was last heard from, shown grouped by site. An ephemeral node reports ready only once it has the cluster's configuration.
ephemeral_ttl_secsinteger600How long the primary keeps an ephemeral member it no longer hears from.
join_urlstring(none)On first start, join the cluster at this URL with bootstrap_secret_file (resolver pods). The node checks the primary proves it knows the secret before trusting its CA.
listenstring"0.0.0.0:8443"The cluster port (mTLS, node-to-node; REQ: CLU-001). Opened only once this node has joined or created a cluster (telltale cluster init|join). Peers dial the URLs given with --advertise, so map this port to those.
namestring"telltale"Cluster name.
sitestring"default"Site label used to group nodes in the UI (e.g. home-pi, k8s).

[cluster.git]

Take the cluster's shared configuration from a Git repository (ADR-049): the primary fetches it, validates it, and publishes it to every node. Set it on every node that may become primary.

KeyTypeDefaultMeaning
allow_rewindtrue/falsefalseAccept a commit that doesn't descend from the one in use (a force-push or rewind).
allowed_signers_filestring(none)git's allowed-signers format: <principal> ssh-ed25519 <key> per line.
credentials_filestring(none)A file with an access token (or user:password) for private repositories.
max_bytessize (bytes or "32MiB")"1MiB"Largest file accepted.
pathstringThe TelltaleDNS file with the shared settings, e.g. telltale/shared.toml.
poll_secsinteger60How often to check the ref, in seconds.
refstring"main"Branch, tag, or commit ID.
repostringThe repository's HTTPS URL, e.g. https://github.com/me/homelab.
require_signedtrue/falsefalseAccept only commits SSH-signed by a key in allowed_signers_file.
webhook_secret_filestring(none)A file with the secret for POST /api/v1/hooks/git (GitHub's X-Hub-Signature-256), which checks the ref at once instead of at the next poll.

[cluster.init]

Create a cluster on first start when this node isn't in one (CLU-009; the Helm chart's controller). Same as telltale cluster init.

KeyTypeDefaultMeaning
advertiselist of stringURLs other nodes reach this node's cluster port at.
config_authoritystring"api"api or gitops (ADR-048).

[cluster.rollout]

REQ: CLU-013 (T13.2) — staged rollouts: new configuration reaches the canary nodes first and everyone else after a bake under a guard. Off until canaries is set. Shared by the whole cluster (the one part of [cluster] the primary replicates); also in Settings → Cluster → Staged rollouts.

KeyTypeDefaultMeaning
bake_secsinteger300How long the canaries run a new version before everyone else gets it, in seconds (at least 5).
canarieslist of string[]The nodes that get a new version first: node IDs, site:<name>, or ephemeral (the resolver pods). Empty (the default): no rollouts, every node gets a change at once.
fail_on_disconnecttrue/falsetrueA canary that disconnects for over a minute after applying the version fails it.
historyinteger20Published versions kept (and their files protected) for pinning back to.
max_bake_secsinteger3600With require_traffic: the longest bake, in seconds.
require_traffictrue/falsefalseJudge only with at least 50 answers in the bake: keep baking until then, up to max_bake_secs, then fail. Off: too few answers passes.
servfail_pctnumber5.0The guard fails a version when the canaries' SERVFAIL share over the bake is above the larger of this (percent) and their share before plus 2 points.

[agents]

AI agents and automation using agent tokens (AGT-004, AGT-009). Shared across a cluster, so enabled = false switches every agent off everywhere.

KeyTypeDefaultMeaning
enabledtrue/falsetrueThe kill switch: false refuses every agent token at once (users and their own tokens are unaffected).
rate_per_minuteinteger120Requests per minute per agent token, unless the token sets its own.
require_approvaltrue/falsefalseREQ: AGT-007 — agents' planned changes wait for an operator's approval ("Agent changes" in the web UI) before apply_plan succeeds.

[alerts]

REQ: OBS-010 (T7.12) — alert rules and where they go.

KeyTypeDefaultMeaning
interval_secsinteger30How often rules are checked, in seconds.

[[alerts.destination]]

One place alerts go.

KeyTypeDefaultMeaning
fromstringREQ: OBS-010 (T9.4) — email: the sender address.
namestringUnique name, used in a rule's to.
password_filestring
tls_castringemail: a PEM file with the mail server's certificate (or its CA), for a private server; public ones are trusted already.
tolist of stringemail: the recipients.
token_filestringA file holding the token (ntfy access token, Gotify application token), so it stays out of the config and Git.
type"webhook" | "ntfy" | "gotify" | "slack" | "email"webhook (JSON POST), ntfy, gotify, slack (Slack-compatible webhooks: Slack, Mattermost, Discord's /slack endpoint), or email.
urlstringThe webhook URL, the ntfy topic URL (https://ntfy.sh/my-topic), the Gotify server, or the mail server: smtp://smtp.gmail.com:587 (STARTTLS), smtps://host:465 (TLS), or smtp+insecure://127.0.0.1:1025 (no encryption: a local mail catcher only).
usernamestringemail: the mail server account, and a file holding its password (a Gmail app password, for example). Sent only over an encrypted connection.

[[alerts.rule]]

One alert rule.

KeyTypeDefaultMeaning
enabledtrue/falsetrueOff: the rule is kept but not checked.
for_secsinteger60How long it must hold before the alert goes out (not for anomaly and update_available, which go out once each).
namestringUnique name, shown in every alert.
thresholdnumberservfail_rate: the percentage of queries (default 5).
tolist of stringDestination names.
when"upstream_down" | "node_down" | "list_failing" | "list_stale" | "anomaly" | "servfail_rate" | "update_available" | "sync_lag" | "new_device" | "disk_full" | "plan_pending" | "slo_burn" | "probe_failing" | "cert_expiring" | "maintenance" | "rollout_failed" | "cluster_pinned" | "chatty_loop" | "drill_report"The condition.

[dns]

REQ: DNS-005 — settings of the DNS answers themselves.

KeyTypeDefaultMeaning
edns_payloadinteger1232The UDP payload size advertised in EDNS(0) (RFC 6891), and the largest UDP answer sent to a client that advertises at least as much; 512 to 4096 (the UDP workers' send buffer). 1232 (the DNS Flag Day 2020 value) avoids IP fragmentation on almost every path; raise it on a network known to carry larger datagrams. Needs a restart.
nsidtrue/falsefalseREQ: DNS-021 (T12.4) — answer the NSID option (RFC 5001) with this node's name, so dig +nsid says which node or pod answered (behind a shared address, say). Only for queries that ask for it.

[dnssec]

DNSSEC validation of forwarded answers (DNS-011).

KeyTypeDefaultMeaning
aggressive_nsectrue/falsetrueREQ: DNS-011 (T9.16) — RFC 8198: answer names that validated NSEC records prove don't exist (or lack the type) from the cache, without asking upstream. On by default.
mode"off" | "validate" | "permissive""off"How DNSSEC is checked (DNS-011, spec/03 §5).
negative_trust_anchorslist of string[]Domains not validated (negative trust anchors, RFC 7646), e.g. an internal domain forwarded to a server that isn't signed. Routes with dnssec_nta = true add theirs.
trust_anchors_filestringREQ: DNS-011 (T9.8) — root trust anchors from a file instead of the built-in ones (KSK-2017 and KSK-2024): DNSKEY records in zone-file form, as dig DNSKEY . +noall +answer prints them or Unbound's root.key holds. Read at start and on reload; a file that can't be read or has no keys leaves the built-in anchors in place (logged).

[drill]

REQ: CLU-014 (T15.2, owner 2026-10-19) — scheduled failover drills: once a month, at night, the nodes named here drill themselves and the report goes out as a drill_report alert.

KeyTypeDefaultMeaning
clientslist of stringDrill these devices (addresses, device names, or workloads).
for_secsinteger30How long, in seconds (15 to 300).
groupstringDrill one group's devices; with neither group nor clients, everyone.
hourinteger3The local hour it starts in (0 to 23; default 3, at night).
mode"drop" | "refused""drop"drop (default) or refused.
nodeslist of string[]The nodes that drill themselves on the schedule: sites, pod names, or node IDs.
schedule"off" | "monthly""off"off (default) or monthly.
tzstringThe time zone of hour (IANA, e.g. Europe/Berlin); default the system's.

[exclusions]

REQ: OBS-022 (T12.1) — names and devices kept out of the query log and analytics. Always serialized, like every top-level section: a replica merges the primary's shared sections into its own by key (shared::with_shared).

KeyTypeDefaultMeaning
clientslist of string[]Devices left out: addresses or networks.
enabledtrue/falsetrueTurns the exclusions on or off without losing the lists.
nameslist of string[]Names left out, each with its subdomains (ntp.org covers pool.ntp.org; a leading *. means the same).

[identify]

REQ: OBS-025 (T13.3) — what kind of device each address is, guessed from its MAC vendor, the names it announces, and the domains it talks to.

KeyTypeDefaultMeaning
enabledtrue/falsetrueOff: no guesses (devices you named keep their names; kind still shows).
max_clientsinteger1024Most devices one node identifies (the most recently seen, over the last day).
signatures_filestring""More signatures, in the shape of the shipped catalog ([[device]] tables). One with a shipped id replaces it; enabled = false on it removes it.

[kubernetes]

REQ: FLT-017 (T15.1) — Kubernetes workloads as devices: the controller watches pods and names each pod address by its workload (media/deploy/jellyfin).

KeyTypeDefaultMeaning
default_groupstring""The group of a workload no label or map rule places; empty: its network's group, or default, as for any unknown device.
enabledtrue/falsefalseWatch pods and namespaces (read-only) and name pod addresses by their workload.
exclude_namespaceslist of string[]Namespaces never watched (their pods stay plain addresses).
labelstring"telltaledns.io/group"The pod or namespace label that names a workload's group.
namespaceslist of string[]Namespaces to watch; empty: all.

[[kubernetes.map]]

REQ: FLT-017 — one [[kubernetes.map]] rule: a workload whose namespace matches the glob (media*, *), and kind and name when given, is in group.

KeyTypeDefaultMeaning
groupstringThe group (it must exist).
kindstringdeploy, sts, ds, job, cronjob, rs, or pod; empty: any.
namestringOne workload's name; empty: any.
namespacestringNamespace glob (* matches any run of characters).

[probe]

REQ: OBS-020 (T11.3) — synthetic probes: each DNS listener asked through its own protocol on a schedule, plus extra targets; TLS certificate expiry.

KeyTypeDefaultMeaning
cert_warn_daysinteger14A listener certificate expiring within this many days degrades the health level.
enabledtrue/falsetrue
interval_secsinteger30Seconds between rounds (at least 5).
targetslist of stringExtra addresses to probe, by IP: udp://192.168.5.112:53 (a load balancer's address), tcp://…, tls://…:853, https://…:443/dns-query, h3://…, quic://…:853. Certificates aren't verified (the listeners' own expiry is checked from their files).
timeout_msinteger2000How long one probe may take, in milliseconds (100 to 10000).

[[router]]

REQ: T8.2 — a router whose DHCP clients name devices.

KeyTypeDefaultMeaning
api_key_filestringUniFi: an API key (UniFi Network 9+), or OPNsense: the API key. A file holding it.
api_secret_filestringOPNsense: the API secret (a file).
interval_secsinteger300Seconds between reads.
namestringUnique name (logs, the leases' source).
password_filestring
sitestringUniFi: the site (default default).
tls_castringA PEM file with the console's certificate (or its CA), for self-signed consoles.
tls_insecure_skip_verifytrue/falseSkip certificate verification (dangerous; logs a warning).
tls_server_namestringThe name the console's certificate is checked against, when url uses another (its address). A UniFi console's own certificate names unifi.local: with that here and the certificate as tls_ca, it's verified.
type"unifi" | "opnsense"unifi (UniFi OS consoles and the Network application) or opnsense.
urlstringThe console or controller: https://192.168.1.1 (UniFi OS), https://controller:8443 (Network application), https://opnsense.lan.
usernamestringUniFi without an API key: a local (read-only is enough) user and a file holding its password.

[[rule]]

REQ: FLT-005, FLT-006 (T6.12, ADR-067) — a quick rule: allow or block a domain and its subdomains for chosen devices, groups, or everyone, optionally until a time.

KeyTypeDefaultMeaning
action"allow" | "block"What a quick rule does.
createdstringWhen it was made (RFC 3339), filled in by the API.
created_bystringWho made it (a user or agent:<token>), filled in by the API.
deviceslist of stringDevices it applies to: device names ([[client]] name), IPs, or CIDRs.
domainstringThe domain; its subdomains are included (example.com covers www.example.com).
expiresstringWhen it stops applying (RFC 3339, e.g. 2026-10-05T21:30:00Z). Absent: never.
groupslist of stringGroups it applies to. With neither devices nor groups, it applies to everyone.
idstringA unique ID (the API assigns one; in a file, any unique name).
notestringWhy it exists ("Mom's game"), shown with every decision it makes.

[[schedule]]

REQ: FLT-010 (T7.10, spec/05 §3) — a weekly schedule: during its windows, the groups that name it block everything, or get extra lists or blocked services.

KeyTypeDefaultMeaning
action"block_all" | "enable_lists" | "block_services"What happens during the windows.
listslist of stringenable_lists: these lists apply during the windows, and not outside them.
namestringUnique name, used in a group's schedules.
serviceslist of stringblock_services: these services (telltale services list) are blocked during the windows.
tzstringIANA time zone, e.g. Europe/Berlin. Default: the system's (TZ, /etc/localtime), else UTC.

[[schedule.window]]

One weekly window: the days it starts on, and local start and end times. An end at or before the start runs past midnight (21:00 to 07:00).

KeyTypeDefaultMeaning
dayslist of stringmon … sun, or weekdays, weekends, daily.
endstringHH:MM, 24-hour (24:00 is the end of the day).
startstringHH:MM, 24-hour.

[simulate]

REQ: OBS-024 (T13.1) — change simulation over the query log.

KeyTypeDefaultMeaning
default_windowstring"24h"The window when a request doesn't name one: 30m, 24h, 7d (at most 7 days).
enabledtrue/falsetrueOff: every simulation answers "unavailable" (dry runs still work, without it).
max_rowsinteger2000000Most logged queries one simulation reads (at least 1,000), newest first.
max_secsinteger20Longest a simulation reads the query log, in seconds (1 to 300); past it the answer is partial (the newest queries).
plans_by_defaulttrue/falsefalseAI agents' plans simulate only when the agent asks (simulate on a plan_* tool); on, every plan carries one over default_window. A person sets this, never an agent.

[slo]

REQ: OBS-016 (T11.1) — service-level objectives: how reliably and how fast DNS should answer, the error budget that leaves, and how fast it's being spent.

KeyTypeDefaultMeaning
availability_targetnumber99.9Percent of answers that must not be SERVFAIL (default 99.9).
enabledtrue/falsetrueOff: no objectives (nothing to show, and slo_burn alerts never fire).
latency_msinteger250The latency objective's threshold: one of 1, 5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000 ms (the bucket bounds of telltale_query_duration_seconds). Default 250.
latency_targetnumber99.0Percent of answers that must arrive within latency_ms (default 99).
window_daysinteger30Days the error budget covers (1 to 90; default 30). The burn-rate alerts are tuned for 30.

[updates]

Whether to check for newer builds (OPS-004, ADR-046).

KeyTypeDefaultMeaning
checktrue/falsetrueRead this build's channel's signed release index once a day to see whether a newer build exists. false: nothing leaves the node, and the status is off.
index_urlstringWhere to read the index instead of GitHub (a mirror); its signature is still checked with the release key built into the binary.

[upstream_check]

REQ: OBS-019 (T11.4) — second opinions: a sample of forwarded questions asked again of another upstream, and the answers compared. Off by default.

KeyTypeDefaultMeaning
keepinteger100Disagreements kept for the API and the Upstreams page, newest first (1 to 1000).
referencestringThe upstream group asked for second opinions. Unset: another upstream of the group that answered (a group with one upstream gets none).
sample_everyinteger0One forwarded question in this many gets a second opinion (0 = off, the default).

[[zone]]

REQ: DNS-018 (T7.22) — an authoritative zone: every name under name is answered from it (names it doesn't have get NXDOMAIN), for everyone or only groups.

KeyTypeDefaultMeaning
filestringAn RFC 1035 zone file (BIND, Technitium's export, PowerDNS).
groupslist of stringOnly these groups see this zone (split horizon); empty: everyone.
namestringThe zone's apex (home.example.com).
negative_ttlinteger300TTL of negative answers (NXDOMAIN, no data), in seconds.

[[zone.record]]

A local DNS record (DNS-010). Value formats by type: A/AAAA: an address; CNAME/PTR: a domain name; TXT: text; MX: "<preference> <exchange>"; SRV: "<priority> <weight> <port> <target>".

KeyTypeDefaultMeaning
namestringOwner name; a leading *. makes it a wildcard for names below it.
node_onlytrue/falseKeep this record on this node only (CLU-006): a cluster primary doesn't share it, and a replica keeps it next to the cluster's records.
ttlintegerTTL in seconds (default: [local] default_ttl).
typestringA, AAAA, CNAME, PTR, TXT, MX, or SRV.
valuestring