Skip to content

ferrous-dns.toml Reference

Complete annotated reference for every option in ferrous-dns.toml. For deployment-ready starting points, see the Configuration Overview.


Loading the Config File

ferrous-dns --config path/to/ferrous-dns.toml

Or via environment variable:

FERROUS_CONFIG=path/to/ferrous-dns.toml ferrous-dns

All sections are optional

Every section and every key has a built-in default. You only need to include what you want to override.


Quick Reference

Section Purpose Detail
[server] Ports, bind address, Pi-hole compat, PROXY Protocol Server config
[server.web_tls] HTTPS for the web dashboard and REST API Server config
[server.encrypted_dns] DoT, DoH, and DoQ server-side listeners Encrypted DNS
[auth] Session authentication for dashboard and API Security
[auth.admin] Admin username and password hash Security
[auth.webauthn] Passkey / WebAuthn relying-party settings Two-Factor Auth
[dns] Upstream fallback, timeouts, DNSSEC, privacy controls DNS & Upstreams
[[dns.pools]] Named upstream server pools with strategy and priority Upstream Management
[dns.health_check] Probes to detect and evict unhealthy upstreams Upstream Management
[dns] cache keys L1/L2 cache, eviction, and optimistic refresh Cache configuration
[dns.rate_limit] Token bucket rate limiter per client subnet Rate Limiting
[dns.tunneling_detection] Two-phase DNS tunneling detector Malware Detection
[dns.dga_detection] Domain Generation Algorithm detector Malware Detection
[dns.nxdomain_hijack] ISP NXDOMAIN hijack detection and reversal Malware Detection
[dns.response_ip_filter] Block responses resolving to known C2 IPs Malware Detection
[[dns.local_records]] Static A/AAAA records with auto-PTR DNS & Upstreams
[dns.dns_cookies] DNS Cookies anti-spoofing (RFC 7873) Security
[blocking] Ad and malware blocking via blocklists Blocking & Filtering
[dns64] NAT64 AAAA synthesis for IPv6-only clients (RFC 6147) DNS64
[logging] Log level
[database] SQLite persistence, query log pipeline, connection pools Database configuration

[server]

Controls which ports and interfaces Ferrous DNS listens on, and enables optional compatibility and protocol features.

ferrous-dns.toml
[server]
dns_port                 = 53
web_port                 = 8080
bind_address             = "0.0.0.0"
pihole_compat            = false
proxy_protocol_enabled   = false
metrics_enabled          = false
Option Type Default Description
dns_port int 53 UDP and TCP port for DNS queries
web_port int 8080 HTTP/HTTPS port for the dashboard and REST API
bind_address str "0.0.0.0" Default address every listener binds to; "0.0.0.0" is all IPv4 interfaces, "[::]" is dual-stack (IPv4 + IPv6)
pihole_compat bool false Expose Pi-hole v6 compatible API at /api/*; Ferrous DNS native API moves to /ferrous/api/*
proxy_protocol_enabled bool false Enable PROXY Protocol v2 on TCP DNS and DoT listeners
metrics_enabled bool false Serve an unauthenticated Prometheus text-exposition endpoint at /metrics on the web port
cors_allowed_origins list (dashboard origin) Browser origins allowed to call the REST API via CORS

PROXY Protocol

Only enable proxy_protocol_enabled when a trusted load balancer always sits in front of Ferrous DNS. Without a load balancer, all TCP DNS connections will be rejected because the server expects a PROXY Protocol header on every connection.

See Server configuration.


[server.web_tls]

Enables HTTPS for the web dashboard and REST API. When enabled = true, HTTPS is served on web_port with automatic redirect from plain HTTP. If the cert or key files are absent at startup, the server logs a warning and falls back to plain HTTP.

ferrous-dns.toml
[server.web_tls]
enabled       = false
tls_cert_path = "/data/cert.pem"
tls_key_path  = "/data/key.pem"
Option Type Default Description
enabled bool false Enable HTTPS for the web server
tls_cert_path str "/data/cert.pem" Path to the PEM-encoded TLS certificate
tls_key_path str "/data/key.pem" Path to the PEM-encoded TLS private key

Docker paths

When running in Docker, tls_cert_path and tls_key_path must point to paths accessible from inside the container. Mount the certificate directory as a volume.


[server.encrypted_dns]

Enables DNS-over-TLS (DoT), DNS-over-HTTPS (DoH), and DNS-over-QUIC (DoQ) server-side listeners. This section is commented out by default. If the cert or key files are missing at startup, the affected listeners are skipped with a warning; plain DNS continues normally.

ferrous-dns.toml
[server.encrypted_dns]
dot_enabled   = false
dot_port      = 853
doh_enabled   = false
# doh_port    = 443           # omit to co-host DoH on web_port
doq_enabled   = false
doq_port      = 853           # UDP port; no collision with dot_port (TCP)
tls_cert_path = "/data/cert.pem"
tls_key_path  = "/data/key.pem"
Option Type Default Description
dot_enabled bool false Enable DNS-over-TLS listener
dot_port int 853 TCP port for DoT (RFC 7858 standard: 853)
dot_bind_address str Address for the DoT listener; inherits bind_address when omitted
doh_enabled bool false Enable the /dns-query DoH endpoint
doh_port int Dedicated HTTPS port for DoH; omit to co-host on web_port
doh_bind_address str Address for the dedicated DoH listener; ignored when doh_port is omitted
doq_enabled bool false Enable the DNS-over-QUIC listener
doq_port int 853 UDP port for DoQ (RFC 9250 standard: 853)
doq_bind_address str Address for the DoQ listener; inherits bind_address when omitted
tls_cert_path str "/data/cert.pem" Path to the PEM-encoded TLS certificate
tls_key_path str "/data/key.pem" Path to the PEM-encoded TLS private key

See Encrypted DNS.


[auth]

Authentication settings for the dashboard and REST API. When enabled = false, all endpoints are publicly accessible without credentials.

ferrous-dns.toml
[auth]
enabled                      = true
session_ttl_hours            = 24
remember_me_days             = 30
login_rate_limit_attempts    = 5
login_rate_limit_window_secs = 900
totp_issuer                  = "Ferrous DNS"
mfa_challenge_ttl_secs       = 300
Option Type Default Description
enabled bool true Enable or disable authentication globally
session_ttl_hours int 24 Default session lifetime in hours (without "Remember Me")
remember_me_days int 30 Extended session lifetime in days with "Remember Me"
login_rate_limit_attempts int 5 Max failed login attempts before lockout
login_rate_limit_window_secs int 900 Lockout window duration in seconds (15 min)
totp_issuer str "Ferrous DNS" Issuer label shown by authenticator apps next to the TOTP account
mfa_challenge_ttl_secs int 300 Lifetime of a pending second-factor / passkey ceremony, in seconds (5 min)

See Two-Factor Authentication for the TOTP and passkey enrollment flows.


[auth.admin]

Admin credentials. The password_hash field stores an Argon2id hash. When left empty, Ferrous DNS presents a setup wizard on first access.

ferrous-dns.toml
[auth.admin]
username      = "admin"
password_hash = ""
Option Type Default Description
username str "admin" Admin username
password_hash str "" Argon2id hash; empty string triggers the setup wizard on first run

Setting the password

Set the admin password via the web setup wizard on first run — the Argon2id hash is written back to the config file automatically. To reset a forgotten password, clear this field (password_hash = "") and restart; the setup wizard runs again on next access.

See Security.


[auth.webauthn]

WebAuthn relying-party settings for passkeys. Passkeys stay inert until both rp_id and rp_origin are set — TOTP works regardless. WebAuthn requires a secure context: rp_origin must be HTTPS (or http://localhost), and rp_id must be a registrable domain the origin belongs to. A server reached by bare IP over plain HTTP cannot use passkeys, and those users rely on TOTP instead.

ferrous-dns.toml
[auth.webauthn]
rp_id     = "dns.example.com"
rp_origin = "https://dns.example.com"
Option Type Default Description
rp_id str "" Relying-party ID — the effective/registrable domain (e.g. dns.example.com, or localhost for local testing)
rp_origin str "" Full relying-party origin URL (e.g. https://dns.example.com, or http://localhost:PORT); must match how the browser reaches the dashboard

rp_origin must match the browser's address bar

Passkey registration and login fail if the origin the browser uses does not exactly match rp_origin. Use the same scheme, host, and port the user types to reach the dashboard.

See Two-Factor Authentication.


[dns]

Core DNS resolver options: upstream fallback, timeouts, DNSSEC validation, local domain handling, and privacy controls. Cache options also live under [dns] and are documented in the Cache keys section below.

ferrous-dns.toml
[dns]
upstream_servers  = []
query_timeout     = 3
default_strategy  = "Parallel"
dnssec_mode       = "Permissive"
block_private_ptr = true
block_non_fqdn    = true
rebinding_protection_enabled = true
local_domain      = "lan"
local_dns_server  = "10.0.0.1:53"
mdns_enabled      = false
qname_case_randomization = false
Option Type Default Description
upstream_servers list [] Fallback upstream servers used when no pool matches; supports all URI schemes
query_timeout int 3 Seconds to wait for an upstream response before trying the next server
default_strategy str "Parallel" Default resolution strategy for upstream_servers: "Parallel" or "Sequential"
dnssec_mode str "Permissive" DNSSEC enforcement: "Off" (no validation), "Permissive" (validate + tag, no SERVFAIL), "Strict" (SERVFAIL on Bogus). The legacy dnssec_enabled bool is still accepted (true → Permissive, false → Off)
block_private_ptr bool true Block PTR lookups for private/RFC-1918 IP ranges
block_non_fqdn bool false Block queries for non-fully-qualified domain names
rebinding_protection_enabled bool true Block responses where a public domain resolves to a private/RFC-1918 IP (DNS rebinding protection)
rebinding_allowlist list [] Domains exempt from rebinding protection (split-horizon DNS: VPN endpoints, router admin panels)
local_domain str "lan" Local domain suffix appended to short hostnames
local_dns_server str "10.0.0.1:53" Router or DHCP server used for PTR lookups and client hostname resolution
mdns_enabled bool false Enable the passive mDNS/Bonjour listener (UDP 5353 multicast) for device discovery; requires host networking in Docker
qname_case_randomization bool false Randomize QNAME letter case on upstream queries of every record type (draft-vixie-dns-0x20) for extra anti-spoofing entropy; off because some upstreams do not preserve case

See DNS & Upstreams.


[[dns.pools]]

Named upstream server pools. Multiple pools are selected by priority order; the pool with the lowest priority value is tried first. Each pool specifies its own resolution strategy independently of default_strategy.

ferrous-dns.toml
[[dns.pools]]
name     = "cloudflare"
strategy = "Parallel"
priority = 1
servers  = [
    "https://cloudflare-dns.com/dns-query",
    "https://1.1.1.1/dns-query",
]

[[dns.pools]]
name     = "google"
strategy = "Failover"
priority = 2
servers  = [
    "tls://8.8.8.8:853",
    "tls://8.8.4.4:853",
]
Option Type Default Description
name str Pool identifier used in logs and the dashboard
strategy str "Parallel" Resolution strategy: "Parallel", "Balanced", or "Failover"
priority int 1 Pool priority; lower value = higher priority
servers list [] List of upstream server URIs

Supported URI schemes

udp://8.8.8.8:53                          Plain UDP
tcp://8.8.8.8:53                          Plain TCP
tls://1.1.1.1:853                         DNS-over-TLS
https://cloudflare-dns.com/dns-query      DNS-over-HTTPS
doq://dns.adguard-dns.com:853             DNS-over-QUIC
h3://dns.google/dns-query                 HTTP/3

See Upstream Management.


[dns.health_check]

Periodic probes that detect and remove unhealthy upstream servers from the active pool. A server is marked unhealthy after failure_threshold consecutive failed probes, and restored after success_threshold consecutive successes.

ferrous-dns.toml
[dns.health_check]
interval           = 30
timeout            = 2000
failure_threshold  = 3
success_threshold  = 2
Option Type Default Description
interval int 30 Seconds between probes per server
timeout int 2000 Milliseconds to wait for a probe response
failure_threshold int 3 Consecutive failures before marking a server unhealthy
success_threshold int 2 Consecutive successes required to restore a server to healthy status

Cache keys under [dns]

These keys live directly under [dns] (not a sub-table). They configure the L1/L2 in-memory DNS cache, eviction strategy, and optimistic background refresh.

Basic cache options

ferrous-dns.toml
[dns]
cache_enabled                    = true
cache_ttl                        = 7200
cache_min_ttl                    = 300
cache_max_ttl                    = 86400
cache_max_entries                = 200000
cache_eviction_strategy          = "hit_rate"
cache_compaction_interval        = 600
cache_batch_eviction_percentage  = 0.1
cache_adaptive_thresholds        = false
# cache_shard_amount             = 512
# cache_inflight_shards          = 64
Option Type Default Description
cache_enabled bool true Enable the DNS response cache
cache_ttl int 7200 Default TTL in seconds when an upstream record carries none
cache_min_ttl int 300 Minimum TTL; records with lower TTLs are clamped to this value
cache_max_ttl int 86400 Maximum TTL; records with higher TTLs are clamped
cache_max_entries int 200000 Maximum number of entries in the L2 cache
cache_eviction_strategy str "hit_rate" Eviction policy: "hit_rate", "lfu", or "lru"
cache_compaction_interval int 600 Seconds between compaction runs that remove expired entries
cache_batch_eviction_percentage float 0.1 Fraction of the cache evicted in one pass when full (0.1 = 10%)
cache_adaptive_thresholds bool false Auto-tune eviction thresholds based on observed hit rates
cache_shard_amount int auto L2 cache shard count; auto = 4 x CPU cores rounded up to next power of 2
cache_inflight_shards int auto In-flight coalescing map shard count; auto = 2 x CPU cores, rounded to power of 2 (min 8, max 128)

Optimistic refresh

Background refresh renews popular entries before they expire, keeping the cache hit rate high without cold misses.

ferrous-dns.toml
[dns]
cache_optimistic_refresh  = true
cache_refresh_threshold   = 0.75
cache_min_hit_rate        = 2.0
cache_min_frequency       = 10
cache_access_window_secs  = 43200
Option Type Default Description
cache_optimistic_refresh bool true Enable background refresh for popular entries
cache_refresh_threshold float 0.75 Schedule a refresh when this fraction of the original TTL has been consumed
cache_min_hit_rate float 2.0 Minimum hits per minute for an entry to qualify for refresh
cache_min_frequency int 10 Minimum total hits before an entry is eligible for refresh
cache_access_window_secs int 43200 Access window in seconds for refresh eligibility (43200 = 12 hours)

LFU-K eviction parameters

Used when cache_eviction_strategy is "hit_rate" or "lfu".

ferrous-dns.toml
[dns]
cache_min_lfuk_score     = 1.5
cache_lfuk_history_size  = 10
Option Type Default Description
cache_min_lfuk_score float 1.5 Minimum LFU-K score threshold for eviction candidates
cache_lfuk_history_size int 10 Number of recent access timestamps tracked per entry

See Cache configuration.


[dns.rate_limit]

Token bucket rate limiter applied per client subnet. Protects against query floods without impacting normal traffic. Separate budgets are enforced for NXDOMAIN-heavy clients.

ferrous-dns.toml
[dns.rate_limit]
enabled                    = true
queries_per_second         = 1000
burst_size                 = 500
ipv4_prefix_len            = 24
ipv6_prefix_len            = 48
whitelist                  = []
nxdomain_per_second        = 50
slip_ratio                 = 2
dry_run                    = false
tcp_max_connections_per_ip = 30
dot_max_connections_per_ip = 15
doq_max_connections_per_ip = 15
stale_entry_ttl_secs       = 300
Option Type Default Description
enabled bool true Enable the rate limiter
queries_per_second int 1000 Sustained query budget per client subnet
burst_size int 500 Maximum burst above the sustained rate
ipv4_prefix_len int 24 Group IPv4 clients by this prefix length (e.g. /24 subnet)
ipv6_prefix_len int 48 Group IPv6 clients by this prefix length (e.g. /48 subnet)
whitelist list [] CIDRs exempt from rate limiting
nxdomain_per_second int 50 Stricter budget applied specifically to NXDOMAIN responses
slip_ratio int 2 1 in N rate-limited responses sends TC=1 to force a TCP retry
dry_run bool false Log rate limit events without enforcing them
tcp_max_connections_per_ip int 30 Maximum concurrent TCP DNS connections per client IP
dot_max_connections_per_ip int 15 Maximum concurrent DoT connections per client IP
doq_max_connections_per_ip int 15 Maximum concurrent DoQ connections per client IP
stale_entry_ttl_secs int 300 Seconds of inactivity before a token bucket entry is evicted

See Rate Limiting.


[dns.tunneling_detection]

Two-phase DNS tunneling detector. Phase 1 runs on the hot path in O(1) time, checking FQDN length, label length, and NULL query type. Phase 2 runs statistical analysis in the background (Shannon entropy, query rate, unique subdomains, record type proportions).

ferrous-dns.toml
[dns.tunneling_detection]
enabled                      = true
action                       = "block"
max_fqdn_length              = 120
max_label_length             = 50
block_null_queries           = true
entropy_threshold            = 3.8
query_rate_per_apex          = 50
unique_subdomain_threshold   = 30
txt_proportion_threshold     = 0.05
nxdomain_ratio_threshold     = 0.20
confidence_threshold         = 0.7
stale_entry_ttl_secs         = 300
domain_whitelist             = []
client_whitelist             = []
Option Type Default Description
enabled bool true Enable tunneling detection
action str "block" Action on detection: "alert", "block", or "throttle"
max_fqdn_length int 120 Phase 1: FQDNs longer than this are blocked immediately
max_label_length int 50 Phase 1: labels longer than this are blocked immediately
block_null_queries bool true Phase 1: block NULL (type 10) queries
entropy_threshold float 3.8 Phase 2: Shannon entropy in bits/char above which a domain is flagged
query_rate_per_apex int 50 Phase 2: queries per minute per client+apex pair above which a domain is flagged
unique_subdomain_threshold int 30 Phase 2: unique subdomains per minute before flagging
txt_proportion_threshold float 0.05 Phase 2: TXT query proportion above 5% triggers a flag
nxdomain_ratio_threshold float 0.20 Phase 2: NXDOMAIN ratio above 20% triggers a flag
confidence_threshold float 0.7 Phase 2: minimum combined confidence score (0–1) required to act
stale_entry_ttl_secs int 300 Seconds of inactivity before a tracking entry is evicted
domain_whitelist list [] Domains exempt from tunneling detection
client_whitelist list [] Client CIDRs exempt from tunneling detection

See Malware Detection.


[dns.dga_detection]

Detects Domain Generation Algorithm domains used by malware families such as Conficker, Mirai, and Emotet. Phase 1 runs weighted mini-scoring on the hot path (entropy, consonant ratio, digit ratio, length). Phase 2 runs n-gram language model scoring in the background.

ferrous-dns.toml
[dns.dga_detection]
enabled                       = true
action                        = "block"
hot_path_confidence_threshold = 0.40
sld_entropy_threshold         = 3.5
sld_max_length                = 24
consonant_ratio_threshold     = 0.75
digit_ratio_threshold         = 0.3
ngram_score_threshold         = 0.6
dga_rate_per_client           = 10
confidence_threshold          = 0.65
stale_entry_ttl_secs          = 300
domain_whitelist              = []
client_whitelist              = []
Option Type Default Description
enabled bool true Enable DGA detection
action str "block" Action on detection: "alert" or "block"
hot_path_confidence_threshold float 0.40 Phase 1: hot-path score threshold; typically requires 2 or more signals
sld_entropy_threshold float 3.5 Shannon entropy of the second-level domain
sld_max_length int 24 Maximum SLD character length before the domain is considered suspicious
consonant_ratio_threshold float 0.75 Consonant fraction of the SLD above which the domain is flagged
digit_ratio_threshold float 0.3 Digit fraction of the SLD above which the domain is flagged
ngram_score_threshold float 0.6 Phase 2: n-gram language model score threshold
dga_rate_per_client int 10 DGA-flagged queries per minute per client before action is escalated
confidence_threshold float 0.65 Phase 2: minimum combined confidence score required to act
stale_entry_ttl_secs int 300 Seconds of inactivity before a tracking entry is evicted
domain_whitelist list [] Domains exempt from DGA detection
client_whitelist list [] Client CIDRs exempt from DGA detection

See Malware Detection.


[dns.nxdomain_hijack]

Detects ISPs that intercept NXDOMAIN responses and substitute advertising IP addresses. Background probes test each upstream with random .invalid domains (RFC 6761). Discovered hijack IPs are recorded, and any hot-path response containing them is converted back to a proper NXDOMAIN.

ferrous-dns.toml
[dns.nxdomain_hijack]
enabled              = true
action               = "block"
probe_interval_secs  = 300
probe_timeout_ms     = 5000
probes_per_round     = 3
hijack_ip_ttl_secs   = 3600
Option Type Default Description
enabled bool true Enable NXDOMAIN hijack detection
action str "block" "alert" to log only; "block" to convert hijacked responses back to NXDOMAIN
probe_interval_secs int 300 Seconds between probe rounds per upstream server
probe_timeout_ms int 5000 Milliseconds to wait for a probe response
probes_per_round int 3 Number of probe queries sent per upstream per round
hijack_ip_ttl_secs int 3600 Seconds before an unconfirmed hijack IP entry is evicted

See Malware Detection.


[dns.response_ip_filter]

Blocks DNS responses that resolve to known command-and-control server IP addresses. Downloads IP threat feeds and checks every DNS response. Disabled by default — opt-in.

ferrous-dns.toml
[dns.response_ip_filter]
enabled                 = false
action                  = "block"
ip_list_urls            = []
refresh_interval_secs   = 86400
ip_ttl_secs             = 604800
Option Type Default Description
enabled bool false Enable response IP filtering (opt-in)
action str "block" "alert" to log only; "block" to return NXDOMAIN
ip_list_urls list [] Feed URLs; one IP per line, # comments are supported
refresh_interval_secs int 86400 Seconds between feed refreshes (24 hours)
ip_ttl_secs int 604800 Seconds before an IP entry expires if not re-confirmed by a feed refresh (7 days)

Example feeds

https://feodotracker.abuse.ch/downloads/ipblocklist.txt
https://sslbl.abuse.ch/blacklist/sslipblacklist.txt

See Malware Detection.


[[dns.local_records]]

Static A or AAAA records served directly from the cache, bypassing upstream entirely. An automatic PTR record is generated for every A record.

ferrous-dns.toml
[[dns.local_records]]
hostname    = "nas"
domain      = "local"
ip          = "192.168.1.50"
record_type = "A"
ttl         = 300

[[dns.local_records]]
hostname    = "printer"
domain      = "local"
ip          = "192.168.1.51"
record_type = "A"
ttl         = 300
Option Type Default Description
hostname str Hostname without the domain suffix
domain str Domain suffix (e.g. "local", "lan")
ip str IP address for this record
record_type str "A" Record type: "A" or "AAAA"
ttl int 300 TTL in seconds

See DNS & Upstreams.


[blocking]

DNS-based ad and malware blocking using downloaded blocklists. Blocklists are managed through the dashboard. Custom per-domain overrides can be specified directly in the config.

ferrous-dns.toml
[blocking]
enabled        = true
custom_blocked = []
whitelist      = []
block_mode     = "null_ip"
block_ttl      = 60
# sinkhole_ipv4 = "192.168.1.2"
# sinkhole_ipv6 = "fd00::2"
Option Type Default Description
enabled bool true Enable DNS blocking globally
custom_blocked list [] Additional domains to block beyond any active blocklists
whitelist list [] Domains that are always allowed, even if present in a blocklist
block_mode string "null_ip" How blocked domains are answered: null_ip (cacheable 0.0.0.0/::, recommended), nxdomain, nodata, or refused (legacy, non-cacheable)
block_ttl int 60 TTL in seconds clients/resolvers cache a blocked answer; also drives the synthetic SOA minimum for nxdomain/nodata
sinkhole_ipv4 string (unset) Custom A target for null_ip blocks (e.g. a LAN block-page server); unset returns 0.0.0.0. See Custom Sinkhole IP
sinkhole_ipv6 string (unset) Custom AAAA target for null_ip blocks; unset returns ::

Restart required

block_mode, block_ttl, sinkhole_ipv4, and sinkhole_ipv6 are applied at startup; changing them needs a server restart. See Block Response Mode for what each mode returns.

See Blocking & Filtering.


[dns64]

NAT64 AAAA synthesis (RFC 6147). Synthesizes AAAA records for IPv4-only domains so IPv6-only clients can reach them through a NAT64 gateway. Requires a NAT64 gateway on the network — without one, IPv6-only clients break. Applied at startup; changes need a restart.

ferrous-dns.toml
[dns64]
enabled = false
prefix  = "64:ff9b::/96"
Option Type Default Description
enabled bool false Enable DNS64 AAAA synthesis; only turn on with a NAT64 gateway present
prefix str "64:ff9b::/96" NAT64 prefix; only /96 is supported (well-known prefix per RFC 6052)

See DNS64.


[dns.dns_cookies]

DNS Cookies (RFC 7873) anti-spoofing. The server echoes an HMAC-SHA256 server cookie on every response so clients can verify they are talking to the same server, mitigating UDP spoofing and amplification abuse. Enabled by default.

ferrous-dns.toml
[dns.dns_cookies]
enabled              = true
server_secret        = ""
secret_rotation_secs = 3600
require_valid_cookie = false
Option Type Default Description
enabled bool true Enable DNS Cookies on UDP responses
server_secret str "" (ephemeral) 64-char hex (32 bytes) secret so cookies survive restarts; generate with openssl rand -hex 32. Empty uses an ephemeral secret regenerated on each restart
secret_rotation_secs int 3600 Seconds between server-secret rotations
require_valid_cookie bool false Strict mode: refuse queries without a valid cookie (REFUSED + EDE 25). Only enable when every client supports RFC 7873

Do not commit a shared server_secret

Leave server_secret empty (ephemeral) or generate a unique value per deployment. A fixed secret baked into an image means every install shares the same cookie key, defeating the anti-spoofing guarantee.

Nested under [dns]

A top-level [dns_cookies] table is silently ignored — the section must be [dns.dns_cookies].

See Security.


[logging]

ferrous-dns.toml
[logging]
level = "info"
Option Type Default Description
level str "info" Log verbosity: "error", "warn", "info", "debug", or "trace"

debug and trace levels

debug and trace are verbose and should only be used for troubleshooting. They emit hot-path events on every DNS query and may measurably impact throughput on high-load deployments.


[database]

SQLite persistence for query logs, client records, blocklists, groups, and settings. The write pipeline is fully async so disk I/O never blocks the DNS hot path.

Basic options

ferrous-dns.toml
[database]
path                      = "ferrous-dns.db"
log_queries               = true
queries_log_stored        = 30
client_tracking_interval  = 60
Option Type Default Description
path str "ferrous-dns.db" Path to the SQLite database file
log_queries bool true Store every DNS query for analytics and the query log dashboard
queries_log_stored int 30 Days to retain query log entries before automatic cleanup
client_tracking_interval int 60 Minimum seconds between consecutive last-seen writes for the same client IP

Query-log write pipeline

ferrous-dns.toml
[database]
query_log_channel_capacity  = 10000
query_log_max_batch_size    = 500
query_log_flush_interval_ms = 100
query_log_sample_rate       = 1
client_channel_capacity     = 4096
Option Type Default Description
query_log_channel_capacity int 10000 Async channel buffer size in entries
query_log_max_batch_size int 500 Maximum entries per INSERT transaction
query_log_flush_interval_ms int 100 Milliseconds between flush cycles
query_log_sample_rate int 1 Log 1 out of every N queries; 1 = log all, 10 = log 1 in 10
client_channel_capacity int 4096 Async channel buffer size for client last-seen updates

Connection pools

ferrous-dns.toml
[database]
write_pool_max_connections       = 2
read_pool_max_connections        = 4
query_log_pool_max_connections   = 2
write_busy_timeout_secs          = 30
read_busy_timeout_secs           = 15
read_acquire_timeout_secs        = 15
Option Type Default Description
write_pool_max_connections int 2 Maximum connections in the write pool
read_pool_max_connections int 4 Maximum connections in the read pool
query_log_pool_max_connections int 2 Maximum connections in the query-log write pool
write_busy_timeout_secs int 30 Seconds to wait for the write lock before returning an error
read_busy_timeout_secs int 15 Seconds to wait for a read connection before returning an error
read_acquire_timeout_secs int 15 Seconds to wait to acquire a connection from the read pool

SQLite tuning

ferrous-dns.toml
[database]
wal_autocheckpoint             = 0
wal_checkpoint_interval_secs   = 120
sqlite_cache_size_kb           = 16384
sqlite_mmap_size_mb            = 64
Option Type Default Description
wal_autocheckpoint int 0 SQLite WAL autocheckpoint threshold in pages; 0 disables automatic checkpointing (a background job manages it instead)
wal_checkpoint_interval_secs int 120 Seconds between WAL PASSIVE checkpoint runs by the background job
sqlite_cache_size_kb int 16384 SQLite page cache size in KB (default: 16 MB)
sqlite_mmap_size_mb int 64 Memory-mapped I/O size in MB; 0 disables mmap

See Database configuration.