Security Features¶
Ferrous DNS includes several security mechanisms to protect your network from DNS-based attacks and data exposure.
Authentication¶
Ferrous DNS provides session-based authentication to protect the dashboard and REST API.
First-Run Setup¶
On first launch (when no password is configured), Ferrous DNS shows a setup wizard. Set the admin password via the web UI or CLI before the server accepts API requests.
Session-Based Login¶
Users authenticate with username and password via the login page. On success, a session cookie (ferrous_session) is issued.
POST /api/auth/login
Content-Type: application/json
{
"username": "admin",
"password": "your-password"
}
| Option | Description |
|---|---|
| Remember Me | Extends session lifetime from session_ttl_hours (default 24h) to remember_me_days (default 30 days) |
| Rate Limiting | After login_rate_limit_attempts failed attempts (default 5), login is locked for login_rate_limit_window_secs (default 900s / 15 min) |
Auth Guard¶
All API endpoints are protected by the auth guard middleware, except:
GET /api/auth/status— check if auth is enabledPOST /api/auth/setup— first-run password setupPOST /api/auth/login— loginPOST /api/auth/logout— logoutGET /api/health— health check
Session Management¶
View and revoke active sessions from Settings > Security or via the API:
Password Change¶
Change the admin password from Settings > Security or via:
POST /api/auth/change-password
Content-Type: application/json
{
"current_password": "old-password",
"new_password": "new-password"
}
Background Cleanup¶
A background task runs periodically to prune expired sessions from the database.
API Tokens¶
Named API tokens provide programmatic access to the Ferrous DNS API without requiring a session login. Tokens are ideal for automation scripts, monitoring integrations, and third-party tools.
Token Authentication¶
Include the token in the X-Api-Key header:
API tokens and session cookies are both valid authentication methods. The auth guard accepts either.
Token Management¶
GET /api/api-tokens # List all tokens (only prefix shown)
POST /api/api-tokens # Create a new token
PUT /api/api-tokens/{id} # Update token name or key
DELETE /api/api-tokens/{id} # Delete a token
Token storage
Tokens are stored as SHA-256 hashes. The full token is only returned once at creation time — save it immediately.
Import Custom Keys¶
You can import existing API keys (e.g., from a Pi-hole migration) via PUT /api/api-tokens/{id} with a custom key value.
Auth Configuration¶
[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 or CLI)
| Field | 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 when "Remember Me" is checked |
login_rate_limit_attempts | int | 5 | Max failed login attempts before lockout |
login_rate_limit_window_secs | int | 900 | Duration of lockout window in seconds |
username | str | admin | Admin username |
password_hash | str | "" | Argon2id password hash (set via setup wizard or CLI) |
Setting the password hash
Use the setup wizard on first run to set the password interactively. The Argon2id hash is written to the config file automatically.
See Auth Configuration for full details.
DNSSEC Validation¶
DNSSEC (DNS Security Extensions) validates that DNS responses are authentic and have not been tampered with in transit.
When enabled, Ferrous DNS validates DNSSEC signatures on all upstream responses. Queries that fail validation return SERVFAIL, preventing forged responses from reaching clients.
Standards: RFC 4035
Performance impact
DNSSEC validation adds a small overhead on cache misses (signature verification). Cache hits have zero DNSSEC overhead. Disable with dnssec_enabled = false only for maximum-throughput benchmarking.
Malware Detection¶
Ferrous DNS includes built-in DNS tunneling detection, DNS rebinding protection, and NXDomain hijack detection. See the dedicated Malware Detection page for full details, real-world attack examples, configuration reference, and comparison with other DNS servers.
PTR Block for Private Ranges¶
Prevent information leakage via reverse DNS lookups on private IP ranges:
When enabled, PTR queries for RFC-1918 addresses that are not in the local records are blocked. This prevents external DNS leakage of your internal network topology.
Non-FQDN Query Blocking¶
Block DNS queries for names that are not fully qualified domain names (FQDNs):
Non-FQDN queries (e.g. myserver without a domain suffix) can expose internal network information when forwarded to external resolvers. Blocking them keeps internal names local.
PROXY Protocol v2¶
When Ferrous DNS is deployed behind a load balancer, PROXY Protocol v2 restores accurate client IPs for logging, client detection, and per-group policies:
Supported load balancers: HAProxy, AWS NLB, nginx (stream module), Traefik
Danger
Only enable when a trusted load balancer always injects the PROXY header. Without a load balancer in front, all TCP connections will fail.
HTTPS for Web UI¶
Ferrous DNS can serve the dashboard and REST API over HTTPS, encrypting all traffic between your browser and the server.
How It Works¶
When HTTPS is enabled, the web server uses a single port (default 8080) that automatically detects the protocol:
- TLS connections (browsers accessing
https://) are served normally over HTTPS - Plain HTTP connections receive a
301 Moved Permanentlyredirect tohttps://
This means you never need to configure separate HTTP and HTTPS ports.
Configuration¶
[server.web_tls]
enabled = false # Enable HTTPS for the dashboard and API
tls_cert_path = "/data/cert.pem" # Path to PEM certificate
tls_key_path = "/data/key.pem" # Path to PEM private key
| 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 |
Graceful fallback
If enabled = true but the certificate files are missing at startup, the server logs a warning and falls back to plain HTTP.
Managing Certificates via the UI¶
Navigate to Settings > Security > HTTPS / TLS to:
- Enable/disable HTTPS with a toggle
- View certificate status — subject, expiration date, and validity
- Upload certificates — drag and drop PEM certificate and key files
- Generate a self-signed certificate — instant HTTPS with one click (browsers will show a security warning, but the connection is encrypted)
Quick setup
Click Generate Self-Signed Certificate for immediate HTTPS without needing external certificates. For production, use Let's Encrypt or your CA.
TLS API Endpoints¶
GET /api/tls/status # Certificate status (exists, valid, subject, expiration)
POST /api/tls/upload # Upload cert + key (multipart/form-data)
POST /api/tls/generate?force=true # Generate self-signed certificate
Restart required
Changing HTTPS settings requires a server restart to take effect. The UI shows a "Restart Required" banner after saving.
Encrypted DNS Transports¶
Encrypting DNS traffic prevents:
- ISP surveillance — your DNS queries are not visible to your ISP
- Man-in-the-middle attacks — responses cannot be forged in transit
- DNS poisoning — combined with DNSSEC for end-to-end verification
See Encrypted DNS for setup.
DNS Rate Limiting¶
Ferrous DNS includes a token-bucket rate limiter that throttles abusive clients per subnet, protecting the server from query floods without affecting legitimate traffic.
How It Works¶
Each client subnet (default /24 for IPv4, /48 for IPv6) gets an independent token bucket. Tokens refill at the configured queries_per_second rate, up to the burst_size capacity. When a subnet exhausts its tokens, queries are either refused (REFUSED response code) or slipped (TC=1 truncated response forcing a TCP retry).
Configuration¶
[dns.rate_limit]
enabled = true
queries_per_second = 1000 # sustained QPS per subnet
burst_size = 500 # token bucket capacity
ipv4_prefix_len = 24 # /24 groups the home LAN
ipv6_prefix_len = 48 # /48 standard home delegation
whitelist = ["127.0.0.0/8", "::1/128", "10.0.0.0/8"]
nxdomain_per_second = 50 # separate stricter budget for NXDOMAIN
slip_ratio = 2 # every 2nd rate-limited response is TC=1
dry_run = false # true = log only, don't refuse
stale_entry_ttl_secs = 300 # evict idle subnet buckets after 5 min
| Option | Default | Description |
|---|---|---|
enabled | false | Master switch for rate limiting |
queries_per_second | 1000 | Sustained token refill rate per subnet |
burst_size | 500 | Maximum tokens (allows short bursts above QPS) |
ipv4_prefix_len | 24 | IPv4 subnet grouping prefix length |
ipv6_prefix_len | 48 | IPv6 subnet grouping prefix length |
whitelist | [] | CIDRs that bypass rate limiting entirely |
nxdomain_per_second | 50 | Separate, stricter budget for NXDOMAIN responses |
slip_ratio | 0 | Every Nth rate-limited response sends TC=1 instead of REFUSED. 0 = disabled |
dry_run | false | Log rate-limit events without refusing queries |
stale_entry_ttl_secs | 300 | Seconds before an idle subnet bucket is evicted |
TC=1 Slip Mechanism¶
When slip_ratio is set (e.g. 2), every Nth rate-limited UDP response is sent as a truncated response (TC=1 flag set) instead of REFUSED. This forces the client to retry over TCP, which:
- Verifies the client is a legitimate resolver (not a spoofed-source flood)
- Allows real clients to still get answers via TCP even when rate-limited on UDP
- Follows the same approach used by NSD and BIND
NXDOMAIN Budget¶
The nxdomain_per_second setting provides a separate, stricter budget for NXDOMAIN responses. This catches malware and IoT devices that probe many random subdomains while leaving the general query budget unaffected.
Dry-Run Mode¶
Set dry_run = true to log rate-limit events without actually refusing queries. This is useful for calibrating thresholds before enforcing limits in production. Rate-limited queries appear in the query log with status RATE_LIMITED and in the dashboard stats.
Recommended first deployment
Enable rate limiting with dry_run = true for 24-48 hours. Check the dashboard for false positives, then switch to dry_run = false once thresholds are validated.
TCP/DoT Connection Limiting¶
Per-IP connection limits protect against TCP and DoT connection exhaustion:
[dns.rate_limit]
tcp_max_connections_per_ip = 30 # max concurrent TCP DNS connections per IP
dot_max_connections_per_ip = 15 # max concurrent DoT connections per IP
| Option | Default | Description |
|---|---|---|
tcp_max_connections_per_ip | 30 | Max concurrent TCP connections per IP. 0 = unlimited |
dot_max_connections_per_ip | 15 | Max concurrent DoT connections per IP. 0 = unlimited |
Connections that exceed the limit are immediately closed. The connection counter is automatically decremented when a connection closes, preventing resource leaks.
Upcoming Security Features¶
The following are planned for future releases:
| Feature | Description |
|---|---|
| TOTP / 2FA | Time-based one-time passwords for login |
| Read-Only Mode | Disable config changes via a flag |
Current Security Posture¶
| Mechanism | Status |
|---|---|
| DNSSEC validation | |
| DNS tunneling detection | |
| DNS rebinding protection | |
| NXDomain hijack detection | |
| Encrypted upstream (DoH/DoT/DoQ) | |
| Server-side DoT/DoH | |
| PROXY Protocol v2 | |
| Dashboard authentication | |
| API token authentication | |
| HTTPS dashboard | |
| DNS rate limiting | |
| TCP/DoT connection limiting | |
| TOTP / 2FA | Planned |