systemd unit
A drop-in systemd unit for running RunWisp headless on a Linux box. Pair it with the VPS deploy guide for the full bring-up flow (create the user, install the binary, set up the data dir, then this unit).
The unit
Section titled “The unit”[Unit]Description=RunWisp daemonDocumentation=https://docs.runwisp.comAfter=network-online.targetWants=network-online.target
[Service]Type=simpleUser=runwispGroup=runwisp
# Optional environment file for RUNWISP_PASSWORD, RUNWISP_TRUST_PROXY,# RUNWISP_FINGERPRINT, etc. The leading `-` makes it optional —# systemd doesn't fail if the file is missing.EnvironmentFile=-/etc/runwisp/runwisp.env
ExecStart=/usr/local/bin/runwisp daemon \ --config /etc/runwisp/runwisp.toml \ --data /var/lib/runwisp \ --host 127.0.0.1 \ --port 9477
# Restart policyRestart=on-failureRestartSec=2s
# Graceful shutdown. The daemon's own deadline is 3s (see# /operations/upgrading/#what-doesnt). TimeoutStopSec=10s gives systemd# 7s of headroom over that so a slow drain still finishes cleanly before# systemd escalates to SIGKILL.KillSignal=SIGTERMTimeoutStopSec=10s
# HardeningNoNewPrivileges=trueProtectSystem=strictProtectHome=truePrivateTmp=truePrivateDevices=trueProtectKernelTunables=trueProtectKernelModules=trueProtectControlGroups=trueRestrictSUIDSGID=trueLockPersonality=trueRestrictRealtime=true
# The daemon needs to write to its data directory and read its config.# `ReadWritePaths` re-grants writes inside the strict ProtectSystem.ReadWritePaths=/var/lib/runwispReadOnlyPaths=/etc/runwisp
[Install]WantedBy=multi-user.targetSave it, then:
sudo systemctl daemon-reloadsudo systemctl enable --now runwispsudo systemctl status runwispWhat the directives do
Section titled “What the directives do”A short justification for each non-obvious directive — feel free to strip the ones you don’t want.
| Directive | Effect |
|---|---|
User= / Group= | Run as a dedicated, shell-less system user. Tasks in runwisp.toml execute under this account. |
EnvironmentFile=-… | Optional file with RUNWISP_PASSWORD=… etc. Leading - = “no error if missing”. |
Restart=on-failure | Restart only when the daemon exits non-zero. A clean systemctl stop doesn’t trigger restart. |
TimeoutStopSec=10s | systemd waits 10s after SIGTERM before sending SIGKILL. The daemon’s own shutdown deadline is 3 seconds (defined in upgrading); the extra 7s of headroom is for systemd’s own bookkeeping. |
NoNewPrivileges=true | The daemon (and the tasks it spawns) cannot gain privileges via setuid binaries. |
ProtectSystem=strict | The whole filesystem is read-only by default — only /dev, /proc, /sys, and the ReadWritePaths exception are writable. |
ProtectHome=true | /home, /root, /run/user are inaccessible. Your tasks can’t accidentally read someone’s ~/.ssh. |
PrivateTmp=true | Per-service /tmp. Avoids collisions with anything else on the host. |
ReadWritePaths=… | The data dir is writable. |
ReadOnlyPaths=… | The config dir is read-only. RunWisp never mutates runwisp.toml, so this also enforces that. |
If any of your tasks need to write outside /var/lib/runwisp, add
those paths to ReadWritePaths. Otherwise the writes will silently
fail with EROFS.
Env file template
Section titled “Env file template”# Permissions: chmod 0640, chown root:runwisp
# The daemon's login password. If unset, RunWisp generates one and# writes it to /var/lib/runwisp/password on first boot.RUNWISP_PASSWORD=<a long random string>
# Trust an upstream reverse proxy on loopback (Caddy, nginx).# Required for the JWT cookie to carry the Secure flag over HTTPS.RUNWISP_TRUST_PROXY=127.0.0.1/32,::1/128sudo install -m 0640 -o root -g runwisp /dev/stdin /etc/runwisp/runwisp.env <<'EOF'RUNWISP_PASSWORD=<paste-or-generate>EOFReload behaviour
Section titled “Reload behaviour”There is no ExecReload directive in the unit above because RunWisp
does not currently support live reload of runwisp.toml. To pick
up config changes, restart:
sudo systemctl restart runwispRestart cancels in-flight runs. They’re marked stopped if they exit
within the daemon’s 3-second graceful-shutdown window
or crashed on the next boot if they don’t — either way no run history
is lost. See Operations: upgrading for the full picture; live
reload is on the roadmap.
The daemon writes its diagnostic logs to stderr; systemd captures them via the journal:
sudo journalctl -u runwisp -n 100 -f # live tailsudo journalctl -u runwisp --since '1 hour ago'sudo journalctl -u runwisp -p warning # warnings and abovePer-task output is captured to /var/lib/runwisp/logs/<task>/ and is
not in the journal — see Operations: data directory.
Health check
Section titled “Health check”systemd doesn’t watch RunWisp’s /health endpoint by default — but
you can wire it in if you’d like systemd to react to liveness:
[Service]ExecStartPost=/bin/bash -c 'until /usr/local/bin/runwisp status; do sleep 1; done'That makes systemctl start runwisp block until the daemon is
healthy, which makes downstream Requires=runwisp.service / After=
units behave correctly.
Where to next
Section titled “Where to next”- Deploy on a VPS — the end-to-end install, proxy, and this unit, glued together.
- Operations: troubleshooting — what
the daemon’s exit codes mean when systemd marks it
failed. - CLI reference — every flag the
ExecStartline accepts.