Backup & restore
Snapshot the configuration, restore it as one safe transaction, keep the daemon running.
What this is
warden config backup writes a single tar.gz archive of your Warden
configuration: the master config.toml, the files it includes, and your Custom
List packs. warden config restore <archive> takes one of those archives,
checks it, replaces the live configuration as one transaction, then asks the
running daemon to reload. You need a restart only when the restored
configuration changes a setting that applies at start, such as the [node] id,
the listen address or the cache. Until you run
sudo systemctl restart purge-warden, the daemon refuses this reload and every
later one.
These are operator checkpoints, not differential snapshots. Take one before a risky change (“I’m about to swap blocklists”), keep a few rotating copies on a separate disk, and restore the most recent valid one when something breaks. Warden can also take these checkpoints on a schedule; see automatic backups below.
When you reach for it
- Before a structural change. Editing
[server], swapping the upstream resolvers, importing a new blocklist set: anything where a typo or validation error could leave you without DNS for the LAN. Take a backup first; on failure, restore in seconds. - Before an upgrade. Take a backup, then follow the upgrade steps. Archives made by Warden 0.41 or earlier cannot be restored by 0.44.0 or later, so take a fresh one once the upgrade is done.
- Migrating to a new host. Run
warden config backupon the old machine, copy the archive, runwarden config restoreon the new one. - Recovering from a bad edit. A change slipped through with a logic error you only noticed an hour later? Restore the latest pre-change archive and let the daemon reload.
- Routine off-host backup. A nightly cron job that runs
warden config backup --out /mnt/backup/warden/and prunes old archives gives you a rolling window, or let the built-in scheduler handle it.
You don’t need backup and restore for downloaded blocklists, statistics,
audit logs, or query logs. They live in the state directory, not in the
configuration (see below). For full system state, snapshot
/var/lib/purge-warden/ separately.
Where the configuration lives
A fresh install of 0.44.0 or later keeps its configuration in /etc/purge-warden. The
backup archives go to /var/lib/purge-warden/backups, because archives are
state, and Warden keeps state under /var/lib.
A host upgraded from 0.41.x, 0.42.0 or 0.43.0 keeps its configuration in
/var/lib/purge-warden/config.toml until you run warden config relocate.
Backup and restore work the same way in both places. They use whichever master
Warden finds first; see CLI reference.
After a move, backups cover the /etc set, and the archive directory stays
wherever [backup] dir points. See Upgrade.
What’s included
A backup is a tar.gz of the configuration directory: the folder that
holds the master config.toml. It contains:
- The master TOML.
- Every file the configuration includes through
includes = [...], wherever it sits inside the configuration directory. The set comes from the real include list, not from a fixed list of folder names. - Your Custom List packs, in
packs/. - Any other plain file or folder in the configuration directory. A backup errs on the side of keeping too much.
It does not contain:
- The backup directory itself.
- Warden’s own internal working stores, such as the node corpus and the cluster and node stores.
- Downloaded blocklist payloads and statistics, which live in the state
directory (
/var/lib/purge-warden/lists/,/var/lib/purge-warden/data/). - The audit log and the query log.
- Entries in
packs/that are not packs, such as an editor swap file (.kids.txt.swp) or a backup copy (kids.txt.bak). Warden reads, backs up and restores only<id>.txtfiles directly inpacks/. The backup prints a warning that names every entry it left out:packs/ holds entries that are not packs and are ignored: .... Move those files out ofpacks/if you want to keep them.
On a fresh install the configuration directory (/etc/purge-warden) holds
configuration only. On a host whose configuration still sits in
/var/lib/purge-warden, the configuration directory is also the state
directory, so an archive can pick up more than configuration and grow larger.
Moving the configuration to /etc with warden config relocate keeps archives
to configuration only.
A backup refuses to run if the configuration directory holds a symbolic link, a hard-linked file, or a special file (a socket or device), or if an included file lies outside the configuration directory. The error names the offending entry. A backup that quietly leaves something out would be worse than one that stops.
If the configuration does not load, a manual warden config backup still
runs, so you can keep a copy of a broken configuration before you repair it. It
prints a warning and the first error:
warning: /etc/purge-warden/config.toml does not currently load (1 error(s)); the archive captures /etc/purge-warden as it stands, but its coverage was not verified against the declared includes. first error: ...
Restore checks an archive the same way warden config lint does, so it refuses
such an archive as it is. To use it, extract it and copy what you need by hand.
The scheduled backup does not archive a configuration that does not load: it
skips the run instead. See automatic backups.
There is no embedded manifest and no encryption. The archive is a plain
tar -czf.
CLI
| Command | What it does |
|---|---|
warden config backup | Write <backup-dir>/config-<UTC-timestamp>.tar.gz. |
warden config backup --out <dir> | Write the archive to <dir>/config-<UTC-timestamp>.tar.gz instead. Warden creates the directory if it is missing. |
warden config restore <archive> | Validate the archive, replace the live configuration, ask the daemon to reload. |
warden config restore --list | List the restore points in the backup directory, newest first, with each archive’s size. Restores nothing. |
warden config restore --latest | Restore the newest archive in the backup directory without naming it. |
The backup directory is [backup] dir when it is set, otherwise a backups/
directory next to the master config.toml. The archive file name is always
config-YYYYMMDDTHHMMSSZ.tar.gz; only the directory is configurable.
When the configuration does not load, Warden reads the master file as plain TOML to find the directory:
- If
[backup]sets a non-emptydir, that path is used as written. - If
[backup]has nodir, or the master has neither a[backup]section nor anincludeslist, the directory isbackups/next to the master. - Otherwise, the directory is
backups/in the state directory,/var/lib/purge-warden/backupson a standard install. This happens, for example, when an included file might hold[backup], or when the master is not valid TOML.
warden config backup, warden config restore --list,
warden config restore --latest and the dashboard all use the same rule, so
they look in the same place.
There is no --json output. To see what you can restore, use
warden config restore --list. To look inside an archive without restoring
it, extract it to an empty scratch directory and run
warden config lint against the extracted config.toml:
mkdir /tmp/archive-check
tar --no-same-owner --no-same-permissions -xzf config-20260505T091322Z.tar.gz -C /tmp/archive-check
warden --config /tmp/archive-check/config.toml config lintThe scheduler-only flags --auto and --reset-auto-failure are covered under
automatic backups.
Where archives go
A fresh install writes [backup] dir for you:
[backup]
dir = "/var/lib/purge-warden/backups"A host upgraded from an earlier release may have no [backup] section. Its
archives then go to backups/ next to the master config.toml, which is
/var/lib/purge-warden/backups while the configuration is still in
/var/lib. Before warden config relocate, set dir to an absolute path
such as /var/lib/purge-warden/backups with warden config edit. Relocate
refuses while dir is unset or relative.
Point archives elsewhere with dir = "/srv/purge-backups". The command line,
the scheduled backup and the dashboard read [backup]. The daemon reads only
dir, to point you at the archives when it refuses to start. You set it in the
file; the dashboard cannot edit it.
Backup directory permissions. Backups run as the daemon user
(purge-warden), whether triggered from the CLI, the timer or the dashboard.
The directory must be writable by that user, or tar fails with
Permission denied. For /var/lib/purge-warden/backups there is nothing to
do. When the configuration is in /etc, as on every fresh install, the first
backup creates it, owned by purge-warden with mode 0750. On a host whose
configuration is still in /var/lib, the installer prepares it. Archives hold your private configuration
and device inventory, so other users cannot read that directory.
If you point dir somewhere else, such as a dedicated volume, you own that
path’s permissions. Keep it private as well:
sudo install -d -m 0750 -o purge-warden -g purge-warden /srv/purge-backupsThe backup service also runs in a sandbox. It can write only to
/etc/purge-warden and /var/lib/purge-warden. A custom dir outside those
two paths works for a manual warden config backup --out, but the scheduled
backup cannot write there until you extend ReadWritePaths= in the unit with a
systemd drop-in. See systemd service.
Examples
A one-shot manual checkpoint before editing:
sudo warden config backup
# backup written: /var/lib/purge-warden/backups/config-20260505T091322Z.tar.gz
sudo warden config editThe command also lists each entry it captured.
A rolling nightly backup to an off-host mount. The mount must be writable by
the purge-warden user:
# Nightly Warden config snapshot, rotate after 30 days.
15 3 * * * root /usr/local/bin/warden config backup --out /mnt/backup/warden/ && \
find /mnt/backup/warden/ -name 'config-*.tar.gz' -mtime +30 -deleteA migration to a new host:
# on the old host
sudo warden config backup --out /tmp/
scp /tmp/config-*.tar.gz newhost:/tmp/
# on the new host, after the installer has finished
sudo warden config restore /tmp/config-20260505T091322Z.tar.gz
sudo systemctl restart purge-wardenThe archive carries the old host’s [node] id, which applies only at start, so
the daemon refuses the reload until this restart. The installer writes a new
[node] id on every host, so every migration changes it.
On the new host the restore must not change cluster membership; see restore rules.
A recovery from a bad edit: list the restore points, then take the newest. Check the dates first: if any backup ran after the bad edit (a manual one, or a scheduled one while the edited configuration still loads), the newest archive holds the bad edit too.
sudo warden config restore --list
# 3 restore point(s) in /var/lib/purge-warden/backups:
# config-20260505T091322Z.tar.gz (14.2 KiB)
# config-20260504T031500Z.tar.gz (14.1 KiB)
# config-20260503T031500Z.tar.gz (13.9 KiB)
sudo warden config restore --latestFrom the dashboard
The TUI Settings tab drives the same engine:
b: open a confirmation; pressyto back up. The result, with the archive path, appears in a result card.R: open the restore picker. It shows every restore point with its date, relative age, and size, newest first. Pick one, pressEnter, confirm withy; Warden restores it and asks the daemon to reload. The confirmation reminds you that restore keeps no copy of the current configuration, and that it deletes packs and included files the archive does not declare.
Automatic backups
Everything above is manual. Warden can also back up on a schedule. A systemd
timer wakes the binary every hour, and warden config backup --auto decides
whether a backup is actually due. The scheduler is pure tooling: the daemon is
not involved.
Configuration
Extend the existing [backup] table with up to five optional fields. A fresh
install already has the table with dir; a second [backup] table makes the
file fail to load.
[backup]
dir = "/var/lib/purge-warden/backups"
auto_interval = "24h" # off when absent. "<N>h" or "<N>d", range 1h..720h (30d).
on_change = false # reserved for a future release; parsed but inert.
retention_count = 30 # keep at most N archives (0 = unbounded).
retention_days = 90 # drop archives older than D days (0 = unbounded).
disable_after_failures = 3 # auto-disable after N consecutive failures (0 = never).All are optional. With no auto_interval the scheduler does nothing.
retention_count and retention_days are combined: an archive is dropped if
it passes either limit. Retention only removes config-*.tar.gz files; the
.lock and .auto_state files are never pruned.
How the schedule runs
Two systemd units, both installed for you:
purge-warden-backup.timerfires hourly (OnUnitActiveSec=1h,OnBootSec=15min) and catches up at boot if the machine was off when a tick was due.purge-warden-backup.serviceisType=oneshotand runswarden config backup --autoas the daemon user.
The timer does not know your interval. Each --auto run reads the
configuration and the state file, and decides:
- The configuration does not load: skip the run and exit 0 (see below).
- No valid
auto_interval: exit 0. - Take the lock. If another backup holds it: exit 75.
disabled = true: exit 0.now - last_attempt < auto_interval: exit 0 (“not due”).- Otherwise: back up, update the state, apply retention.
A change to auto_interval takes effect on the next hourly tick. You do not
need systemctl daemon-reload or a reinstall.
A configuration that does not load would give an archive that restore refuses, so the scheduled run writes nothing. It takes no lock, leaves the state file and the failure count as they are, and prints:
warning: /etc/purge-warden/config.toml does not load (1 error(s)); the scheduled backup is skipped and writes nothing. Run `warden config lint` to see every error. first error: ...
The message is in journalctl -u purge-warden-backup.service. Fix the
configuration, and the next tick backs up again.
Automatic backups are off until you set auto_interval. Turning them on is one
of the habits in
Prevent an outage.
State file
<backup-dir>/.auto_state is JSON, written atomically, and safe to read:
sudo cat /var/lib/purge-warden/backups/.auto_state
# {
# "consecutive_failures": 0,
# "last_attempt": "2026-05-29T03:00:00Z",
# "last_outcome": { "kind": "ok" },
# "disabled": false
# }consecutive_failures: how many--autoruns in a row have failed. A successfulwarden config backupor--autorun resets it to zero. A failed manual run does not count. A backup from the dashboard does not read or write.auto_state. A success does not cleardisabled.last_attempt: RFC 3339 UTC time of the most recent run. A manualwarden config backupupdates it too, also when writing the archive fails, so such a failed manual run postpones the next automatic backup by a full interval.last_outcome:{"kind":"ok"}or{"kind":"err","message":"..."}.disabled:trueafterdisable_after_failuresfailures in a row. Manual backups ignore it and run anyway.
Re-enable after auto-disable
After disable_after_failures failures in a row the scheduler sets
disabled = true, and later ticks do nothing. Clear it:
sudo -u purge-warden warden config backup --reset-auto-failureIt resets consecutive_failures and disabled, writes no archive, and
keeps the history (last_attempt and last_outcome). Running it twice is
harmless. The next --auto tick tries again, and the red banner on the
Settings tab clears.
Lock file
<backup-dir>/.lock is created at the start of every backup, manual or
automatic, and removed at the end:
- If another backup is already running, the command exits
75(EX_TEMPFAIL) withbackup in progress (lock held since <ts>)on stderr. - A stale lock (older than 5 minutes, left by a crashed run) is removed by the next run.
Inspecting the scheduler
systemctl status purge-warden-backup.timer # when the next tick fires
systemctl status purge-warden-backup.service # last tick and exit code
journalctl -u purge-warden-backup.service # tick history, including "not due" and "disabled"How restore works
Restore is a policy transaction: it either finishes completely or leaves your live configuration as it was. In order:
- Extract. Warden copies the archive into a private staging directory under the system temp directory, then unpacks that copy. The staging directory is removed on exit, also on failure.
- Check limits. An archive larger than 1 GiB, announcing more than 1 GiB of content, or listing more than 10,000 files is refused.
- Validate. The staged master is loaded with the full schema 5 checker,
the same one
warden config lintuses. If it fails, the live configuration is not touched. Exit code 1; the errors are printed with file and line. An archive from Warden 0.41 or earlier is refused with a message that startsschema-4 archive (Warden 0.41 or earlier); live config untouched. - Check cluster membership. A restore never changes whether this node is
standalone, a primary or a secondary, or which cluster it belongs to. If the
archive’s membership differs from the live one, the restore is refused
with
refusing restore: it would change this node's .... Membership means standalone or member, the role and, for a member, its cluster identity and[node]id. A standalone archive restores onto a standalone node even if[node]differs. The node id applies only at start: the daemon then refuses this reload and every later one untilsudo systemctl restart purge-warden. Usewarden cluster(leave, join or create) to change membership, and restore an archive taken with the current membership. - Apply as one transaction. Warden swaps in the files the restored master
declares: the master, its includes and the Custom List packs. It writes the
change with a recovery copy, so a failure part way rolls back. A failed
restore says so, for example
restore rolled back after a commit failure (transaction <id>): live config unchanged. - Ask the daemon to reload. If a daemon is running, the command sends
SIGHUP. If no daemon is running (for example, the unit failed), the restored files apply at the next start. Runsudo systemctl reset-failed purge-wardenandsudo systemctl start purge-warden;systemctl reloaddoes not work on a stopped unit. If a running daemon could not be signalled, runsudo systemctl reload purge-warden. In both cases the restore still succeeded, and the command prints a note that namessystemctl reload.
If the archive already matches the live configuration, the command prints
config already matches the archive; no reload requested and exits 0.
If the live configuration does not load (a broken master), restore repairs it
from the archive. The command prints repaired invalid config at <path>; unnamed paths retained.
Restore only replaces the files the archive’s master declares. A file in the configuration directory that no include refers to is left as it is. A file that the live configuration uses but the archive does not is deleted: an included file, or the pack of a Custom List that the archive does not declare. When restore repairs a configuration that does not load, it deletes nothing. On a cluster secondary, restore refuses any change to files that the primary replicates; make those changes on the primary.
If a restore is interrupted in a way that Warden cannot settle itself, it
prints restore state is uncertain and tells you to run
systemctl restart purge-warden. Boot recovery then finishes or rolls back the
transaction before any other write.
warden config backup before the restore.Exit codes
| Code | Meaning |
|---|---|
0 | Success: backup written, restore applied, or an --auto run that was not due or that skipped a configuration that does not load. |
1 | Restore validation failed (the live configuration is unchanged and the error names the file and key), or an I/O error: archive missing, unreadable, malformed, refused for membership or size, or destination not writable. |
75 | EX_TEMPFAIL: a backup is already in progress (the lock is held). Retry shortly. |
Security
- Archives contain plaintext configuration, including any secrets you wrote
into the TOML. Treat them as sensitive; do not push them to a public Git
remote, and prefer
chmod 600on the destination directory. - No encryption. For encrypted off-host backup, wrap the archive yourself
with
gpg --encryptorage, or keep it on an encrypted filesystem. - No signature. Restore trusts the archive beyond the schema check. Do not restore archives from untrusted sources.
- No audit record. Backup and restore do not write audit-log records. The daemon reload after a restore is recorded as a reload event.
Troubleshooting
warden config restore fails with schema-4 archive (Warden 0.41 or earlier).
The archive was taken by Warden 0.41 or earlier. 0.44.0 and later restore schema 5
archives only. The live configuration is untouched. To reuse the settings,
extract the archive into a new empty directory and copy what you need into
the current configuration with warden config edit. The error prints the exact
tar command.
refusing restore: it would change this node's ....
The archive was taken while the node had a different cluster role, or belonged
to a different cluster. Restore an archive taken with the current membership,
or change membership first with warden cluster.
refusing restore: this node's cluster membership is unknown.
The live master does not load, and the node looks like a cluster member. Run
warden config lint, fix the master by hand, then restore again.
warden config restore exits 0 but the daemon still serves the old
configuration.
Run sudo warden audit tail -n 20 or journalctl -u purge-warden to see what
happened to the reload:
- The reload is
rejectedwithreload requires daemon restart, for example because the archive has a different[node]id. Runsudo systemctl restart purge-warden.systemctl reloadis refused the same way. - No reload was recorded. The
SIGHUPwas missed. Runsudo systemctl reload purge-warden. - The unit had failed. Run
sudo systemctl reset-failed purge-wardenandsudo systemctl start purge-warden.
warden config backup exits 75 with backup in progress.
Another backup holds the lock, usually a scheduled run that overlaps your manual
one. Wait a moment and retry. A stale .lock older than 5 minutes is cleared on
the next run.
Scheduled backups stopped happening. There are two usual causes:
- The configuration does not load. Each tick skips the run, and
journalctl -u purge-warden-backup.serviceshowsthe scheduled backup is skipped and writes nothing. Runsudo warden config lintand fix the error. The next tick backs up again. - The scheduler disabled itself after repeated failures. Check
sudo cat <backup-dir>/.auto_statefor"disabled": trueand thelast_outcomemessage, fix the cause (usually directory permissions), then runsudo -u purge-warden warden config backup --reset-auto-failure.
tar: Cannot open: Permission denied from warden config backup.
The backup directory is not writable by the daemon user. Create it with the
right owner (see where archives go) or pick a writable --out.
backup refuses symlink member ... or backup refuses hard-linked member ....
Something in the configuration directory is a link or a special file. Replace
it with a regular file, or move it out of the configuration directory.
The backup file grows over time. An unused file has built up in the configuration directory. Look through it, and remove what you no longer need.
For other operational issues, see troubleshooting.
See also
warden configreference: every config command at a glance.- Upgrade: take a backup first, and move the configuration to
/etc. - File paths: where Warden reads and writes on disk.
- systemd service: the timer and service units.
- Troubleshooting: broader recovery flows.