sdns: a recursive DNSSEC-validating resolver written in Go
A high-performance, recursive DNS resolver server with DNSSEC support, focused on preserving privacy.
At a glance
- What is it?
- sdns is a recursive DNS resolver that validates every answer against the DNSSEC trust anchors, caches what it has, and serves plain UDP and TCP alongside DoT, DoH over HTTP/3 and DoQ. It suits an operator who wants validation and privacy without giving up a recursive resolver of their own, and the configuration file is generated rather than hand-written.
- Who is it for?
- Adopt sdns if you want to run a recursive resolver that validates DNSSEC itself and serves encrypted transports, since it covers UDP, TCP, DoT, DoH with HTTP/3 and DoQ from one binary. Skip it if you want a caching forwarder only, because the whole design is recursion from the root, and read the accesslist setting before it serves a network, since the generated configuration listens on port 53 on every interface and lets every client query.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 1 day ago.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What the resolver does, and the shape of it
sdns is a recursive DNS resolver with DNSSEC validation, written in Go, and the one-sentence summary of its behaviour is precise. It resolves from the root, validates answers against the DNSSEC trust anchors, and caches them. It serves DNS over TLS, HTTPS and QUIC alongside plain UDP and TCP, and answers warm cache hits from the bytes it already holds. The project description calls it a high-performance recursive DNS resolver server with DNSSEC support, focused on preserving privacy. The primary language is Go, the licence is MIT, the homepage is sdns.dev, the last push was on 2026-10-15, and the releases are v1.9.0 on 2026-10-02, v1.8.3 on 2026-09-24 and v1.8.2 on 2026-09-01, so the project is on a 1.x line and shipping steadily. The topics list names the transport set, with dns-over-https, dns-over-quic and dns-over-tls all present alongside dnssec and edns.
The resolution features, and the RFCs they come from
The resolution section is where the depth is, and each item names the specification it implements. Recursion is from the root with DNSSEC validation, QNAME minimisation per RFC 9156, aggressive NSEC use per RFC 8198, NXDOMAIN subtree cuts per RFC 8020, failure caching per RFC 9520, and Extended DNS Errors per RFC 8914. There is a cache detail that matters in practice, since delegations and nameserver addresses still in use are renewed in the last tenth of their lease, so the cache does not fall off a cliff when a busy zone's delegation expires. Optionally the root zone is served from a ZONEMD-verified local copy per RFC 8806, or expired answers are served per RFC 8767, either when resolution fails or, with `serve_stale_mode = "immediate"`, at once while a background refresh runs. The second of those is a meaningful operational choice, since serving stale answers during an outage is a decision rather than a fallback.
Post-quantum DNSSEC and the TLS side
The post-quantum support is specific rather than a slogan. DNSSEC validation of ML-DSA-44 signatures is supported, which is algorithm 18. Every TLS transport, served and upstream, offers the X25519MLKEM768 hybrid key exchange that Go's TLS stack enables by default, and sdns does not restrict the curve list. The second half of that is the important part for an operator, because a resolver that pinned a classical curve list would silently opt out of the hybrid exchange it appears to support. The go.mod shows the TLS work is inherited from the standard library rather than from a bundled library, with `golang.org/x/crypto` present alongside `quic-go` for the QUIC transport and `miekg/dns` for the protocol itself.
The transports and their details
The transport set is UDP, TCP, DoT per RFC 7858, DoH with HTTP/3 per RFC 8484, and DoQ per RFC 9250, and each listener takes one address or a list. Discovery of Designated Resolvers per RFC 9462 under a `[ddr]` section lets plain DNS clients find the encrypted listeners, which is the piece that makes the encryption usable by a device that does not know about it. DoT upstreams can be authenticated by name in the `tls://ip:port#name` form per RFC 8310. DNS cookies per RFC 7873 and RFC 9018 come with a `cookiesecret` that can be shared across an anycast set, and a padded query over an encrypted transport gets a padded reply per RFC 8467, so the padding does not leak over the encrypted path. Warm wire-eligible cache hits are served allocation-free, with batched `recvmmsg` and `sendmmsg` on Linux.
Policy, other namespaces and the operational surface
The policy section is substantial. Response Policy Zones have name, client-address and answer-address triggers, with file and TSIG-signed AXFR feeds, and there is a shadow mode whose counters predict what enforcement would do before you turn it on. There are also blocklists, per-client views, access lists, rate limits, and reflection-attack detection. The other namespaces section covers per-zone conditional forwarding, whole-server forwarder mode, Kubernetes cluster DNS, DNS64 synthesis per RFC 6147, EDNS Client Subnet per RFC 7871, and locally served zones per RFC 6303. The operations section has Prometheus metrics, an HTTP API, dnstap, a recursion firewall that bounds the work one request may cause, serving bounds derived from the machine at startup, and a validation gate that reports every configuration problem at once. The go.mod includes the Kubernetes client libraries, which is what backs the cluster DNS mode.
Installing it, and the access list warning
The install is a Go command that needs Go 1.27 or newer:
go install github.com/semihalev/sdns@latestPre-built binaries for Linux, macOS, Windows and the BSDs, plus `.deb` and `.rpm` packages for Linux amd64, are on the releases page, and there are Docker images at ghcr.io and on Docker Hub, along with Homebrew, Snap and an Arch package. The images carry a warning that is worth reading. `latest` is built from every push to the main branch, not from a release, so you should pin a release tag in production, and every tagged release publishes both its full version such as `1.9.0` and its minor series such as `1.9`. The quick start then shows the configuration being generated and checked with `sdns -t -c sdns.conf`, and the note that the generated configuration listens on port 53 on every interface and lets every client query, so you should try it on a loopback high port first and set `accesslist` before it serves a network.
Editorial conclusion
Adopt sdns if you want to run a recursive resolver that validates DNSSEC itself and serves encrypted transports, since it covers UDP, TCP, DoT, DoH with HTTP/3 and DoQ from one binary. Skip it if you want a caching forwarder only, because the whole design is recursion from the root, and read the accesslist setting before it serves a network, since the generated configuration listens on port 53 on every interface and lets every client query. Before you deploy, pin a release tag rather than `latest`, since the latest image is built from every push to main and not from a release.
Frequently asked questions
What does sdns do?
It resolves from the root, validates answers against the DNSSEC trust anchors, and caches them. It serves UDP, TCP, DoT, DoH with HTTP/3 and DoQ, answering warm cache hits from the bytes it already holds.
How do I install sdns?
With `go install github.com/semihalev/sdns@latest`, which needs Go 1.27 or newer. Pre-built binaries for Linux, macOS, Windows and the BSDs, plus .deb and .rpm packages, are on the releases page, and there are Docker images, Homebrew, Snap and Arch packages.
Why should I pin a sdns image tag in production?
Because the latest image is built from every push to the main branch rather than from a release. Every tagged release publishes both its full version such as 1.9.0 and its minor series such as 1.9, so you can pin either.
Does sdns support post-quantum cryptography?
Yes. It validates DNSSEC ML-DSA-44 signatures, which is algorithm 18, and every TLS transport, served and upstream, offers the X25519MLKEM768 hybrid key exchange that Go's TLS stack enables by default, since sdns does not restrict the curve list.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/semihalev-sdns)Was this useful?