JSON data files, dnsplane.json keys, REST paths, and curl samples. For resolution order and the diagram see resolution.md. For CLI paths and the TUI see usage.md. For logging options see logging.md. RPM/Debian packages and version strings (BASE-SHORTSHA): packaging/README.md.
| File | Usage |
|---|---|
| dnsrecords.json | holds dns records |
| dnsservers.json | holds the dns servers used for queries (see Upstream servers and domain whitelist) |
| dnscache.json | Cached answers while still valid (TTL not expired), when caching is enabled |
| dnsplane.json | the app config |
Starter copies in the repo: examples/dnsplane-example.json and examples/dnsservers-example.json (point file_locations.dnsservers at your real path, e.g. ./dnsservers.json).
DNS records are always configured via file_locations.records_source in dnsplane.json. One source type applies for both loading and (when writable) saving:
- file – Local path; records are read from and written to this path (e.g.
dnsrecords.json). Default. - url – HTTP(S) URL that returns JSON in the same format as the records file (
{"records": [...]}). Read-only; a refresh interval controls how often dnsplane re-fetches. - git – Git repository URL (HTTPS or SSH). dnsplane clones/pulls the repo and reads
dnsrecords.jsonat the root. Read-only; refresh interval controls how often it runsgit pull. - bind_dir – Directory of BIND-style zone files (see zone-files.md). Read-only for API/TUI mutations that would persist to JSON; use
POST /dns/records/reload,record load, optionalwatch, or optionalrefresh_interval_secondsto refresh from disk.
For url, git, and bind_dir, record add/update/delete via API and TUI returns 403 (records source is read-only). POST /dns/records/reload and record load still reload from the configured source.
Example – local file (default):
"file_locations": {
"dnsservers": "/etc/dnsplane/dnsservers.json",
"cache": "/etc/dnsplane/dnscache.json",
"records_source": {
"type": "file",
"location": "/etc/dnsplane/dnsrecords.json"
}
}Example – URL (read-only):
"records_source": {
"type": "url",
"location": "https://example.com/dnsrecords.json",
"refresh_interval_seconds": 60
}Example – Git (read-only):
"records_source": {
"type": "git",
"location": "https://github.com/you/repo.git",
"refresh_interval_seconds": 120
}For url and git, refresh_interval_seconds defaults to 60 if omitted.
Example – BIND zone directory (read-only):
"records_source": {
"type": "bind_dir",
"location": "/var/named",
"include_pattern": "*.db",
"named_conf": "/etc/named.conf.local",
"watch": true,
"refresh_interval_seconds": 300
}Omit named_conf to glob include_pattern under location (default pattern *.db). Set refresh_interval_seconds only if you want periodic reloads in addition to (or instead of) watch.
The file is JSON: { "dnsservers": [ ... ] }. Each entry has at least address, port, active, local_resolver, adblocker. Optional: transport (udp / tcp / dot / doh), doh_url for DoH. Optional per-row fallback (queried in parallel with that row): fallback_address, fallback_port (default 53), fallback_transport (defaults to the row’s transport), fallback_doh_url (for DoH fallbacks).
domain_whitelist (optional array of strings): if set, this upstream is used only for query names that match an entry (exact name or a subdomain under that suffix). Names that match any active whitelisted server are resolved only via those servers — not via global upstreams (no domain_whitelist) and not via the configured fallback_server_* for that query. You can list multiple suffixes on one server, or use multiple server rows each with its own whitelist and IP.
Example (same structure as examples/dnsservers-example.json):
{
"dnsservers": [
{
"address": "1.1.1.1",
"port": "53",
"active": true,
"local_resolver": true,
"adblocker": false
},
{
"address": "10.0.0.53",
"port": "53",
"active": true,
"local_resolver": true,
"adblocker": false,
"domain_whitelist": ["internal.example.com", "corp.other.net"]
}
]
}TUI equivalent for the internal row:
dns add 10.0.0.53 53 active:true localresolver:true adblocker:false whitelist:internal.example.com,corp.other.net
See also resolution.md for Domain whitelist and Server selection.
Blocked domains are stored in a single in-memory list. You can:
- TUI:
adblock load <filepath|url>(one file or URL per command; each load is merged),adblock list(sources and count per source),adblock domains(all blocked domains),adblock add/adblock remove/adblock clear. - Config: In
dnsplane.json, setadblock_list_filesto an array of paths. Those files are loaded in order at startup and merged into the block list (same hosts-style format:0.0.0.0 domain1.com domain2.com). Stats show "Adblock list: N domains".
See dnsplane.example.json for every key and defaults. examples/dnsplane-example.json is a shorter starter. Upstreams and domain_whitelist are covered in examples/dnsservers-example.json and Upstream servers (dnsservers.json). The tables below group the same options for quick lookup.
DNS and fallback
| Key | Meaning |
|---|---|
port |
DNS UDP/TCP listen port (default 53). |
dns_bind |
IP to bind for DNS (empty = all interfaces). |
fallback_server_ip, fallback_server_port |
Resolver used when no upstream matches or for recursion-style paths. |
timeout |
Upstream query timeout (seconds). |
fallback_server_transport |
udp (default), tcp, dot, or doh for the fallback resolver. |
dns_refuse_any |
If true, answer NOTIMP for ANY queries. |
dns_max_edns_udp_payload |
Cap EDNS UDP payload on responses (0 = unchanged; e.g. 1232). |
REST API and client access
| Key | Meaning |
|---|---|
api |
Enable REST API listener. |
apiport |
API listen port (e.g. 8080). |
api_bind |
API bind address (empty = all interfaces). |
api_auth_token |
If set, requires Authorization: Bearer or X-API-Token except GET/HEAD /health and /ready. |
api_tls_cert, api_tls_key |
PEM paths; both set → HTTPS for the API. |
api_rate_limit_rps, api_rate_limit_burst |
Per-client HTTP rate limit (0 RPS = disabled). |
server_socket |
UNIX socket for TUI control. |
server_tcp |
TCP address for remote TUI clients (default 0.0.0.0:8053). |
DoT / DoH (inbound)
| Key | Meaning |
|---|---|
dot_enabled, dot_bind, dot_port, dot_cert_file, dot_key_file |
DNS over TLS listener. |
doh_enabled, doh_bind, doh_port, doh_path, doh_cert_file, doh_key_file |
DNS over HTTPS. |
axfr_enabled |
If true, answer AXFR over TCP and DoT for zones present in local records (single-message transfer). |
axfr_allowed_networks |
CIDR list allowed to request AXFR (e.g. ["127.0.0.0/8","10.0.0.0/8"]). If axfr_enabled is true but this list is empty or invalid, AXFR is refused. |
Response / abuse limits
| Key | Meaning |
|---|---|
dns_rate_limit_rps, dns_rate_limit_burst |
Per-IP DNS rate limit (0 = off). |
dns_amplification_max_ratio |
Max packed response vs request ratio (0 = off). |
dns_response_limit_mode |
sliding_window or rrl. |
dns_sliding_window_seconds, dns_max_responses_per_ip_window |
Sliding-window mode. |
dns_rrl_max_per_bucket, dns_rrl_window_seconds, dns_rrl_slip |
RRL mode. |
DNSSEC
| Key | Meaning |
|---|---|
dnssec_validate, dnssec_validate_strict, dnssec_trust_anchor_file |
Validation when upstream returns DNSSEC material. |
dnssec_sign_enabled, dnssec_sign_zone, dnssec_sign_key_file, dnssec_sign_private_key_file |
Sign answers from local records (see security-public-dns.md). |
Cache and performance
| Key | Meaning |
|---|---|
cache_records |
Enable resolver cache (false = no dnscache.json lookups; upstream only). |
local_records_enabled |
Default true. If false, dnsrecords are not used for DNS answers (forward-only to upstreams + fallback). Records still load for API/TUI unless you use read-only/cluster modes. localhost is still answered locally (RFC 6761). |
min_cache_ttl_seconds |
Floor for cached TTL (default 600; 0 = use upstream TTL). |
stale_while_revalidate |
Serve stale entries (TTL=1) while refreshing in background. |
cache_warm_enabled, cache_warm_interval_seconds |
Keep-alive self-query (defaults: on, every 10s). |
cache_compact_enabled, cache_compact_interval_seconds |
Periodic removal of expired cache rows from memory + persist (defaults: on, every 1800s / 30m; interval minimum 60s). No effect if cache_records is false. |
pretty_json |
Default false. If true, writes indented JSON for dnsservers.json, dnsrecords.json (file source), and dnscache.json. If false, writes compact JSON (less CPU and I/O on large caches). Does not affect dnsplane.json itself (the main config file is always written indented when saved). |
Stats, dashboard, and profiling
| Key | Meaning |
|---|---|
stats_dashboard_enabled |
/stats/dashboard (HTML UI including Tuning), /stats/dashboard/data, /stats/dashboard/resolutions, and /stats/dashboard/ws (default on). |
dashboard_resolution_log_cap |
How many recent DNS resolutions are kept in memory for the dashboard Log view (default 1000; max 1000000). Not persisted; lost on restart. |
full_stats, full_stats_dir |
Optional aggregated stats DB + TUI statistics commands. |
pprof_enabled |
Default false. If true, exposes runtime profiling over HTTP (Go pprof: CPU, heap, etc.). |
pprof_listen |
Listen address when pprof_enabled is true; if empty, 127.0.0.1:6060. In production, keep profiling on loopback or behind a protected interface. |
Profiling: When pprof_enabled is true, profiling is available on pprof_listen (default 127.0.0.1:6060). Changing pprof_enabled or pprof_listen takes effect after a restart. pretty_json affects the next write of data JSON files and can be changed with server set + server save without restarting.
Upstream health — upstream_health_check_enabled, upstream_health_check_failures, upstream_health_check_interval_seconds, upstream_health_check_query_name. See upstream-health.md.
Clustering — cluster_enabled, cluster_listen_addr, cluster_peers, cluster_auth_token, cluster_node_id, cluster_sync_interval_seconds, cluster_advertise_addr, cluster_replica_only, cluster_reject_local_writes, cluster_admin, cluster_admin_token, cluster_sync_policy, cluster_allowed_writer_node_ids, cluster_discovery_srv, cluster_discovery_interval_seconds. See clustering.md.
Files and records
file_locations—dnsservers,cache,records_source(type+location+ optionalrefresh_interval_secondsfor url/git/bind_dir, plus bind_dir-onlyinclude_pattern,named_conf,watch). See Records source above.dnsservers.jsonformat and per-serverdomain_whitelist: Upstream servers (dnsservers.json).adblock_list_files— list of hosts-style list paths loaded at startup.
DNSRecordSettings — auto_build_ptr_from_a, forward_ptr_queries, add_updates_records.
log — log_dir, log_severity, log_rotation, log_rotation_size_mb, log_rotation_time_days.
In the TUI, server config shows current settings; server set <setting> <value> then server save writes them to dnsplane.json. For the exact key names as in JSON, see dnsplane.example.json (TUI server set lists the same keys in its help).
Enable the REST API with "api": true in dnsplane.json and set apiport (e.g. 8080). You can start or stop the API listener from the TUI with server start api / server stop api.
Optional API authentication: set "api_auth_token": "<secret>" in dnsplane.json (or server set api_auth_token '<secret>' then server save). When set, clients must send either Authorization: Bearer <secret> or X-API-Token: <secret>, except GET/HEAD /health and GET/HEAD /ready (so automated health checks work without the token). All other paths—including /version, /metrics, and HTML stats pages—require the token when configured.
TLS, bind, and rate limits: set api_tls_cert and api_tls_key to PEM file paths to serve the REST API over HTTPS. Use dns_bind and api_bind (e.g. "127.0.0.1") to listen on a specific address instead of all interfaces. Per-IP limits: api_rate_limit_rps / api_rate_limit_burst (HTTP 429 when exceeded), dns_rate_limit_rps / dns_rate_limit_burst (DNS REFUSED when exceeded). Amplification hardening: dns_amplification_max_ratio caps packed response size vs packed request (0 disables).
Upstream transport: in dnsservers.json, each server may set transport to udp (default), tcp, dot (TLS to port 853 by default), or doh. For DoH set doh_url to the full https://…/dns-query URL (or put a URL in address when using doh). Global config fallback_server_* applies when the query is not using whitelist-only upstreams. Per-row fallback_* fields add a second resolver in the same parallel race (see resolution.md). domain_whitelist (split DNS) is documented in Upstream servers (dnsservers.json). POST / PUT /dns/servers accept the same fallback_* JSON keys as dnsservers.json.
Public / internet-facing DNS: see security-public-dns.md for DoT/DoH listeners (dot_*, doh_*), response limit modes (dns_response_limit_mode sliding_window vs rrl), DNSSEC validation flags, and metrics.
| Method | Path | Description |
|---|---|---|
| GET | /health |
Liveness: returns 200 when the API process is up. No dependency on DNS or other listeners. |
| GET | /ready |
Readiness: returns 200 when the API and DNS listener are both up, 503 otherwise. Response is JSON with ready, api, dns, tui_client (connected, addr, since), listeners (dns_port, api_port, api_enabled, client_socket_path, client_tcp_address), and build (version, go_version, os, arch). Use this for load balancers and orchestrator readiness checks (e.g. Kubernetes). |
| GET | /version |
Build metadata as JSON: version, go_version, os, arch (same as build in /stats and /ready). |
| GET | /version/page |
HTML view of the same build fields (for embedding in the dashboard). 404 if stats_dashboard_enabled is false. |
| GET | /dns/records |
List DNS records (same data as the TUI). Returns JSON with records, optional detailed (boolean), filter, messages. Query: name (substring on normalized owner name), type (DNS type, AND with name when both set; invalid type → 400), details or d (1 / true for verbose fields in each record, like record list details). |
| POST | /dns/records |
Add a DNS record. Body: {"name":"...","type":"A","value":"...","ttl":3600}. Optional id: if set, must be unique (for import/sync); if omitted, the server assigns a UUID. Response lists include id on each record. |
| POST | /dns/records/reload |
Reload records from the configured records_source (no body). Returns {"status":"reloaded","records":N}. Subject to api_auth_token when set. |
| PUT | /dns/records |
Update a record. With id in the body, replace that row’s name/type/value/TTL (stable update). Without id, same as today: match name + type + value and update in place (legacy). |
| DELETE | /dns/records |
Delete by query ?id=… or JSON body {"id":"..."}. Otherwise name (required) plus optional type / value (same as legacy). |
| GET | /dns/servers |
List upstreams plus health: servers, upstream_health_check_enabled, interval/failures hints, and upstream_health per address_port (unhealthy, consecutive_failures, last_probe_*, last_success_at). |
| GET | /dns/upstreams/health |
Same health slice and check settings without full server config. See upstream-health.md. |
| GET | /stats |
Resolver stats as JSON: session / total scopes with resolver counters; top-level build (version, go_version, os, arch). When full_stats is enabled in config, includes full_stats.enabled, full_stats.requesters_count, full_stats.domains_count. |
| GET | /metrics |
Prometheus text format: counters and gauges (queries, cache hits, blocks, process uptime, etc.). With full_stats enabled, adds full-stats gauges. Histogram dnsplane_dns_resolve_duration_seconds reports resolve latency by QTYPE (same breakdown as /stats/perf). |
| GET | /stats/dashboard |
Live HTML UI: Status (listeners + feature flags), Statistics (rates, charts, full_stats top 10, activity log), Log (recent resolutions), Historical (full_stats), Tuning (fast-path perf histograms), plus embedded Version. 404 if stats_dashboard_enabled is false (default is on). |
| GET | /stats/dashboard/data |
JSON backing the dashboard (counters, perf, summary, status, series, log, per_sec_rates, fullstats / fullstats_top when full_stats is on). 404 if stats_dashboard_enabled is false. |
| GET | /stats/dashboard/resolutions |
JSON for the dashboard Log (resolutions): cap (matches dashboard_resolution_log_cap), count, resolutions (newest first; client IP, query, type, outcome, upstream, reply, duration_ms, time). 404 if stats_dashboard_enabled is false. |
| POST | /stats/dashboard/resolutions/purge |
Clears the in-memory resolution log (same data as the dashboard Log and main dashboard activity list). 404 if stats_dashboard_enabled is false. |
| GET | /stats/perf |
JSON performance breakdown: outcomes (local/cache/upstream/none) and histograms for cache-only vs upstream paths. Prefer cache-only vs upstream histograms for tuning; the combined total histogram mixes both. Reset with POST /stats/perf/reset. The dashboard Tuning view shows the same data. |
| POST | /stats/perf/reset |
Clears A-record performance counters (use before measuring latency). |
With API on port 8080 (set apiport in config):
# Full upstream health JSON
curl -sS http://127.0.0.1:8080/dns/upstreams/health | jq .
# Servers plus embedded health
curl -sS http://127.0.0.1:8080/dns/servers | jq '{servers: .servers, checks_enabled: .upstream_health_check_enabled, upstream_health: .upstream_health}'Enable checks in dnsplane.json, then restart server:
"upstream_health_check_enabled": true,
"upstream_health_check_failures": 3,
"upstream_health_check_interval_seconds": 30,
"upstream_health_check_query_name": "google.com."