Deploying with systemd
The bench run from the quickstart used sudo. Production should not: Atlas ships a hardened unit that runs the daemon as an unprivileged user holding exactly one capability, inside a sandbox tight enough that verifying it is part of this guide. A static binary, a config file and this unit are a complete deployment.
1 — Install the binary and config
sudo install -m 0755 atlasd /usr/local/bin/atlasd
sudo mkdir -p /etc/atlas
sudo install -m 0600 node-a.toml /etc/atlas/config.toml
atlasd config check # validates /etc/atlas/config.toml
The config directory is canonical at /etc/atlas/; installations migrating from the earlier product name keep working through a legacy-path fallback, with a one-time log suggestion to move. Mode 0600 on the config matters — it contains the node's private key, and the daemon warns about anything looser.
2 — The service unit
Install the packaged unit rather than writing one — its hardening is the product of real deployments:
sudo install -m 0644 nexus-atlasd.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now nexus-atlasd
What the unit gives you:
- An unprivileged service user (
nexus-atlas) withCAP_NET_ADMINgranted via ambient capabilities — enough to create the TUN device and program routes, and nothing else. No root process anywhere. - A real sandbox: read-only system, no new privileges, a reduced capability bounding set, a syscall allowlist, restricted address families, and device access limited to
/dev/net/tun. Writable paths are exactly the product's own:/etc/atlas(the commit-confirmed apply lifecycle writes staged configs),/var/lib/atlas,/var/log/atlas. - Startup that means something:
Type=notify—systemctl startreturns only when the TUN exists and every link is serviced, so "started" in your orchestration equals "data path up", not "process exec'd". - A watchdog fed honestly:
WatchdogSec=30s, fed from data-path liveness counters — a wedged data path stops feeding it and systemd restarts the service; a busy-but-healthy daemon never trips it. - Crash accounting on the way down: the unit records exit status into the local crash spool, so an unexpected exit is a diagnosable artifact rather than a mystery.
3 — The dashboard as its own service
The web UI runs as a separate templated unit per interface (atlasd web --interface nexus0 --listen 0.0.0.0:9800), serving static assets from /opt/atlas/web. Keep it separate deliberately: the data-path daemon and the HTTP server restart independently, and a host that should be headless simply doesn't run the second unit. Both units share the snapshot handoff in /tmp/atlas/, so private temp directories must stay off for the pair.
:9800 beyond localhost: create dashboard accounts (atlasd web adduser), decide the protection posture (protect = "writes" or "all"), and consider tls = true. An accountless dashboard is read-open and write-blocked — right for a bench, wrong for a fleet.4 — Verify the sandbox
systemctl status nexus-atlasd # active (running), Type=notify ready
ping -c 3 10.0.100.2 # the tunnel works
ps -o user= -p $(pidof atlasd) # nexus-atlas — not root
systemd-analyze security nexus-atlasd # sandbox scorecard
The last command grades the unit's confinement; the packaged unit scores in the range that makes accreditation reviewers relax. If you tighten it further and the daemon breaks, loosen one directive at a time — every directive in the shipped unit is there because something needed it.
5 — Mandatory access control (optional, recommended on hardened hosts)
Packaging includes both flavors: an SELinux policy module (build and install with the provided Makefile on RHEL-family hosts) and an AppArmor profile (apparmor_parser -r on Debian-family hosts). Each confines the daemon to its documented paths and sockets. Deep-dive material on the confinement design — directive-by-directive rationale, policy internals, verification on a live system — is part of the OS hardening profile available under approved access.
6 — Fleets: Ansible
An Ansible role drives the same artifacts across many hosts — binary delivery with mandatory checksum, config templating, unit installation, and optionally the MAC policy — and can layer onto a STIG-baseline host. Start with one hand-installed node so you know what correct looks like; then let the role make it boring.
7 — Changing config on a running fleet
Edit-and-restart works, but the daemon offers better: the commit-confirmed apply lifecycle. Stage a config, commit it, and confirm within the window — a change that severs your own path to the node reverts itself. Pushing a config to the far end of a link you are about to reconfigure stops being an act of courage.