Explicit listeners
Binding named IPv4 and IPv6 addresses avoids accidentally serving on an unintended interface. Wildcard listeners may be preferable on hosts whose addresses change, but require equally deliberate firewall policy.
Authoritative implementation
NSD has a deliberately small authoritative-only role. A secure deployment preserves that narrow scope in its zone configuration, filesystem permissions, AppArmor policy, systemd sandbox and network firewall.
Security model
NSD serves authoritative data and does not perform recursive resolution. Its public network surface is normally UDP and TCP port 53, while administrative control should remain local.
systemd starts NSD as an unprivileged account, grants only the capability needed to bind a privileged port, removes broad kernel-facing features and makes most of the filesystem read-only.
AppArmor adds path-oriented mandatory access control. Even if an NSD process is compromised, the profile can prevent it from reading or modifying files outside the explicitly allowed NSD locations.
RestrictAddressFamilies= permits protocol families, not individual addresses or ports. Packet filtering must still expose only the intended DNS listeners and keep the control interface private.
NSD configuration
This Debian-style example uses documentation-only addresses. Paths and package defaults vary, so validate them against the installed NSD version.
server:
ip-address: 192.0.2.53
ip-address: 2001:db8:53::53
server-count: 2
zonesdir: "/etc/nsd"
xfrdfile: "/var/lib/nsd/xfrd.state"
zonelistfile: "/var/lib/nsd/zone.list"
logfile: ""
log-only-syslog: yes
verbosity: 1
hide-version: yes
hide-identity: yes
minimal-responses: yes
# Warning!
# Necessary and safe when running NSD as user with systemd, but DANGEROUS when NSD is running as root!
# Only set (uncomment) this when you have made sure systemd is running NSD as a user!
#username: ""
remote-control:
control-enable: yes
control-interface: 127.0.0.1
control-interface: ::1
key:
name: "xfr-key.example."
algorithm: hmac-sha256
secret: "BASE64-ENCODED-SECRET"
# This server is primary for example.com.
zone:
name: "example.com"
zonefile: "master/example.com.zone"
notify: 198.51.100.53 xfr-key.example.
provide-xfr: 198.51.100.53 xfr-key.example.
store-ixfr: yes
create-ixfr: yes
# This server is secondary for example.net.
zone:
name: "example.net"
zonefile: "slaves/example.net.zone"
allow-notify: 203.0.113.53 xfr-key.example.
request-xfr: 203.0.113.53 xfr-key.example.Binding named IPv4 and IPv6 addresses avoids accidentally serving on an unintended interface. Wildcard listeners may be preferable on hosts whose addresses change, but require equally deliberate firewall policy.
Primary source zones remain under /etc/nsd/master; transferable state and the dynamic zone list live under /var/lib/nsd; secondary zone files are the only writable content under /etc/nsd/slaves.
nsd-control uses mutually authenticated TLS by default. Keeping its interfaces on loopback avoids publishing an administrative listener. A Unix control socket can reduce the network and certificate surface further.
A primary uses notify and provide-xfr. A secondary separately uses allow-notify and request-xfr. TSIG authenticates those messages but does not encrypt zone contents.
Generate a random TSIG secret, distribute it over a protected channel and restrict its file permissions. Use XFR-over-TLS as well when transfer confidentiality is required.
Primary and secondary files
The daemon should read configuration, keys and primary zones, but only write transfer state and secondary data. That distinction lets both systemd and AppArmor enforce a useful policy.
/etc/nsd/nsd.confRead-only configuration/etc/nsd/master/Read-only primary zones/etc/nsd/slaves/Writable secondary zones/var/lib/nsd/Writable transfer and runtime state/run/nsd/Ephemeral PID or control socket datainstall -d -o root -g nsd -m 0750 /etc/nsd/master
install -d -o nsd -g nsd -m 0700 /etc/nsd/slaves
install -d -o nsd -g nsd -m 0700 /var/lib/nsdnsd-checkconf /etc/nsd/nsd.conf
nsd-checkzone example.com /etc/nsd/master/example.com.zone
systemctl reload nsdAppArmor
The attached profile below is automatically applied whenever the kernel executes /usr/sbin/nsd.
# Last reviewed for an AppArmor 4 policy ABI
abi <abi/4.0>,
include <tunables/global>
/usr/sbin/nsd {
include <abstractions/base>
include <abstractions/nameservice>
include <abstractions/ssl_certs>
include <abstractions/user-tmp>
capability net_bind_service,
/etc/nsd/** r,
/usr/sbin/nsd mr,
owner /etc/nsd/slaves/* rw,
owner /var/lib/nsd/* rw,
}mrAllows NSD to be read and mapped into memory. The profile attaches to the exact executable path.
net_bind_servicePermits binding below port 1024. AppArmor capability permission and the process capability granted by systemd are independent checks.
/etc/nsd/** rAllows recursive read access to NSD configuration, zone files, control certificates and secrets. It is simple, but can be narrowed further when the exact file set is stable.
owner … rwOnly files owned by the NSD user match an owner rule. Existing root-owned secondary or state files can therefore produce denials even when the pathname looks correct.
* covers direct children, while ** also covers deeper paths. Distribution abstractions can change and may grant more than their name suggests. Inspect the resolved profile whenever least privilege matters.
systemd sandbox
The packaged service already supplies Type=notify, restart behaviour and the NSD command line. A local drop-in can tighten the service while remaining separate from package upgrades.
[Unit]
After=network-online.target
# Optional local integration, only when this template exists:
# OnFailure=notify-error@%n
[Service]
# Ask all configured secondary zones to check their primaries after startup.
ExecStartPost=/usr/sbin/nsd-control transfer
# Run the daemon and ExecStartPost helper as the dedicated account.
User=nsd
Group=nsd
NoNewPrivileges=yes
# Make the filesystem read-only, then reopen only required state paths.
ProtectSystem=strict
ProtectHome=yes
PrivateTmp=yes
PrivateDevices=yes
ReadWritePaths=
ReadWritePaths=/var/lib/nsd /etc/nsd/slaves /run/nsd
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectControlGroups=yes
ProtectClock=yes
ProtectHostname=yes
ProtectKernelLogs=yes
RemoveIPC=yes
RestrictNamespaces=yes
RestrictRealtime=yes
RestrictSUIDSGID=yes
LockPersonality=yes
PrivateNetwork=no
RestrictAddressFamilies=AF_INET AF_INET6 AF_NETLINK AF_UNIX
# Reset inherited lists before granting the single required capability.
CapabilityBoundingSet=
AmbientCapabilities=
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_BIND_SERVICE
ProtectProc=invisible
ProcSubset=pid
MemoryDenyWriteExecute=yes
UMask=0077
SystemCallArchitectures=native
SystemCallErrorNumber=EPERM
SystemCallFilter=~@clock @cpu-emulation @debug @module @mount @obsolete @privileged @raw-io @reboot @resources @swapUser=nsd avoids starting a general root process. The ambient CAP_NET_BIND_SERVICE capability is enough for UDP and TCP port 53 without retaining CAP_NET_ADMIN, CAP_NET_RAW, CAP_SETUID or CAP_SYS_CHROOT.
ProtectSystem=strict makes the filesystem read-only inside the service mount namespace. Resetting the inherited ReadWritePaths list prevents a vendor rule that exposes all of /etc/nsd as writable from silently remaining in force.
Namespace, device, kernel, clock, hostname, realtime and executable-memory restrictions remove facilities that an authoritative DNS server normally does not require.
ProtectProc=invisible and ProcSubset=pid reduce what NSD can learn about unrelated processes while preserving the process information needed for its own operation.
The leading ~ makes this a denylist. This is generally more upgrade-tolerant than a tight allowlist, but every syscall not covered by the denied groups remains available.
nsd-control transfer checks secondary zones for newer versions. NSD already performs normal refresh processing; this hook simply requests an immediate check after a successful start.
After=network-online.target only orders NSD after that target when the target is already part of the transaction. Add Wants=network-online.target only when the host genuinely needs the online target and provides a working wait-online service.
Verification workflow
Configuration syntax, zone syntax and runtime loading are separate checks.
nsd-checkconf /etc/nsd/nsd.conf
nsd-checkzone example.com /etc/nsd/master/example.com.zoneParse the profile before restarting the service, then confirm it is in enforce mode.
apparmor_parser -r /etc/apparmor.d/usr.sbin.nsd
aa-status | grep -F /usr/sbin/nsdsystemctl cat reveals inherited vendor settings as well as the drop-in.
systemctl daemon-reload
systemd-analyze verify nsd.service
systemctl cat nsd.service
systemd-analyze security nsd.serviceLook at both service logs and kernel AppArmor denials.
systemctl restart nsd
systemctl --no-pager --full status nsd
journalctl -u nsd -b
journalctl -k -b | grep 'apparmor="DENIED"'Query both transports and verify that the answer is authoritative and recursion is unavailable.
dig @192.0.2.53 example.com SOA +norecurse
dig @192.0.2.53 example.com SOA +norecurse +tcp
dig @192.0.2.53 unrelated.example A +recurseFor secondary zones, confirm the loaded serial and force a check when diagnosing.
nsd-control status
nsd-control zonestatus example.net
nsd-control transfer example.netBoundaries and caveats
nsd-control is a separate executable.The AppArmor profile attached to /usr/sbin/nsd does not automatically confine a helper that systemd directly executes as /usr/sbin/nsd-control. The helper still inherits the unit’s systemd sandbox. Add a separate AppArmor profile if path-level confinement of the helper is required.
Starting NSD as User=nsd means both server and control credentials need carefully chosen ownership and mode bits. Do not solve a permission failure by making private keys world-readable.
/etc/nsd/** r includes TSIG secrets and control private keys. That is acceptable only because it applies to the confined NSD process; a more mature policy can name individual directories and files.
owner.If a deployment tool creates files as root inside /etc/nsd/slaves, the path matches but the AppArmor owner condition does not. Align the deployment workflow with the policy.
Library, resolver, logging or NSD upgrades may introduce a previously unused syscall. Test upgrades before broad deployment and treat an EPERM in the journal as a possible sandbox denial.
AppArmor and systemd constrain the daemon. DNSSEC authenticates published RRsets; TSIG authenticates transfers and control transactions; TLS can add transfer confidentiality. These controls are complementary.