Skip to content

Server Configuration

The [server] section controls ports, bind address, API authentication, and encrypted DNS listeners.


Basic Options

[server]
dns_port = 53
web_port = 8080
bind_address = "0.0.0.0"
metrics_enabled = false
Option Default Description
dns_port 53 UDP and TCP port for DNS queries
web_port 8080 HTTP/HTTPS port for the dashboard and REST API
bind_address 0.0.0.0 Default address every listener binds to. Use a specific IP to restrict access, or [::] for dual-stack
metrics_enabled false Serve an unauthenticated Prometheus text-exposition endpoint at /metrics on the web port (opt-in)

Dual-stack (IPv4 + IPv6)

bind_address = "[::]" makes every listener — Do53 (UDP and TCP), DoT, DoH, DoQ, and the web dashboard — serve IPv4 and IPv6 clients on a single dual-stack socket:

[server]
bind_address = "[::]"

IPv4 clients arrive on that socket in v4-mapped form (::ffff:192.168.1.10). Do53, DoT, and DoQ normalise them back to plain IPv4 before anything client-facing sees them, so client groups, per-client rules, the per-IP connection limit, and the query log key a device the same way whichever of those protocols it used. The same normalisation is applied to the address a PROXY protocol v2 header carries.

DoH is the exception: it never looks at the socket peer. It reads the client from the X-Real-IP / X-Forwarded-For headers its reverse proxy sets, so what a DoH client is keyed as depends on what the proxy sends.

Upgrading from a release that stored mapped addresses

Earlier releases stored the v4-mapped form for DoT and DoQ clients, so a device could end up with two client rows. A migration runs once on first start and rewrites them: ::ffff:10.0.0.1 becomes 10.0.0.1, a mapped row is merged into its plain twin (query counts add up, and a group assigned to the mapped row is kept unless the plain row already had one), the query log follows, and a subnet written as ::ffff:10.0.0.0/104 becomes 10.0.0.0/8. Nothing needs to be done by hand.

A bare bind_address = "::" works too; the brackets are added automatically when the listen address is built.

IPv6 must exist in the kernel

The DNS listeners — Do53 (UDP and TCP), DoT, and DoQ — are created as AF_INET6 sockets with IPV6_V6ONLY off, even for an IPv4-only bind_address (which is bound in v4-mapped form). A kernel booted with ipv6.disable=1 or built without CONFIG_IPV6 cannot create them, and those listeners will fail to start. IPv6 does not need to be configured on the network — it only needs to be available in the kernel. The dedicated DoH listener and the web dashboard bind the address as given, so they are unaffected.

Per-listener bind addresses

Each encrypted-DNS listener can override bind_address, which is useful when the protocols should not all be exposed on the same interface:

[server]
bind_address = "0.0.0.0"            # Do53 and the dashboard stay on IPv4

[server.encrypted_dns]
dot_enabled      = true
dot_bind_address = "[::]"           # DoT is dual-stack
doq_enabled      = true
doq_bind_address = "192.168.1.10"   # DoQ only on the LAN address

See Encrypted DNS for the full list of options.


Authentication

Ferrous DNS supports session-based authentication and API tokens to protect the dashboard and REST API.

Enabling Authentication

ferrous-dns.toml
[auth]
enabled = true                          # Enable authentication globally
session_ttl_hours = 24                  # Session lifetime without "Remember Me"
remember_me_days = 30                   # Session lifetime with "Remember Me"
login_rate_limit_attempts = 5           # Max failed attempts before lockout
login_rate_limit_window_secs = 900      # Lockout window (15 min)

[auth.admin]
username = "admin"                      # Admin username
password_hash = ""                      # Argon2id hash (set via setup wizard; empty triggers it)
Option Type Default Description
enabled bool true Enable or disable authentication globally
session_ttl_hours int 24 Default session lifetime in hours
remember_me_days int 30 Extended session lifetime 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
username str admin Admin username
password_hash str "" Argon2id password hash

First-Run Setup

When password_hash is empty, Ferrous DNS shows a setup wizard on the dashboard. Set the admin password via the web UI — the Argon2id hash is written to the config file automatically.

Session Authentication

Users log in with username and password. A ferrous_session cookie is set on success. The "Remember Me" option extends the session from session_ttl_hours to remember_me_days.

API Token Authentication

API tokens provide programmatic access via the X-Api-Key header. Tokens are managed through the REST API or the dashboard under Settings > Security. See API Tokens for endpoint details.

Auth Guard

All API endpoints are protected except public auth routes (/api/auth/status, /api/auth/setup, /api/auth/login, /api/auth/logout) and the health check (/api/health).

Background cleanup

A background task runs periodically to prune expired sessions from the database.

CORS

Allow cross-origin requests from specific origins:

[server]
cors_allowed_origins = ["https://dashboard.example.com"]

Use ["*"] to allow all origins (not recommended for production).


HTTPS (Web TLS)

Serve the dashboard and REST API over HTTPS. When enabled, the same web_port handles both HTTPS and plain HTTP (with automatic redirect to HTTPS).

ferrous-dns.toml
[server.web_tls]
enabled       = false               # Enable HTTPS
tls_cert_path = "/data/cert.pem"    # PEM certificate path
tls_key_path  = "/data/key.pem"     # PEM private key path
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

Quick setup

You can generate a self-signed certificate directly from the UI under Settings > Security > HTTPS / TLS — no command-line tools needed.

Graceful fallback

If enabled = true but certificate files are missing, the server logs a warning and falls back to plain HTTP.

See HTTPS for Web UI for full details, API endpoints, and UI management.


Pi-hole Compatibility

Ferrous DNS can expose the Pi-hole v6 API at /api/*, making it a drop-in replacement for existing integrations (Gravity Sync, third-party dashboards, scripts):

[server]
pihole_compat = true

When pihole_compat = true:

  • Pi-hole API is served at /api/*
  • Ferrous DNS native API moves to /ferrous/api/*
  • The frontend auto-detects the correct prefix via /ferrous-config.js

PROXY Protocol v2

Enable real client IP detection when running behind a load balancer (HAProxy, AWS NLB, nginx):

[server]
proxy_protocol_enabled = true

Danger

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


Encrypted DNS

Ferrous DNS can serve DNS-over-TLS (DoT), DNS-over-HTTPS (DoH), and DNS-over-QUIC (DoQ) directly to clients. All three require a TLS certificate and private key in PEM format.

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

Missing certificate

If the certificate files are absent at startup, the affected listeners are skipped with a warning. The server continues serving plain DNS normally.

Generating a Self-Signed Certificate

openssl req -x509 -newkey rsa:4096 -nodes \
  -keyout key.pem -out cert.pem \
  -days 365 -subj "/CN=ferrous-dns"

For production, use Let's Encrypt or your CA.

Client Configuration (DoT)

Configure your devices or router to use DoT:

DNS server: 192.168.1.100
Port: 853
Protocol: DNS-over-TLS

Client Configuration (DoH)

Use the DoH endpoint with any compatible client:

https://192.168.1.100/dns-query

See Encrypted DNS features for more details and client setup examples.