DOCUMENTATION / CONFIGURATION

Configure DNS Deny.

Every setting, what it controls, and when it takes effect. Understand the first seed, saved state, live updates, and restarts before changing your resolver.

TOML seed → SQLite statePer-setting restart guidanceGeneral + Pi Zero presets

Saved state and restarts

Start with the complete general-purpose seed or the Pi Zero seed. This reference follows the current source and those examples. “Seed value” means the general-purpose example, including defaults applied to omitted optional fields; it is not a promise to overwrite an existing installation.

  1. First start: open DNS_STATE_DB. If no configuration row exists, read DNS_CONFIG, normalize the optional API token, validate, and persist revision 1.
  2. Later starts: load and validate the saved database configuration. Do not merge new seed values into it. Invalid or unsupported state fails startup; it is not silently replaced by a fresh seed.
  3. Supported updates: dashboard actions and dedicated API endpoints validate a candidate, commit it to SQLite, then publish a new in-memory configuration. Failed validation or persistence leaves the live configuration unchanged. Rule files and downloaded source caches have their own persistence; they are not all inside state.db.
  4. Requests in flight: an existing DNS request may finish using its earlier configuration snapshot. New requests read the published settings. Downstream clients may still retain DNS answers until their own cache expires.
Change classWhat to doRestart behavior
Live settingsUse the dashboard or the specific API endpoint documented below.Successful edits are saved and used by subsequent requests; they survive restart.
Primary upstreams and resolver modeSave in their settings editor, then restart the service. Both API endpoints return restart_required: true.The primary resolver pool is built at startup. Treat resolver-mode edits as restart-required even though the current query path reads the mode flag live.
Startup settingChoose it before the first seed. There is no general-purpose configuration write endpoint.Restart initializes the component from saved state. Editing the original TOML alone cannot change it; existing installations need a planned migration or re-provisioning.
Environment variableSet it in the process, systemd unit override, or Compose environment, then restart/recreate.Environment is read by the new process. Shell assignments do not alter a running service.

GET /api/v1/config is read-only and redacts credentials. A settings response can show a saved value before a startup component has been rebuilt. Do not interpret “saved” as “already active” for restart-required settings.

Settings endpoints below use POST unless another method is named, and require JSON API authentication. Send the complete object expected by that settings section, not an arbitrary fragment of the full configuration. For example, service updates replace both blocked_services and custom_services; blocklist updates replace both source arrays. Use the dashboard if you do not need to automate those updates.

Environment and paths

These are process environment variables, not command-line flags. Relative paths resolve from the process working directory.

VariableDefaultUsage and restart behavior
DNS_CONFIGconfig/default.tomlPath to the initial TOML seed. Ignored for configuration loading once the selected state database contains saved configuration.
DNS_STATE_DBstate.dbPersistent configuration file. Changing the path selects a different installation state; an empty database seeds again. Keep this file across upgrades.
DNS_PORT53UDP and TCP listener port. Use 1053 for unprivileged testing. Must not collide with the admin port. There is no TOML dns_port field.
DNS_STATIC_DIRstaticDashboard asset directory. Ship CSS/JS matching the backend version; API-only and resolver-only builds do not need dashboard assets.
DNS_API_TRUSTED_PROXY_CIDRSSaved api_trusted_proxy_cidrsComma-separated proxy networks, e.g. 127.0.0.1/32,::1/128. Overrides the API’s trusted peers for this process without changing saved configuration. Invalid entries prevent API startup.
RUST_LOGdns_deny=info,core_dns=info,filter=warn,logger=info,api=info,ingest=warnDiagnostic log filter; independent of DNS query logging. Set before starting the process.

The packaged service uses /var/lib/dns-deny as its working directory, with the seed at /etc/dns-deny/config.toml. Docker also stores data under /var/lib/dns-deny, with dashboard assets under /usr/share/dns-deny/static. Keep the data directory private and writable by the service user; the seed may stay read-only.

LOCAL PROCESS EXAMPLE
DNS_CONFIG=/absolute/path/to/private-config.toml \
DNS_STATE_DB=/absolute/path/to/dev-state.db \
DNS_PORT=1053 \
  ./target/release/dns-deny

Prepare the complete seed and replace its sample password before using this example. Changing DNS_CONFIG while keeping an existing DNS_STATE_DB will still use the saved configuration.

Network access

DNS admission controls apply before device policies, caches, filtering, logging, or upstream queries.

bind_address

Seed value
"0.0.0.0"
Change takes effect
Startup setting

IP address for the DNS UDP/TCP listener. 0.0.0.0 listens on all IPv4 interfaces; choose a specific LAN or loopback address to restrict binding. It is an IP, not a hostname or CIDR. IPv6 requires an IPv6 bind address. The port comes from DNS_PORT.

allowed_client_cidrs

Seed value
Private, loopback, link-local, and carrier-grade NAT ranges
Change takes effect
Startup setting

Allowed source IPs or CIDR networks. Outside clients receive REFUSED, even while protection is paused or a device is whitelisted. Omitted values use 127.0.0.0/8, ::1/128, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 100.64.0.0/10, fc00::/7, and fe80::/10. An explicit empty array allows all clients. Restrict this to the actual client networks and enforce UDP/TCP access at the firewall too.

NETWORK SEED FRAGMENT
bind_address = "0.0.0.0"
allowed_client_cidrs = ["127.0.0.0/8", "192.168.1.0/24", "::1/128"]

Upstreams, encryption, and DNSSEC

The primary forwarding pool, the custom DoH client, and the threat-discovery pool serve different purposes. A blocklist URL is not an upstream resolver.

upstreams

Seed value
Four DoT servers: Cloudflare and Quad9
Change takes effect
Save through /api/v1/config/upstreams; restart

Nonempty array of resolver objects. The general seed uses 1.1.1.1, 1.0.0.1, 9.9.9.9, and 149.112.112.112. The pool is built at startup; saving new servers does not replace the running pool. Keep at least one configured even in recursive or custom DoH mode.

dnssec_validation

Seed value
true
Change takes effect
Startup setting

Locally validates DNSSEC for forwarding, custom DoH, and iterative resolution. Bogus signatures or missing required proof return SERVFAIL; an upstream AD bit alone is not trusted. Unsigned delegations remain usable without AD. This validates authenticity and integrity, not encryption. Pi Zero seeds false to reduce CPU use.

recursive_resolver_enabled

Seed value
false
Change takes effect
Save through /api/v1/config/resolver; restart

Uses an iterative resolver that contacts root and authoritative servers. The current request path reads the flag live and the update clears response caches, but the supported endpoint reports a restart requirement: restart after saving. If iterative resolution fails while DNSSEC is enabled, return SERVFAIL; with validation disabled it can fall through to custom DoH or the primary pool. Iterative DNS is not an end-to-end encrypted transport.

encrypted_upstream_enabled

Seed value
false
Change takes effect
Live through /api/v1/config/doh

Enables the custom HTTPS DNS client. Successful iterative resolution takes precedence; otherwise this client is tried before the primary pool. The settings endpoint constructs a replacement client and swaps it after persistence. Existing cached answers may remain until expiry or an explicit cache clear.

encrypted_upstream_url

Seed value
"https://cloudflare-dns.com/dns-query"
Change takes effect
Live through /api/v1/config/doh

The one active custom DoH URL. Must use HTTPS, include a host, and contain no embedded username/password. Redirects are not followed; the HTTP client has a five-second timeout. A hostname-based endpoint still needs working name resolution to establish its HTTPS connection.

encrypted_upstream_allow_plaintext_fallback

Seed value
false
Change takes effect
Live through /api/v1/config/doh

If custom DoH fails, false returns SERVFAIL. With true and DNSSEC validation disabled, failures may fall back to the configured primary pool. The primary pool’s actual protocols determine whether that fallback is encrypted; the option does not convert DoT into UDP. With DNSSEC enabled, custom DoH errors fail closed regardless of this flag.

doh_upstreams

Seed value
Five example HTTPS URLs
Change takes effect
Live through /api/v1/config/doh

Saved choices offered by the DoH settings editor; this is not an automatic failover or load-balancing pool. Only encrypted_upstream_url is active. The shipped seed lists Cloudflare, Google, Quad9, AdGuard, and Mullvad; omitting this optional field uses the first three.

FieldFormatBehavior
addressIP, IPv4:port, or [IPv6]:portLiteral server address, not a hostname. Bare IPv6 is accepted without a port. Explicit ports must be nonzero.
protocoludp, tcp, dot, dohDefault ports are 53, 53, 853, and 443. UDP includes TCP fallback for truncated responses.
tls_sniTLS server nameRequired for primary DoT/DoH configuration; select a name matching the server certificate.
doh_pathOptional request pathFor primary DoH, defaults to /dns-query. This is separate from the full custom DoH URL.
UPSTREAM SEED FRAGMENT
[[upstreams]]
address = "1.1.1.1"
protocol = "dot"
tls_sni = "cloudflare-dns.com"

Use the same upstream object shape for auto_block_upstreams. Configure its TLS names explicitly too.

Blocklists and source updates

Downloaded list subscriptions and local allow/deny rules are restored before the DNS listener opens. A background refresh then checks remote sources; an offline restart can use the last successful downloads.

blocklist_urls

Seed value
Four base subscription URLs
Change takes effect
Live through /api/v1/config/blocklist

Base sources that apply independently of category toggles. The seed includes StevenBlack, AdGuard DNS via Firebog, someonewhocares, and URLhaus. Sources may be HTTP(S) downloads or supported local files. Disabling a category does not remove the same URL if it is still present in the base array or another enabled category.

blocklist_categories

Seed value
Seven enabled categories
Change takes effect
Live through /api/v1/config/blocklist

Array of id, name, urls, and enabled. IDs identify categories and their pauses; names are display labels. Enabled, unpaused categories contribute their URLs. The general seed enables ads, malware, crypto, gambling, adult, fakenews, and social; review these before deploying because social/adult/gambling blocks can be broad. Pi Zero has a smaller set with its combined adult/gambling category disabled.

blocklist_refresh_cron

Seed value
"0 0 3 * * *"
Change takes effect
Startup setting

Six fields: second, minute, hour, day of month, month, day of week. The default runs daily at 03:00 UTC. The scheduler is installed at startup; a manual sync is also available. A malformed expression prevents the scheduler from starting and is logged; startup still performs its initial remote sync.

blocklist_fetch_concurrency

Seed value
4
Change takes effect
Startup setting

Source download concurrency, 1–16. The Pi preset sets 1. Independently, at most two source body buffers/parsers run together. More concurrency may shorten refresh time but does not raise source-size or aggregate memory limits.

Formats, failure handling, and persistence

Imports recognize plain domains, hosts files, unconditional domain-style Adblock entries, regex lists, and Pi-hole Gravity databases. Contextual browser rules such as resource-type, third-party, or domain-scoped exceptions are skipped because DNS cannot supply that context.

Conditional HTTP requests reuse validators from successful imports. Failed downloads, oversized inputs, or failed rebuilds retain the last working rules rather than publishing an empty filter. Source status is visible in list management; POST /api/v1/lists/sources/sync requests a network refresh. A local allow/deny edit rebuilds from already available source rules without downloading every list.

Limits are 32 MiB and one million rules per source, 16 KiB per line/pattern, and two million retained source rules / 64 MiB of retained pattern text in aggregate. These are import limits, not total process-memory limits. Valid large regex sets are partitioned into at most eight compiled sets; a failed rebuild preserves the previous snapshot.

CATEGORY SEED FRAGMENT
[[blocklist_categories]]
id = "ads"
name = "Advertising"
urls = ["https://adaway.org/hosts.txt"]
enabled = true

Manage local domains and regexes through list management. They live in separate rule files, not a local_denies TOML setting. Domain entries include subdomains; explicit domain allows precede denies across filter tiers. Service blocks still take precedence over domain allows.

Threat discovery and scoring

Discovery asks a separate filtering resolver whether a public name is sinkholed. It is distinct from the resolver that obtains the primary DNS answer.

auto_block_upstream_sinkholes

Seed value
true
Change takes effect
Live through /api/v1/config/detection

For protected public names, require a discovery verdict before releasing an answer, including the first query and CNAME targets. Explicit 0.0.0.0 or :: answers count as sinkholes; NXDOMAIN alone does not. Timeout, unavailable discovery, or exhausted discovery capacity returns SERVFAIL. Private-use names and locally configured owners are not sent to the threat pool. Whitelists, explicit domain allows, and global pauses bypass discovery enforcement. Pi Zero seeds false.

auto_block_upstreams

Seed value
Two Cloudflare Security DoT resolvers
Change takes effect
Startup setting

Pool used only for threat discovery; default addresses are 1.1.1.2 and 1.0.0.2, with security.cloudflare-dns.com as TLS name. Uses the upstream object shape above. Built at startup. Do not enable enforced discovery with an empty or unreachable pool: protected public lookups cannot receive a clean verdict.

discovery_timeout_ms

Seed value
250
Change takes effect
Startup setting

Allowed range 1–5000 ms. Bounds how long discovery is awaited. Set the budget for actual resolver latency; a short budget can produce SERVFAIL on otherwise healthy primary lookups. The current query path reads this value from its snapshot, but no dedicated settings editor exposes it.

smart_ip_blocking

Seed value
false
Change takes effect
Live through /api/v1/config/detection

Checks returned addresses against administrator-managed malicious_ips.txt entries, including local answers and cached responses. Domain answers never automatically create global malicious-IP entries. Manage IPs in list management; disabling this toggle retains the saved IP entries.

ml_scoring_enabled

Seed value
false
Change takes effect
Live through /api/v1/config/detection

Enables heuristic domain scoring for review/logging when the binary is built with feat-scoring. It does not change DNS responses or automatically block a domain. In builds without that feature, enabling the saved flag cannot add scoring code.

Discovered auto-denies are persisted in auto_blocked.txt and restored after restart. Disabling discovery does not delete previously learned denies; remove a false positive explicitly. New auto-deny persistence stops at 100,000 entries, while live threat and malicious-IP checks continue. Review inherited malicious_ips.txt entries when upgrading from older behavior.

Devices and schedules

Device rules apply to the client identity visible to this server. MAC identity is available on the local network segment; NAT and routed clients may need IP policies.

device_blocking_enabled

Seed value
true
Change takes effect
Live through /api/v1/network/device-blocking

Master switch for device blocks, whitelists, schedules, and timed device pauses. Turning it off retains their saved configuration but disables its enforcement. The optional field defaults to false when omitted, unlike the shipped seeds.

device_policies

Seed value
Empty map
Change takes effect
Live through /api/v1/network/policy

Map of IP/MAC identity to block or whitelist. Block refuses DNS requests; whitelist bypasses content restrictions, including service/domain/IP checks and SafeSearch, but not CIDR admission or rate limits. Schedules take precedence over static policies, and static MAC policies take precedence over IP policies. Assigning a policy clears a pause for that identity.

device_policy_ip_overrides

Seed value
Empty; internally maintained
Change takes effect
Managed with device-policy updates

Tracks whether an IP whitelist was explicitly confirmed (true) or quarantined as a legacy IP/MAC pair (false). Do not hand-edit this compatibility map. Legacy mixed pairs are quarantined so a new DHCP client cannot inherit bypass; resave the policy to resolve ambiguity. Explicit IP whitelists in new seeds are confirmed address policies.

device_schedules

Seed value
Empty array
Change takes effect
Live through /api/v1/network/schedules

Each schedule has ip (IP or MAC despite its name), policy (block/whitelist), days, start_time, and end_time. Days are mon–sun; times are HH:MM in the server’s local timezone. Start is inclusive, end exclusive; equal start/end is invalid. Overnight windows belong to their starting day. The first matching schedule in saved order wins, including across matching IP and MAC identities.

client_aliases

Seed value
Empty array
Change takes effect
Live through /api/v1/network/aliases

Array of identifier (IP/MAC) and name (display label). Aliases change how clients are shown; they do not grant access, block queries, or create DNS records.

Linux ARP and IPv6 NDP discovery refreshes every five seconds and discards stale identities. While a MAC block or a blocking MAC schedule is active, an unidentified client receives REFUSED until identified. A MAC whitelist does not grant bypass before identification. Device discovery and counters are in memory and rebuild after restart; saved policies and aliases remain.

DEVICE SEED FRAGMENTS
[device_policies]
"aa:bb:cc:dd:ee:01" = "block"

[[device_schedules]]
ip = "192.168.1.25"
policy = "block"
days = ["mon", "tue", "wed", "thu", "fri"]
start_time = "21:00"
end_time = "07:00"

[[client_aliases]]
identifier = "192.168.1.25"
name = "Study laptop"

Services, SafeSearch, and local DNS

Service and domain policy runs before DNS rewriting or local answers. Configured local owners stay local; explicitly external CNAME targets are resolved separately.

blocked_services

Seed value
Empty array
Change takes effect
Live through /api/v1/config/services

Built-in service IDs to block: tiktok, youtube, facebook (includes Instagram), steam, fortnite, and roblox. Matching domains and their subdomains are blocked. Service rules take precedence over domain allowlists; use a service pause or disable the service block to allow it.

custom_services

Seed value
Empty array
Change takes effect
Live through /api/v1/config/services

Objects contain id, name, description, wildcards, and enabled. IDs identify service pauses; display names/descriptions do not affect matching. Entries in wildcards are bare domain suffixes such as example.com, not glob or regex syntax: they match the domain and its subdomains on label boundaries. Only enabled, unpaused services apply.

enforce_safesearch

Seed value
false
Change takes effect
Live through /api/v1/config/safesearch

Rewrites explicitly listed Google, YouTube, and Bing names to restricted targets. It does not rewrite all subdomains or inspect HTTPS content. Local records take precedence; whitelists and global/device protection pauses bypass rewriting. Service/domain rules may block a name before a rewrite.

local_dns_records

Seed value
Empty array
Change takes effect
Live through /api/v1/local-dns

Objects contain name, record_type (A, AAAA, CNAME), and value. A/AAAA values must be valid addresses; CNAME values are DNS target names. Case and trailing-dot spelling identify the same owner. Multiple address records are allowed; CNAME cannot coexist with A/AAAA or a conflicting target for the same owner. Domain and malicious-IP policy still applies.

A locally known name queried for an unconfigured type returns empty NOERROR (NODATA), not upstream NXDOMAIN. Local CNAME chains are followed with a hop bound; loops/excessive chains fail rather than resolving indefinitely. Changing a local record publishes a fresh local index; client-side cached answers can outlive that edit.

LOCAL DNS AND SERVICE SEED FRAGMENTS
[[local_dns_records]]
name = "nas.home.arpa"
record_type = "A"
value = "192.168.1.100"

[[custom_services]]
id = "games"
name = "Example games"
description = "Block the example gaming domain"
wildcards = ["games.example.com"]
enabled = true

Protection pauses

Pauses are saved expiry timestamps, not durations restarted at boot. A future expiry remains active after restart; time spent stopped still counts. Expired timers are ignored immediately on requests and cleaned up by housekeeping.

protection_pause_default_duration_secs

Seed value
172800 (48 hours)
Change takes effect
Live through /api/v1/protection/settings

Used when a pause request omits a duration. Changing the default does not extend existing timers. Explicit global/category/service durations are capped at 30 days; indefinite mode is a separate request. Device pauses allow 1–172800 seconds and reject indefinite mode.

protection_paused_until

Seed value
Absent (not paused)
Change takes effect
Live through /api/v1/protection/pause or /resume

Absolute Unix seconds for a global pause. Bypasses device restrictions, service/domain/IP filtering, discovery enforcement, and SafeSearch. CIDR admission and DNS rate limits remain active. Indefinite global pauses use the far-future sentinel 253402300799, not an empty value.

category_pauses

Seed value
Empty map
Change takes effect
Live through protection pause/resume

Map of category ID to Unix expiry seconds. Paused categories stop contributing sources, while their downloaded data is retained. Resume or expiry restores available rules from memory/disk before the next query without waiting for a successful network download. A base source or another category can still block the same domain.

service_pauses

Seed value
Empty map
Change takes effect
Live through protection pause/resume

Map of built-in/custom service ID to Unix expiry seconds. Temporarily skips that service’s block. Independent domain, category, and IP rules can still block its domains.

device_pauses

Seed value
Empty map
Change takes effect
Live through protection pause/resume; Network → Devices

Map of normalized IP/MAC identity to Unix expiry seconds. Temporarily bypasses that device’s static policy, schedules, service/domain/IP checks, discovery enforcement, and SafeSearch. Policies remain saved and resume at expiry. MAC-bound pauses follow address changes; IP pauses apply to anyone using the address. An unknown MAC cannot use an IP pause to evade an active MAC restriction. The device master switch controls these pauses too.

DEVICE PAUSE API BODY
{"scope":"device","id":"aa:bb:cc:dd:ee:01","duration_secs":3600}

Send that JSON body to POST /api/v1/protection/pause. To resume early, send the same scope and id to POST /api/v1/protection/resume. Other scopes are global, category, and service; global needs no ID. An already-active pause for the same normalized identity returns HTTP 409. Query GET /api/v1/protection/status for active timers.

Caches and DNS rate limits

Both DNS cache tiers are in memory and start cold after every process restart. Capacity settings are captured at startup; there is no general cache-capacity or DNS-rate editor.

cache_l1_max_entries

Seed value
100000
Change takes effect
Startup setting

Maximum front-cache entry count. L1 also has a weighted byte budget of 1024 bytes per configured entry, capped at 64 MiB. Oversized answers skip L1; eviction makes room for new entries. Pi Zero uses 5000 entries.

cache_l2_max_size_mb

Seed value
256 MiB
Change takes effect
Startup setting

Weighted response-storage budget, not a total process-memory cap. L1/L2 share response payloads; allocator/index overhead and filters, imports, logging, and the OS need additional memory. Must be positive; validation rejects a budget exceeding 75% of currently available memory when measurable. Pi Zero uses 16 MiB.

cache_negative_ttl_fallback_secs

Seed value
60
Change takes effect
Startup setting

TTL used for negative responses only when usable SOA TTL data is absent. With an SOA, the smaller of its RR TTL and MINIMUM is used; this setting never raises that value. Legacy seed spelling cache_negative_ttl_floor_secs is accepted as an alias, but do not set both names.

ratelimit_qps_per_worker

Seed value
1000
Change takes effect
Startup setting

Despite its legacy name, this is one shared per-client-IP allowance across all listener workers. It is a number of requests per configured window, not multiplied by CPU count. With a two-second window, 1000 means 1000 per two seconds. NAT clients share the quota. Rejected traffic receives REFUSED; whitelists and pauses do not exempt it.

ratelimit_window_duration_secs

Seed value
1
Change takes effect
Startup setting

Positive duration of the per-IP rate-limit window. Tune together with the allowance; changing the duration changes the effective average request rate.

ratelimit_max_ips

Seed value
100000
Change takes effect
Startup setting

Positive capacity for tracked source-IP limiter state, not a setting for how many DNS names can be cached. Pi Zero uses 1000. Counters are in memory and reset on restart.

Positive DNS answers age using their record TTLs. Negative answers use the SOA rule above. Transport failures and SERVFAIL are transient and are not stored as successful answers. Policy checks also examine cached CNAME chains and address answers so a cache hit does not bypass new filtering rules.

To clear server response and verdict caches without restarting, use the dashboard cache tool or POST /api/v1/cache/invalidate. This cannot clear clients’ or upstream providers’ caches. Cache clearing does not delete configuration, rule files, or logs.

Query logs and retention

DNS query history uses a separate SQLite file from persistent configuration. Restarting keeps committed history. Queued or unflushed records are not durable and can be lost during a crash, shutdown, or restart.

logging_enabled

Seed value
true
Change takes effect
Startup setting

Starts query logging and the live-tail broadcaster. Turning it off requires provisioning the saved startup setting; it does not delete an existing history database. Diagnostic process logs are separately controlled by RUST_LOG.

logging_db_path

Seed value
"logs.db"
Change takes effect
Startup setting

A SQLite file path, not a directory. Relative to the working directory. Both the logger and historical API reads capture this path at startup. Preserve it for history across upgrades; keep it separate from DNS_STATE_DB.

logging_channel_capacity

Seed value
100000
Change takes effect
Startup setting

Positive size of the bounded in-memory log queue. When full, new records are dropped and counted rather than delaying DNS queries. A larger queue absorbs bursts but costs RAM and is not durable storage. Pi Zero uses 5000.

logging_retention_days

Seed value
90
Change takes effect
Startup setting

Positive history-retention window. Retention runs at startup and then every 24 hours, dropping only fully expired UTC-day partitions and matching statistics. Data at the boundary can remain until the next sweep. Reducing retention does not restore previously deleted history. Pi Zero uses 7 days.

Log writes are batched, normally flushed at most every 200 ms or after 500 entries, with SQLite work kept off the DNS runtime. Historical statistics use retained rollups; they are not a copy of state.db. Back up live SQLite logs with an SQLite-aware backup or after stopping the service, rather than copying only the main file while its WAL is changing.

Administration and credentials

The default build serves the dashboard and JSON API. Dashboard-only builds still need the HTTP admin server; resolver-only builds omit it and should set api_enabled to false.

api_enabled

Seed value
true
Change takes effect
Startup setting

Starts the HTTP administration server when the binary includes feat-api. Disabling it removes dashboard/API access for that process, without disabling DNS. JSON-only and dashboard-only builds expose only their compiled surface.

api_bind_address

Seed value
"0.0.0.0"
Change takes effect
Startup setting

Admin listener IP. Prefer 127.0.0.1 when a same-host HTTPS proxy is the only intended peer; container networking may require a container-accessible bind with restricted published ports. This is independent of the DNS bind.

api_port

Seed value
8080
Change takes effect
Startup setting

HTTP listener port, 1–65535; must not be 53 or collide with the actual DNS_PORT. A custom DNS port is checked for collision after startup overrides are read.

api_allow_insecure_http

Seed value
true
Change takes effect
Startup setting

Allows direct HTTP administration. Set false before seeding to require HTTPS through a trusted TLS terminator and Secure cookies. Existing installations keep their saved value. When HTTP is permitted, HTTPS requests can still receive Secure cookies; do not send credentials over an untrusted plain-HTTP connection.

api_trusted_proxy_cidrs

Seed value
["127.0.0.1/32", "::1/128"]
Change takes effect
Startup setting or environment override

CIDRs allowed to supply forwarding headers. Only those peers may assert HTTPS or client addresses. The proxy must preserve public Host, overwrite X-Forwarded-Proto, and overwrite or safely append verified X-Forwarded-For. Use the actual proxy peer, not a whole client LAN. DNS_API_TRUSTED_PROXY_CIDRS overrides this at process startup without a state migration.

api_key

Seed value
Public sample password — replace before first start
Change takes effect
Initial seed; live password update afterward

Initial administrator password; not an API enable/disable token. Replace the seed’s published sample value before deployment. Dashboard password changes or POST /api/v1/config/password store a bcrypt hash, revoke existing sessions after a successful commit, and survive restart. New passwords require at least eight characters and at most 72 UTF-8 bytes. Editing the seed cannot rotate an existing saved password.

api_token

Seed value
Absent
Change takes effect
Initial seed; token issuance/revocation afterward

Optional dedicated JSON API credential of at least 16 characters; use a long random secret. On seed load it is converted to SHA-256 and omitted from serialized state. Do not configure it alongside api_token_sha256. After a token is issued, only that token authenticates JSON API requests; browser cookies never authenticate them.

api_token_sha256

Seed value
Absent
Change takes effect
Managed by token issuance/revocation

Persisted lowercase 64-character hex SHA-256 digest of the API token. A seed may supply this instead of plaintext. The digest is not the Bearer credential. Tokens survive restart and administrator password changes; revoke or rotate separately through Admin Security or /api/v1/auth/token.

api_ratelimit_qps

Seed value
20
Change takes effect
Startup setting

Positive per-client-IP admin request allowance per second. Login is additionally limited to ten attempts per minute per client; authentication/password work permits two concurrent bcrypt jobs. Excess work receives HTTP 429. Correct trusted-proxy configuration prevents unrelated users from unintentionally sharing the proxy’s quota.

Sessions versus API tokens

The dashboard uses a 12-hour HttpOnly, SameSite=Strict session cookie. Sessions are in memory: restart or logout invalidates them, even though the administrator password remains saved. Cookie-authenticated changes require the matching Origin and public Host.

JSON API requests use Authorization: Bearer <credential>. Until a dedicated token exists, the administrator credential is accepted. Issue one with POST /api/v1/auth/token; it is returned once and only the digest is saved. DELETE /api/v1/auth/token revokes it and returns API authentication to the administrator credential. A password change does not revoke a dedicated token.

Updates, backups, and troubleshooting

Keep application upgrades separate from configuration changes. Replacing a binary or container should retain the existing data and should not re-seed your installation.

What survives restart?

DataLocationAfter restart
Configuration, passwords, token digest, policies, schedules, pausesDNS_STATE_DBRestored from saved state. Future timers continue toward their original absolute expiry; expired ones no longer bypass protection.
Local domain and regex ruleslocal_allow.txt, local_deny.txt, regex_allow.txt, regex_deny.txt in the working directoryRestored before serving DNS. Dashboard/API rule changes persist in these files.
Auto-denies and malicious IPsauto_blocked.txt, malicious_ips.txt in the working directoryRestored. Disabling detection does not erase existing entries.
Remote blocklist copies and HTTP validatorsblocklist_cache/ in the working directoryUsed during startup, including when offline; background refresh updates successful sources.
Committed query history and rollupslogging_db_pathPreserved, subject to retention. Log queue entries not yet committed can be lost on a crash.
DNS caches, discovery cache, rate counters, device discovery, browser sessionsProcess memoryReset. Answers warm the cache again, neighbors are rediscovered, and administrators sign in again.

Upgrade an existing installation

  1. Review the release’s configuration/state compatibility and record any restart-required settings you have saved.
  2. Stop the service for a simple consistent backup, or use SQLite-aware backups for live databases. Save state.db, rule files, cached sources, and query history if needed, along with the seed and service environment. Keep credentials and backups private.
  3. Replace the binary and matching dashboard assets, or rebuild/recreate the container while retaining its named data volume. Do not use volume deletion as an upgrade step.
  4. Start the service and check startup diagnostics, DNS queries, source status, and admin sign-in. The new release reads saved state; a newly shipped seed does not overwrite it. Newly introduced optional fields may receive their code defaults when absent from older saved state.

For startup fields without an editor, plan a stopped-service state migration or an intentional new installation with a reviewed complete seed. There is no supported general-purpose SQL editing workflow. Pointing DNS_STATE_DB at an empty path starts a separate state; deleting the old database loses its settings and credentials and is not a routine update or password-reset procedure.

Manual changes to rule files are not a supported hot-reload interface. Use dashboard/API list management while the service is running; preserve the files when restarting or replacing the application.

Common surprises

I changed TOML, but nothing changed.
The existing database is authoritative. Use the relevant settings editor or endpoint. Changing DNS_CONFIG does not override existing state.
The settings page shows new upstreams, but traffic uses the old servers.
The new pool is saved for restart. Restart the process and let client-side caches expire.
I changed custom DoH, but a cached name still shows the earlier result.
The DoH client updates live; existing server/client caches can retain answers. Use the server cache tool if appropriate and account for client TTLs.
I paused a category or service, but its domain is still blocked.
A base list, another category, service, local deny, or malicious-IP rule can still match. Pauses only remove their own protection scope.
Primary DNS works, but protected lookups receive SERVFAIL.
If sinkhole discovery is enabled, check the threat resolver and discovery budget too. Also inspect local DNSSEC failures; an upstream answer alone does not prove validation.
A different device inherited an address and cannot use an old whitelist.
MAC policies intentionally avoid transferring bypass across DHCP changes. Check neighbor visibility and resave an ambiguous legacy policy; do not grant a broad IP bypass to work around identification.
The service restarts, but I cannot stay signed in.
Browser sessions are process-local. Sign in again; the saved password and dedicated API token remain valid unless explicitly changed.

Examples are fragments

Use the complete shipped seed as the starting file. Examples above describe selected fields; do not concatenate them blindly. In TOML, root scalar settings must appear before [table] or [[array_of_tables]] sections. Merge keys into their correct tables and do not define the same key twice.

Source references: configuration model and validation, startup loading, settings endpoints, and repository configuration guide. Use the documentation and code matching the version you deploy.

Keep your settings. Make changes deliberately.

Back to top ↑