sdns.
A recursive DNS resolver with DNSSEC validation, written in Go. It resolves from the root, serves DoT, DoH and DoQ alongside plain UDP/TCP, and answers warm cache hits from the bytes it already holds.
$ dig @127.0.0.1 example.com A +dnssec +multiline ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 41022 ;; flags: qr rd ra ad; QUERY: 1, ANSWER: 2 ;; ANSWER SECTION: example.com. 3600 IN A 93.184.215.14 example.com. 3600 IN RRSIG A 13 2 3600 (...) ;; Query time: 0 msec ;; SERVER: 127.0.0.1#53(127.0.0.1)
Throughput and cost, with the conditions attached
Every figure below was produced by a documented run on one host, against the same corpus, with the methodology and the fairness notes written down. Read how they were taken before you trust them.
sdns 1.8.0 at 8b36b91, 2×Xeon E5-2620 v4 (32 logical cores).
The cold figure is the configuration sdns ships, with QNAME minimisation on;
turning it off measures 905/s and gives up privacy the resolver is meant to keep.
Methodology, tuning of each contender, and the raw tables →
Same host, same corpus, same day
Each contender was given a reasonable configuration, not an exhaustive tuning pass. PowerDNS answers these from its packet cache — its lightest path, as this is ours.
| Resolver | UDP median | TCP median |
|---|---|---|
| sdns 1.8.0 | 424k | 226k |
| PowerDNS Recursor 5.4.1 | 371k | 56k |
| Unbound 1.24.2 | 343k | 136k |
| Knot Resolver 6.2.0 | 191k | 142k |
What is implemented
Standards the resolver implements, not a list of things it is aware of.
A working configuration is short
sdns writes a complete, commented configuration on first run. Most deployments change a handful of its lines and leave the rest — these three are the ones worth reading first.
# three lines of the generated file
bind = ":53"
dnssec = "on"
# The default allows everyone. Narrow it
# before binding to a reachable address.
accesslist = ["192.168.0.0/16"]
- Everything new ships off or in shadow. Enabling a feature is a decision you make, never one made for you by an upgrade.
sdns -tjudges the whole file and reports every problem at once — addresses, CIDRs, enumerations, TLS files, policy zones — with the same loaders the server runs.- A key no setting claims fails the test and only warns at startup, so a stale key from an older version cannot become an outage.
Current state
Beyond resolving
Response Policy Zones
Name, client-address and answer-address triggers; file and TSIG-signed AXFR feeds; a shadow mode whose counters predict what enforcement would do.
Local root zone
The root served from a ZONEMD-verified local copy, refreshed on its own SOA schedule. Referrals and junk-TLD denials cost no upstream query.
Recursion firewall
Per-request-tree budgets on outbound queries, internal work and DNSSEC operations, with shadow and enforce modes.
Serve stale
An expired answer as a last resort when resolution fails, always bounded by the delegation lease the parent granted.
Views and forwarding
Static answers scoped to client networks, and per-zone conditional forwarding to their own upstreams.
Middleware and plugins
The query path is a chain of middlewares; external plugins load into it without forking the server.
64 metrics
Cache, resolution, DNSSEC work, ingress, policy — each with its labels documented, and the PromQL to answer the questions operators actually ask.