> Source: https://nexusatlas.dev/guides/systemd — Nexus Atlas developer documentation (Guides). Converted from the HTML page; the page is canonical.

# Deploying with systemd

The bench run from the [quickstart](https://nexusatlas.dev/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`) with `CAP_NET_ADMIN` granted 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 start` returns 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.

**Before exposing `: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](https://nexusatlas.dev/security/hardening) 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](https://nexusatlas.dev/docs/api#write-endpoints). 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.
