runbound 0.3.4

RFC-compliant DNS resolver — drop-in Unbound with REST API, ACME auto-TLS, HMAC audit log, and master/slave HA
[Unit]
Description=Runbound DNS Server
Documentation=https://github.com/runbound/runbound
After=network-online.target
Wants=network-online.target
# Restart up to 5 times within 5 minutes before giving up
StartLimitIntervalSec=300
StartLimitBurst=5

[Service]
Type=simple
ExecStart=/usr/local/sbin/runbound /etc/runbound/unbound.conf
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartSec=5s

# User / group created by install.sh (useradd -r runbound)
User=runbound
Group=runbound

# API key persistence — set here to survive restarts without regeneration.
# Leave commented to auto-generate on first start (saved to /etc/runbound/api.key).
# EnvironmentFile=-/etc/runbound/environment
Environment="RUST_LOG=runbound=info"

WorkingDirectory=/var/lib/runbound

# ── Systemd hardening (defence-in-depth) ────────────────────────────────────

# No privilege escalation from runbound user
NoNewPrivileges=true

# Private /tmp and /dev — the server has no reason to touch shared temp files
PrivateTmp=true
PrivateDevices=true

# Filesystem: read-only except for the two directories we actually need
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/etc/runbound /var/lib/runbound

# Kernel hardening
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectKernelLogs=true
ProtectControlGroups=true
ProtectClock=true
ProtectHostname=true

# Namespace isolation
RestrictNamespaces=true
LockPersonality=true

# Syscall filter — DNS server only needs inet sockets and file I/O
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
SystemCallFilter=@system-service
SystemCallErrorNumber=EPERM

# No memory regions executable + writable simultaneously
MemoryDenyWriteExecute=true

# No realtime scheduling (unnecessary, prevents priority inversion attacks)
RestrictRealtime=true
RestrictSUIDSGID=true

# Clean up IPC objects on exit
RemoveIPC=true

# ── Capabilities ─────────────────────────────────────────────────────────────
# CAP_NET_BIND_SERVICE: bind to port 53 (< 1024) as non-root
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_BIND_SERVICE

# ── Resource limits ──────────────────────────────────────────────────────────
# 512 MB hard cap — the memory guard already purges caches at 80 % system RAM,
# but this systemd limit is a belt-and-suspenders last line of defence.
MemoryMax=512M
# File descriptors: 32 SO_REUSEPORT UDP sockets + TCP + DoT + feeds HTTP
LimitNOFILE=65536
# Tokio thread pool + hickory tasks
TasksMax=4096

[Install]
WantedBy=multi-user.target