Run the agent as a service
For real monitoring you want the agent running continuously and restarting on boot. On Linux, the simplest way is a systemd service.
Quick setup (recommended)
One command does everything below — it creates the unprivileged demerzel user, sets up the identity, and installs, enables and starts the service:
curl -fsSL https://get.demerzel.cloud/service.sh | sudo sh
The script is careful about the identity so you never enroll twice:
-
if you already enrolled as your own user (the common case — you ran
demerzel-agent --enroll …and it stored things under~/.config/demerzel), it migrates that identity to the service user. No token needed. -
if nothing is enrolled yet, pass your enrollment token and it enrolls for you:
curl -fsSL https://get.demerzel.cloud/service.sh | sudo sh -s <your-token>
It’s idempotent — safe to re-run. To change the collection interval, set DEMERZEL_INTERVAL (default 1m):
curl -fsSL https://get.demerzel.cloud/service.sh | sudo DEMERZEL_INTERVAL=30s sh
To also have the agent keep itself up to date, set DEMERZEL_AUTO_UPDATE=1 — the installer bakes --auto-update into the service (off by default, see Update the agent for why):
curl -fsSL https://get.demerzel.cloud/service.sh | sudo DEMERZEL_AUTO_UPDATE=1 sh
Once it finishes, follow the logs with journalctl -u demerzel-agent -f, then confirm the data is arriving in Verify & troubleshoot.
Or set it up by hand
If you’d rather do each step yourself, here’s exactly what the script does.
Create a system user
Run the agent as an unprivileged user that owns only its config directory:
sudo useradd --system --home /etc/demerzel --shell /usr/sbin/nologin demerzel
sudo mkdir -p /etc/demerzel
sudo chown demerzel:demerzel /etc/demerzel
sudo chmod 700 /etc/demerzel
Give it an identity
If you already enrolled as your own user, move that identity over instead of enrolling again:
sudo cp -a ~/.config/demerzel/. /etc/demerzel/
sudo chown -R demerzel:demerzel /etc/demerzel
Otherwise, enroll now — the files land owned by root, so hand them to the service user afterwards:
sudo demerzel-agent --config-dir /etc/demerzel --enroll <your-token>
sudo chown -R demerzel:demerzel /etc/demerzel
Give the binary a home the service owns
The service user must be able to replace its own binary for --update (and auto-update, if you opt in) to work, and nobody but root can write /usr/local/bin. So the real binary lives in /opt/demerzel, owned by the service user, with a symlink left on your PATH:
sudo mkdir -p /opt/demerzel
sudo cp "$(readlink -f /usr/local/bin/demerzel-agent)" /opt/demerzel/demerzel-agent
sudo ln -sfn /opt/demerzel/demerzel-agent /usr/local/bin/demerzel-agent
sudo chown -R demerzel:demerzel /opt/demerzel
sudo chmod 755 /opt/demerzel
Define the service
Create /etc/systemd/system/demerzel-agent.service:
[Unit]
Description=Demerzel agent
After=network-online.target
Wants=network-online.target
[Service]
User=demerzel
ExecStart=/opt/demerzel/demerzel-agent --config-dir /etc/demerzel --interval 1m
Restart=on-failure
RestartSec=10
NoNewPrivileges=true
ProtectSystem=strict
ReadWritePaths=/etc/demerzel /opt/demerzel -/run/demerzel/decisions/local -/run/demerzel/decisions/fleet
[Install]
WantedBy=multi-user.target
ProtectSystem=strict mounts the whole filesystem read-only for the agent, so ReadWritePaths lists the only places it writes: its identity, its own binary, and the two spools where it hands decisions to the warden — its own detector’s, and the signed fleet decisions it relays. Without those two, the agent cannot hand the warden anything, and Demerzel raises an Action saying so. The leading - lets the service start when the warden is not installed and they do not exist. Add --auto-update to ExecStart to opt in to automatic updates.
Enable and start
sudo systemctl daemon-reload
sudo systemctl enable demerzel-agent
sudo systemctl restart demerzel-agent
sudo systemctl status demerzel-agent
restart rather than enable --now: if the agent was already running, only a restart makes it adopt the new unit and the relocated binary.
1m is a good default for metrics and health. The agent is deliberately light — collection is read-only and summarized before it leaves the host.Let it read the web logs (optional)
From agent v0.15, the server’s Security tab shows who is hitting your web server — the busiest addresses, the ones collecting 4xx errors, the ones sweeping many of your vhosts at once. For that the agent has to read the web server’s access logs, and by default it can’t: they belong to root:adm, and the agent’s account is deliberately not in adm.
Rather than widen the account, grant it read access to the web log directories alone:
curl -fsSL https://get.demerzel.cloud/service.sh | sudo sh -s grant-web-logs
This sets POSIX ACLs on /var/log/apache2 and /var/log/nginx (those that exist) so the demerzel user can read them — plus a default ACL, so the logs logrotate creates tomorrow stay readable. It grants nothing else: not adm, not the journal, not auth.log. It needs the acl package (sudo apt-get install acl). Logs kept elsewhere? Name their directories after the command: … | sudo sh -s grant-web-logs /srv/logs.
The agent then sends per-address summaries, never a log line and never a query string — see Data & privacy. To take the access back:
curl -fsSL https://get.demerzel.cloud/service.sh | sudo sh -s revoke-web-logs
No restart is needed either way: the agent notices on its next cycle, and the Security tab says plainly when it cannot read the logs.
Let it watch SSH logins (optional)
From agent v0.22, the agent can spot SSH password guessers: addresses that fail to log in over 10 connections within 10 minutes. It reads sshd’s own messages from the system journal, which its account may not read until you allow it:
curl -fsSL https://get.demerzel.cloud/service.sh | sudo sh -s grant-ssh-logs
This adds the demerzel user to the systemd-journal group, which is the only way journald lets an account read system messages, then restarts the agent. The agent asks journalctl for sshd’s messages only (_COMM=sshd) and sends, per address, how many connections failed, plus the user names tried when no such account exists on the server (a bot’s admin or oracle). It never sends a log line, and never the name of an account that exists. An address that logged in successfully is not counted for a day afterwards. To take the access back:
curl -fsSL https://get.demerzel.cloud/service.sh | sudo sh -s revoke-ssh-logs
From v0.24 the guessers are reported even on a server that serves no website. With the access granted, replay covers SSH too: its verdict for ssh-bruteforce counts, as a legitimate user caught, any address it would have banned that also logged in during the range. Once a replay of at least a day caught no one, the warden can enforce the rule: sudo demerzel-warden enable ssh-bruteforce.
Let it read fail2ban’s bans (optional)
If fail2ban already guards the server, Demerzel can show what it banned, beside its own evidence: each address’s dossier says which jail banned it and when, and the server’s Security tab counts the last day’s bans per jail. From agent v0.24:
curl -fsSL https://get.demerzel.cloud/service.sh | sudo sh -s grant-fail2ban-logs
/var/log/fail2ban.log belongs to root and the adm group, and logrotate recreates it so every week: joining adm is the one lasting way to read it, so that is what this does, then it restarts the agent. The agent reads only the lines fail2ban’s actions wrote (Ban, Unban) and sends jail, address, action and time — nothing else. It starts from the moment you grant it: earlier bans stay on the server. To take the access back:
curl -fsSL https://get.demerzel.cloud/service.sh | sudo sh -s revoke-fail2ban-logs
Let it read your hardening audit (optional)
Demerzel can show the report of the audit tool you run, Lynis or OpenSCAP: its score, its findings, and whether it still runs. From agent v0.29:
curl -fsSL https://get.demerzel.cloud/service.sh | sudo sh -s grant-audit lynis
This copies Lynis’s report into a directory only root and the agent can read, each time Lynis writes it. --run weekly schedules the tool instead, and revoke-audit lynis takes it all back. See hardening audits.
Replay past logs
The agent only watches traffic from the moment it can read the logs. To see what happened before — last night’s incident, or a whole week — replay the logs you still have, rotated ones included (access.log.1, access.log.2.gz…). From agent v0.18:
sudo -u demerzel demerzel-agent replay --from 2026-09-28 # a day: prints a summary, sends nothing
sudo -u demerzel demerzel-agent replay --from 2026-09-28 --send # store it in Demerzel
sudo -u demerzel demerzel-agent replay --from 2026-09-28T11:00Z --to 2026-09-28T13:00Z --send
The replay runs the detection rules this agent ships over those lines, afresh, and on its own clock (the log’s), so it concludes exactly what a live run would have then. With --send it goes to Demerzel as a replay, kept apart from what is observed live — it never raises an alert, since it is not happening now — and the server’s Security tab shows its timeline and the incidents it contains. It only reads: no read position moves, and the live detector is untouched.
Each replay is bound to the exact rules, log files and log lines it ran on (by their fingerprints). That is how a rule proves itself on your server: the Security tab lists every rule with whether a replay of that exact version succeeded — a rule changed since has to be replayed again. From agent v0.20.2, it also counts, for each rule, the addresses it would have caught that look like real visitors, and leaves that verdict in /etc/demerzel/replays/ for the warden to clear the rule on.
Name files after the command to read something else, such as logs you archived: … replay --from 2026-09-28 /srv/archive/shop-access.log.gz. The agent’s user must be able to read them.
Check the logs any time with journalctl -u demerzel-agent -f. Next, confirm the data is arriving in Verify & troubleshoot.