Running as a service
systemd, the unprivileged user, file layout and binding to port 53.
The .deb and .rpm packages install the unit, the user and the configuration
for you. This page describes what they set up, which is also what you want if
you are installing from a tarball.
The unit
[Unit]
Description=SDNS - Fast DNS Resolver
ConditionPathExists=/var/lib/sdns
Wants=network.target
After=network.target
[Service]
Type=simple
User=sdns
Group=sdns
LimitNOFILE=131072
Restart=on-failure
RestartSec=10
WorkingDirectory=/var/lib/sdns
ExecStart=/usr/bin/sdns --config=/etc/sdns.conf
AmbientCapabilities=CAP_NET_BIND_SERVICE
StandardOutput=syslog
StandardError=journal
SyslogIdentifier=sdns
[Install]
WantedBy=multi-user.target
Three lines carry most of the weight.
AmbientCapabilities=CAP_NET_BIND_SERVICE is what lets an unprivileged process
bind port 53. Do not run sdns as root to solve this.
LimitNOFILE=131072 matters more than it looks: the TCP/DoT connection cap is
derived at startup partly from the file-descriptor limit, so a low limit
silently gives you a smaller connection cap than the machine could handle.
ConditionPathExists=/var/lib/sdns stops the unit rather than starting a
resolver with nowhere to write.
Files
| Path | Contents |
|---|---|
/usr/bin/sdns |
The binary |
/etc/sdns.conf |
Configuration |
/var/lib/sdns |
Trust anchors, cached blocklists, the local root copy |
/var/lib/sdns must be writable by the sdns user. It holds real state — the
RFC 5011 trust anchor database in particular — so it belongs on persistent
storage, not in a tmpfs.
Operating it
sudo systemctl enable --now sdns
sudo systemctl status sdns
sudo journalctl -u sdns -f
The listener bounds — worker pool, in-flight cap, TCP connection cap — are logged as each listener starts, which is the quickest way to confirm what the process actually derived from the machine.
Restarting safely
sudo -u sdns /usr/bin/sdns -t -c /etc/sdns.conf && sudo systemctl restart sdns
Run the check as the service user. As root it reads files the sdns user
cannot — a TLS key with tight ownership passes the test and then fails at
startup.
Make the validation gate part of the restart, not a thing you remember to run.
It reports every problem in the file at once and exits nonzero on any of them,
so the && is doing real work.
Conflicting resolvers
On most distributions something already holds port 53 — systemd-resolved,
dnsmasq, or an existing recursor. Check before the first start:
sudo ss -lnup 'sport = :53'
For systemd-resolved, the usual approach is to turn off its stub listener
(DNSStubListener=no in /etc/systemd/resolved.conf) rather than disabling the
service, so /etc/resolv.conf handling stays intact.
Debug environment variables
The unit ships both off:
SDNS_PPROF=false # serve /debug/pprof on the API listener
SDNS_DEBUGNS=false # serve the CHAOS-class nameserver debug view
SDNS_PPROF=true exposes Go’s profiling endpoints on the API address, and
bearertoken does not cover them — pprof tooling sends no Authorization
header, so those routes stay open even when a token is set. Leave it off unless
you are actively profiling, and keep the listener on loopback while it is on.
SDNS_DEBUGNS=true answers CHAOS HINFO queries with the delegation sdns holds
for a name, its per-server RTT and its health. Both are covered on the
diagnostics page.