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.jsonand point your TOML extension at it. - Environment variables override the file:
TELLTALE_CACHE_MAX_TTL=3600sets[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
| Key | Type | Default | Meaning |
|---|---|---|---|
config_version | integer | 1 | Schema version of this file (currently 1). |
[node]
This node's role and local settings.
| Key | Type | Default | Meaning |
|---|---|---|---|
data_dir | string | "/var/lib/telltale" | The only writable directory (snapshots, state.db, query log). |
drain_delay_secs | integer | 0 | REQ: 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_secs | integer | 3600 | REQ: 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. |
name | string | "" | Node name; empty means the hostname. |
role | "all" | "resolver" | "controller" | "all" | Process role. |
workers | integer | 0 | UDP worker threads; 0 means min(cgroup CPU limit, cores). |
[[listen]]
| Key | Type | Default | Meaning |
|---|---|---|---|
addr | string | Address and port to bind. | |
max_connections | integer | Connections open at once on this listener (default 1,024); more are refused. Not for udp. | |
max_connections_per_address | integer | Connections 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. | |
path | string | URL 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_protocol | true/false | false | Accept PROXY protocol v2 (TCP-based listeners only). |
[listen.tls]
Certificate and key (required for dot/doh/doh3/doq; reloaded on change).
| Key | Type | Default | Meaning |
|---|---|---|---|
cert | string | ||
key | string |
[[upstream]]
One upstream resolver (spec/04 §1–2).
| Key | Type | Default | Meaning |
|---|---|---|---|
args | list of string | REQ: UPS-011 (T7.16) — exec:// only: the plugin program's arguments. | |
bootstrap | list of string | DNS 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). | |
ecs | string | REQ: 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. | |
headers | object | Extra HTTP headers for DoH (e.g. an auth token). | |
http_version | "2" | "3" | "auto" | "auto" | DoH HTTP version: "auto", "2", or "3". |
idle_timeout_ms | integer | 30000 | Idle connection timeout in milliseconds. |
labels | list of string | Free-form labels. | |
name | string | Unique name referenced by upstream groups. | |
pool_size | integer | 4 | Max pooled connections for TCP/DoT/DoH. |
proxy | string | socks5://host:port or http://host:port proxy (P1). | |
relay | string | REQ: 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_pins | list of string | Base64 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_ms | integer | 400 | Per-attempt timeout in milliseconds. |
tls_ca | string | REQ: UPS-011 (T7.16) — a PEM file of CA certificates to trust for this upstream. | |
tls_client_cert | string | A client certificate and key (PEM files) for servers that require one (mTLS). | |
tls_client_key | string | ||
tls_insecure_skip_verify | true/false | false | Disable certificate verification (dangerous; logs a warning). |
tls_server_name | string | TLS SNI / certificate name override. | |
url | string | Scheme selects the protocol: udp://, tcp://, tls://, https://, h3://, quic://, sdns://, recursive://, unix://, exec://. | |
weight | integer | 1 | Weight for the weighted strategy. |
[upstream.recursive]
REQ: DNS-012 (T7.15) — recursive:// only: how the resolver asks the root servers and below.
| Key | Type | Default | Meaning |
|---|---|---|---|
case_randomization | true/false | false | 0x20: random letter case in queries, checked in answers (spoofing defense; servers that don't echo the case are asked again without it). |
ipv6 | true/false | false | Ask servers over IPv6 too (needs an IPv6 route to the Internet). |
qname_minimization | true/false | true | RFC 9156: each server sees only as much of the name as it needs. |
[[upstream_group]]
| Key | Type | Default | Meaning |
|---|---|---|---|
members | list of string | Upstream names, in priority order for failover. | |
name | string | Unique group name. Queries that match no route use the group named default. | |
parallel_fanout | integer | 2 | How 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).
| Key | Type | Default | Meaning |
|---|---|---|---|
dnssec_nta | true/false | false | Add a DNSSEC negative trust anchor for the suffixes (local zones are usually unsigned). |
match_group | list of string | Client group names this route applies to (empty = all). | |
match_qtype | list of string | Query types this route applies to, e.g. ["PTR"] (empty = all). | |
match_suffix | list of string | Domain suffixes (subtree match), e.g. corp.example.com, 168.192.in-addr.arpa. | |
upstream_group | string | Target upstream group. |
[[list]]
A filter list (spec/05 §1). Exactly one source: url, path, or rules.
| Key | Type | Default | Meaning |
|---|---|---|---|
enabled | true/false | true | |
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_bytes | size (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. | |
name | string | Stable ID: lowercase letters, digits, - and _, at most 64 characters. Used in file names, metrics, and the API, so renaming a list re-downloads it. | |
path | string | Local file, re-read on every refresh. | |
refresh_secs | integer | Overrides [filter] refresh_secs for this list. | |
rules | list of string | Inline rules, one per entry, in any supported syntax. | |
stale_after_days | integer | REQ: OBS-023 (T12.3) — overrides [filter] stale_after_days for this list (0: never stale, for a list that rarely changes on purpose). | |
url | string | https:// (or http://) source, refreshed every refresh_secs. |
[filter]
List download and compile settings.
| Key | Type | Default | Meaning |
|---|---|---|---|
allow_link_local_urls | true/false | false | Allow 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_memory | size (bytes or "32MiB") | "128MiB" | Memory for sorting list entries before spilling to disk (spec/05 §3.4). |
compile_threads | integer | 0 | Threads 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_concurrency | integer | 4 | Lists downloaded at the same time. |
fetch_retries | integer | 3 | Extra attempts after a failed download (network errors, HTTP 5xx, 429). |
fetch_timeout_secs | integer | 120 | Time limit for one download attempt, including the body. |
fsync | true/false | true | Sync 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_percent | integer | 50 | A 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_bytes | size (bytes or "32MiB") | "64MiB" | Downloads larger than this fail and the previous copy is kept. |
max_regexes | integer | 1000 | Most 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_percent | integer | 50 | REQ: 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_secs | integer | 86400 | How often URL and file lists are refreshed (default 24 h, minimum 15 min). |
stale_after_days | integer | 30 | REQ: 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).
| Key | Type | Default | Meaning |
|---|---|---|---|
block_answer_ips | list of string | Answers inside these networks are blocked, whatever the name. | |
block_ips | list of string | Addresses 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_ttl | integer | 60 | TTL of block answers (and of the SOA that lets clients cache a negative block). |
blocked_services | list of string | REQ: FLT-012 (T7.9) — services this group blocks (tiktok, fortnite, ...: see telltale services list), on top of its lists. | |
color | string | Color for this group in charts and chips (#rrggbb); one is picked when unset. | |
device_classes | list 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. | |
dns64 | true/false | REQ: 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_exclude | list of string | REQ: 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_prefix | string | The 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_text | true/false | true | Include which list blocked the name in the EDE text. |
filter_aaaa | true/false | REQ: 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. | |
lists | list of string | Lists (by name) this group uses. Omitted = every enabled list. | |
name | string | Unique name; default applies to clients that match nothing else. | |
networks | list of string | Networks 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. | |
priority | integer | 0 | When a client is in several groups, the union of their lists applies and other settings come from the highest-priority group. |
rebinding_allow | list of string | Domains allowed to answer with private addresses (your own domain, plex.direct). Domains with a [[route]] (conditional forwarding) always are. | |
rebinding_protection | true/false | REQ: 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_search | true/false | REQ: FLT-011 (T7.11) — safe search: Google, Bing, DuckDuckGo, Yandex, and Pixabay answer with their safe-search service, and YouTube with Restricted Mode. | |
schedules | list of string | REQ: FLT-010 (T7.10) — [[schedule]] names that apply to this group. | |
upstreams | string | REQ: 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.
| Key | Type | Default | Meaning |
|---|---|---|---|
answer | string | An address (answered as A or AAAA) or a name (answered as a CNAME to it, with its addresses). | |
domain | string | The name (nas.example.com), or *.example.com for every name under it. |
[[client]]
A known device (FLT-006).
| Key | Type | Default | Meaning |
|---|---|---|---|
groups | list of string | Groups 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. | |
match | list of string | How 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). | |
name | string | Device name, shown everywhere and usable in $client= rules. |
[clients]
Client identification settings.
| Key | Type | Default | Meaning |
|---|---|---|---|
infrastructure | list 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. |
mdns | true/false | false | REQ: 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_port | integer | 5353 | The mDNS port (5353; shared with avahi or the OS responder). |
neighbor_refresh_secs | integer | 60 | How often the neighbor table is re-read. |
neighbor_table | true/false | true | Read 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_from | list 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>".
| Key | Type | Default | Meaning |
|---|---|---|---|
name | string | Owner name; a leading *. makes it a wildcard for names below it. | |
node_only | true/false | Keep 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. | |
ttl | integer | TTL in seconds (default: [local] default_ttl). | |
type | string | A, AAAA, CNAME, PTR, TXT, MX, or SRV. | |
value | string |
[local]
Local data settings (hosts files, PTR generation).
| Key | Type | Default | Meaning |
|---|---|---|---|
auto_ptr | true/false | true | Generate PTR records for every local A/AAAA record. |
default_ttl | integer | 300 | TTL for local records without their own. |
hosts_files | list of string | [] | Hosts files (IP name [alias...] per line) to import as A/AAAA records. |
[access]
Who may query (refuse everyone else).
| Key | Type | Default | Meaning |
|---|---|---|---|
allowed_networks | list 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).
| Key | Type | Default | Meaning |
|---|---|---|---|
action | "refused" | "drop" | "refused" | What to do with a rate-limited query. |
enabled | true/false | true | |
exempt | list of string | ["127.0.0.0/8", "::1/128"] | Clients never limited. |
ipv4_prefix | integer | 32 | Count IPv4 clients per /N (32 = per address). |
ipv6_prefix | integer | 64 | Count IPv6 clients per /N (64 groups a device's rotating privacy addresses). |
queries | integer | 1000 | Queries allowed per window_secs per client (token bucket; bursts up to this). |
window_secs | integer | 60 |
[special]
Special-name handling (RFC 6761 etc.).
| Key | Type | Default | Meaning |
|---|---|---|---|
block_firefox_canary | true/false | true | use-application-dns.net → NXDOMAIN, so Firefox doesn't switch to its own DoH. |
localhost | true/false | true | localhost and *.localhost → 127.0.0.1 / ::1 (RFC 6761 §6.3). |
private_ptr_nxdomain | true/false | true | Reverse lookups for private addresses → NXDOMAIN instead of asking public upstreams, unless a local record or route covers them (Pi-hole "bogus-priv"). |
refuse_chaos | true/false | true | CHAOS-class version.bind, id.server, ... → REFUSED. |
[cache]
Response cache.
| Key | Type | Default | Meaning |
|---|---|---|---|
max_bytes | size (bytes or "32MiB") | "32MiB" | Memory budget for cached responses. |
max_entries | integer | 0 | Optional cap on entry count (0 = limited by bytes only). |
max_ttl | integer | 86400 | Maximum TTL served, seconds. |
min_ttl | integer | 0 | Minimum TTL served, seconds. |
negative_ttl_max | integer | 3600 | Cap for negative-answer TTLs, seconds (RFC 2308). |
persist | true/false | false | Dump the cache on shutdown and reload it on start (DNS-009). |
prefetch | true/false | true | Refresh hot entries before they expire (DNS-008). |
prefetch_min_hits | integer | 3 | Minimum hits before an entry is prefetched. |
prefetch_threshold_pct | integer | 10 | Prefetch when remaining TTL drops below this percentage. |
serve_stale | true/false | true | Serve expired answers when upstreams fail (RFC 8767, DNS-007). |
servfail_ttl | integer | 5 | How long SERVFAIL is cached, seconds (RFC 9520). |
stale_answer_client_timeout_ms | integer | 1800 | Answer from stale data if upstreams haven't replied within this many ms. |
stale_answer_ttl | integer | 30 | TTL on stale answers, seconds. |
stale_max_age | integer | 86400 | How long expired entries are kept for serve-stale, seconds. |
warm_names | integer | 300 | REQ: 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.
| Key | Type | Default | Meaning |
|---|---|---|---|
mode | "local" | "ship" | "local" | Where detailed query events go (spec/12 CLU-007). |
ring_slots | integer | 4096 | Event 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.
| Key | Type | Default | Meaning |
|---|---|---|---|
enabled | true/false | true | |
ignore_domains | list of string | [] | Registrable domains never reported (e.g. connectivity checks you expect). |
learning_days | integer | 7 | Days a device is watched before it can raise a finding. |
max_clients | integer | 1024 | Devices with state; the least recently seen are evicted first. |
nxdomain_per_minute | integer | 30 | REQ: OBS-009 (T7.14) — an NXDOMAIN storm: at least this many NXDOMAIN answers to one device in a minute... |
nxdomain_percent | integer | 50 | ...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.
| Key | Type | Default | Meaning |
|---|---|---|---|
address | string | Or a TCP reader: tcp://host:port. | |
buffer | integer | 10000 | Copies held while the reader is slow; beyond, they're dropped. |
forwarder | true/false | false | REQ: OBS-007 (T9.19) — also copy queries to upstreams and their answers (FORWARDER_QUERY, FORWARDER_RESPONSE), sampled the same way. |
sample_every | integer | 1 | One query in this many is copied (1 = every query). |
socket | string | A Unix socket a dnstap reader listens on (/run/dnstap.sock). |
[telemetry.metrics]
Prometheus endpoint (OBS-005).
| Key | Type | Default | Meaning |
|---|---|---|---|
enabled | true/false | true | |
exemplars | true/false | true | REQ: 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. |
listen | string | "0.0.0.0:9153" | |
per_client | true/false | false | Export per-client series (high cardinality; capped). |
per_client_cap | integer | 100 | At 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.
| Key | Type | Default | Meaning |
|---|---|---|---|
endpoint | string | The collector's OTLP/HTTP base URL (http://otel-collector:4318); metrics go to <endpoint>/v1/metrics. Unset: no export. | |
headers | object | Extra headers (an API key for a hosted collector). | |
interval_secs | integer | 60 | Seconds between exports. |
traces_sample_every | integer | 0 | REQ: OBS-017 (T11.6) — send 1 in this many queries as a trace to <endpoint>/v1/traces (0: none). Needs endpoint. |
traces_slow_ms | integer | 0 | REQ: 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).
| Key | Type | Default | Meaning |
|---|---|---|---|
enabled | true/false | true | |
flush_interval_secs | integer | 10 | Flush the in-progress block after this many seconds. |
fsync | true/false | false | fsync each block flush. |
privacy_level | integer | 0 | 0 full; 1 hide domains; 2 hide domains + clients; 3 aggregates only. |
retention_bytes | size (bytes or "32MiB") | "2GiB" | Delete oldest segments beyond this total size. |
retention_days | integer | 30 | Delete segments older than this many days. |
[telemetry.ship]
Ship mode ([telemetry] mode = "ship", CLU-007, ADR-055).
| Key | Type | Default | Meaning |
|---|---|---|---|
buffer_bytes | size (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_secs | integer | 300 | Close and ship the query log at least this often, in seconds (it's searchable from this node until then, through the cluster). |
to | string | (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.
| Key | Type | Default | Meaning |
|---|---|---|---|
address | string | syslog: udp://host:514, tcp://host:514, or (T9.11) tls://host:6514 (RFC 5425). | |
batch | integer | 500 | webhook: events per batch, and the most seconds an event waits for one. |
facility | integer | 16 | syslog: the facility number (16 = local0). |
flush_secs | integer | 5 | |
format | "json_lines" | "json_array" | "otlp_logs" | "json_lines" | webhook: json_lines (Loki-style collectors, Vector, Fluent Bit) or json_array. |
keep | integer | 3 | file: rotated files kept. |
max_buffer | integer | 10000 | Events held while the destination is slow or down; beyond it, events are dropped. |
max_bytes | size (bytes or "32MiB") | "100MiB" | file: rotate when the file reaches this size (to <path>.1, ...). |
name | string | Unique name (logs and metrics). | |
path | string | file: the file to append to. | |
spill_max_bytes | size (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. | |
statuses | list of string | Only these statuses (blocked, cached, ...); empty means all. | |
tls_ca | string | syslog over tls://: a PEM file of CAs trusted besides the public roots (a private collector's CA). | |
token_file | string | webhook: a file holding a token sent as Authorization: <token_scheme> <token>. | |
token_scheme | string | "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). | |
url | string | webhook: where batches are posted (newline-delimited JSON, or a JSON array with format = "json_array"). |
[api]
The REST API (and, later, the web UI).
| Key | Type | Default | Meaning |
|---|---|---|---|
enabled | true/false | true | |
listen | string | "0.0.0.0:8053" | Where the API listens (default 0.0.0.0:8053). |
trusted_proxies | list 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).
| Key | Type | Default | Meaning |
|---|---|---|---|
cert | string | Your certificate chain (PEM, the server's first). With key; not with self_signed. | |
key | string | Its private key (PEM). | |
self_signed | true/false | false | Make 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).
| Key | Type | Default | Meaning |
|---|---|---|---|
allow_insecure_basic | true/false | false | Accept HTTP Basic over plain HTTP. Off: Basic only behind HTTPS (a proxy that sends X-Forwarded-Proto: https). |
audit_retention_days | integer | 365 | Days 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_hours | integer | 24 | ... or after this long unused (default 24). |
session_ttl_hours | integer | 168 | A UI session ends this long after sign-in (default 168 = 7 days). |
totp_required_roles | list 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).
| Key | Type | Default | Meaning |
|---|---|---|---|
allowed_admin_networks | list of string | […] | Where break-glass admin sign-in is allowed. Default: private networks and loopback. |
disable_local_login | true/false | false | Turn 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_audience | string | "" | The audience (aud) MCP access tokens must carry. Default: <public_url>/mcp. |
mcp_provider | string | "" | 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_url | string | "" | 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.
| Key | Type | Default | Meaning |
|---|---|---|---|
client_id | string | ||
client_secret | string | (none) | The client secret, or ... |
client_secret_file | string | (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_claim | string | "groups" | Claim listing the user's groups or roles (groups, roles, realm_access.roles). Default groups. |
id | string | Short ID used in URLs: lowercase letters, digits, and - (keycloak, authentik). | |
issuer | string | Issuer URL (discovery is <issuer>/.well-known/openid-configuration). | |
name | string | "" | Button label ("Sign in with ..."). Default: the ID. |
require_verified_email | true/false | false | Refuse users whose provider says their email isn't verified. |
scopes | list of string | ["openid", "profile", "email"] | Scopes to request. Default: openid, profile, email. |
username_claim | string | "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.
| Key | Type | Default | Meaning |
|---|---|---|---|
group | string | ||
role | "viewer" | "operator" | "admin" | What a user may do (spec/08 §6). |
[cluster]
Cluster identity (full cluster settings arrive with M5).
| Key | Type | Default | Meaning |
|---|---|---|---|
bootstrap_secret_file | string | (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_source | string | "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. |
eligible | true/false | true | Whether this node may become the cluster primary. |
ephemeral | true/false | false | Join 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_secs | integer | 600 | How long the primary keeps an ephemeral member it no longer hears from. |
join_url | string | (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. |
listen | string | "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. |
name | string | "telltale" | Cluster name. |
site | string | "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.
| Key | Type | Default | Meaning |
|---|---|---|---|
allow_rewind | true/false | false | Accept a commit that doesn't descend from the one in use (a force-push or rewind). |
allowed_signers_file | string | (none) | git's allowed-signers format: <principal> ssh-ed25519 <key> per line. |
credentials_file | string | (none) | A file with an access token (or user:password) for private repositories. |
max_bytes | size (bytes or "32MiB") | "1MiB" | Largest file accepted. |
path | string | The TelltaleDNS file with the shared settings, e.g. telltale/shared.toml. | |
poll_secs | integer | 60 | How often to check the ref, in seconds. |
ref | string | "main" | Branch, tag, or commit ID. |
repo | string | The repository's HTTPS URL, e.g. https://github.com/me/homelab. | |
require_signed | true/false | false | Accept only commits SSH-signed by a key in allowed_signers_file. |
webhook_secret_file | string | (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.
| Key | Type | Default | Meaning |
|---|---|---|---|
advertise | list of string | URLs other nodes reach this node's cluster port at. | |
config_authority | string | "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.
| Key | Type | Default | Meaning |
|---|---|---|---|
bake_secs | integer | 300 | How long the canaries run a new version before everyone else gets it, in seconds (at least 5). |
canaries | list 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_disconnect | true/false | true | A canary that disconnects for over a minute after applying the version fails it. |
history | integer | 20 | Published versions kept (and their files protected) for pinning back to. |
max_bake_secs | integer | 3600 | With require_traffic: the longest bake, in seconds. |
require_traffic | true/false | false | Judge 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_pct | number | 5.0 | The 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.
| Key | Type | Default | Meaning |
|---|---|---|---|
enabled | true/false | true | The kill switch: false refuses every agent token at once (users and their own tokens are unaffected). |
rate_per_minute | integer | 120 | Requests per minute per agent token, unless the token sets its own. |
require_approval | true/false | false | REQ: 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.
| Key | Type | Default | Meaning |
|---|---|---|---|
interval_secs | integer | 30 | How often rules are checked, in seconds. |
[[alerts.destination]]
One place alerts go.
| Key | Type | Default | Meaning |
|---|---|---|---|
from | string | REQ: OBS-010 (T9.4) — email: the sender address. | |
name | string | Unique name, used in a rule's to. | |
password_file | string | ||
tls_ca | string | email: a PEM file with the mail server's certificate (or its CA), for a private server; public ones are trusted already. | |
to | list of string | email: the recipients. | |
token_file | string | A 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. | |
url | string | The 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). | |
username | string | email: 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.
| Key | Type | Default | Meaning |
|---|---|---|---|
enabled | true/false | true | Off: the rule is kept but not checked. |
for_secs | integer | 60 | How long it must hold before the alert goes out (not for anomaly and update_available, which go out once each). |
name | string | Unique name, shown in every alert. | |
threshold | number | servfail_rate: the percentage of queries (default 5). | |
to | list of string | Destination 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.
| Key | Type | Default | Meaning |
|---|---|---|---|
edns_payload | integer | 1232 | The 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. |
nsid | true/false | false | REQ: 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).
| Key | Type | Default | Meaning |
|---|---|---|---|
aggressive_nsec | true/false | true | REQ: 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_anchors | list 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_file | string | REQ: 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.
| Key | Type | Default | Meaning |
|---|---|---|---|
clients | list of string | Drill these devices (addresses, device names, or workloads). | |
for_secs | integer | 30 | How long, in seconds (15 to 300). |
group | string | Drill one group's devices; with neither group nor clients, everyone. | |
hour | integer | 3 | The local hour it starts in (0 to 23; default 3, at night). |
mode | "drop" | "refused" | "drop" | drop (default) or refused. |
nodes | list of string | [] | The nodes that drill themselves on the schedule: sites, pod names, or node IDs. |
schedule | "off" | "monthly" | "off" | off (default) or monthly. |
tz | string | The 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).
| Key | Type | Default | Meaning |
|---|---|---|---|
clients | list of string | [] | Devices left out: addresses or networks. |
enabled | true/false | true | Turns the exclusions on or off without losing the lists. |
names | list 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.
| Key | Type | Default | Meaning |
|---|---|---|---|
enabled | true/false | true | Off: no guesses (devices you named keep their names; kind still shows). |
max_clients | integer | 1024 | Most devices one node identifies (the most recently seen, over the last day). |
signatures_file | string | "" | 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).
| Key | Type | Default | Meaning |
|---|---|---|---|
default_group | string | "" | The group of a workload no label or map rule places; empty: its network's group, or default, as for any unknown device. |
enabled | true/false | false | Watch pods and namespaces (read-only) and name pod addresses by their workload. |
exclude_namespaces | list of string | [] | Namespaces never watched (their pods stay plain addresses). |
label | string | "telltaledns.io/group" | The pod or namespace label that names a workload's group. |
namespaces | list 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.
| Key | Type | Default | Meaning |
|---|---|---|---|
group | string | The group (it must exist). | |
kind | string | deploy, sts, ds, job, cronjob, rs, or pod; empty: any. | |
name | string | One workload's name; empty: any. | |
namespace | string | Namespace 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.
| Key | Type | Default | Meaning |
|---|---|---|---|
cert_warn_days | integer | 14 | A listener certificate expiring within this many days degrades the health level. |
enabled | true/false | true | |
interval_secs | integer | 30 | Seconds between rounds (at least 5). |
targets | list of string | Extra 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_ms | integer | 2000 | How long one probe may take, in milliseconds (100 to 10000). |
[[router]]
REQ: T8.2 — a router whose DHCP clients name devices.
| Key | Type | Default | Meaning |
|---|---|---|---|
api_key_file | string | UniFi: an API key (UniFi Network 9+), or OPNsense: the API key. A file holding it. | |
api_secret_file | string | OPNsense: the API secret (a file). | |
interval_secs | integer | 300 | Seconds between reads. |
name | string | Unique name (logs, the leases' source). | |
password_file | string | ||
site | string | UniFi: the site (default default). | |
tls_ca | string | A PEM file with the console's certificate (or its CA), for self-signed consoles. | |
tls_insecure_skip_verify | true/false | Skip certificate verification (dangerous; logs a warning). | |
tls_server_name | string | The 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. | |
url | string | The console or controller: https://192.168.1.1 (UniFi OS), https://controller:8443 (Network application), https://opnsense.lan. | |
username | string | UniFi 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.
| Key | Type | Default | Meaning |
|---|---|---|---|
action | "allow" | "block" | What a quick rule does. | |
created | string | When it was made (RFC 3339), filled in by the API. | |
created_by | string | Who made it (a user or agent:<token>), filled in by the API. | |
devices | list of string | Devices it applies to: device names ([[client]] name), IPs, or CIDRs. | |
domain | string | The domain; its subdomains are included (example.com covers www.example.com). | |
expires | string | When it stops applying (RFC 3339, e.g. 2026-10-05T21:30:00Z). Absent: never. | |
groups | list of string | Groups it applies to. With neither devices nor groups, it applies to everyone. | |
id | string | A unique ID (the API assigns one; in a file, any unique name). | |
note | string | Why 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.
| Key | Type | Default | Meaning |
|---|---|---|---|
action | "block_all" | "enable_lists" | "block_services" | What happens during the windows. | |
lists | list of string | enable_lists: these lists apply during the windows, and not outside them. | |
name | string | Unique name, used in a group's schedules. | |
services | list of string | block_services: these services (telltale services list) are blocked during the windows. | |
tz | string | IANA 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).
| Key | Type | Default | Meaning |
|---|---|---|---|
days | list of string | mon … sun, or weekdays, weekends, daily. | |
end | string | HH:MM, 24-hour (24:00 is the end of the day). | |
start | string | HH:MM, 24-hour. |
[simulate]
REQ: OBS-024 (T13.1) — change simulation over the query log.
| Key | Type | Default | Meaning |
|---|---|---|---|
default_window | string | "24h" | The window when a request doesn't name one: 30m, 24h, 7d (at most 7 days). |
enabled | true/false | true | Off: every simulation answers "unavailable" (dry runs still work, without it). |
max_rows | integer | 2000000 | Most logged queries one simulation reads (at least 1,000), newest first. |
max_secs | integer | 20 | Longest a simulation reads the query log, in seconds (1 to 300); past it the answer is partial (the newest queries). |
plans_by_default | true/false | false | AI 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.
| Key | Type | Default | Meaning |
|---|---|---|---|
availability_target | number | 99.9 | Percent of answers that must not be SERVFAIL (default 99.9). |
enabled | true/false | true | Off: no objectives (nothing to show, and slo_burn alerts never fire). |
latency_ms | integer | 250 | The 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_target | number | 99.0 | Percent of answers that must arrive within latency_ms (default 99). |
window_days | integer | 30 | Days 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).
| Key | Type | Default | Meaning |
|---|---|---|---|
check | true/false | true | Read 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_url | string | Where 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.
| Key | Type | Default | Meaning |
|---|---|---|---|
keep | integer | 100 | Disagreements kept for the API and the Upstreams page, newest first (1 to 1000). |
reference | string | The upstream group asked for second opinions. Unset: another upstream of the group that answered (a group with one upstream gets none). | |
sample_every | integer | 0 | One 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.
| Key | Type | Default | Meaning |
|---|---|---|---|
file | string | An RFC 1035 zone file (BIND, Technitium's export, PowerDNS). | |
groups | list of string | Only these groups see this zone (split horizon); empty: everyone. | |
name | string | The zone's apex (home.example.com). | |
negative_ttl | integer | 300 | TTL 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>".
| Key | Type | Default | Meaning |
|---|---|---|---|
name | string | Owner name; a leading *. makes it a wildcard for names below it. | |
node_only | true/false | Keep 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. | |
ttl | integer | TTL in seconds (default: [local] default_ttl). | |
type | string | A, AAAA, CNAME, PTR, TXT, MX, or SRV. | |
value | string |