EN

Backup & restore

Under review — wording may still change.

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 backup on the old machine, copy the archive, run warden config restore on 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>.txt files directly in packs/. 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 of packs/ 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:

text
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.

Why config-only
Blocklists download themselves again on the next refresh, and statistics regenerate from live traffic. Audit and query logs are append-only journals of past events that you generally do not want to overwrite. The configuration is the one thing that is irreversibly your work, and the one thing where a bad edit can cost you DNS for the whole LAN. The backup scope reflects that.

CLI

CommandWhat it does
warden config backupWrite <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 --listList the restore points in the backup directory, newest first, with each archive’s size. Restores nothing.
warden config restore --latestRestore 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-empty dir, that path is used as written.
  • If [backup] has no dir, or the master has neither a [backup] section nor an includes list, the directory is backups/ next to the master.
  • Otherwise, the directory is backups/ in the state directory, /var/lib/purge-warden/backups on 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:

bash
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 lint

The scheduler-only flags --auto and --reset-auto-failure are covered under automatic backups.

Where archives go

A fresh install writes [backup] dir for you:

/etc/purge-warden/config.toml
toml
[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:

bash
sudo install -d -m 0750 -o purge-warden -g purge-warden /srv/purge-backups

The 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:

bash
sudo warden config backup
# backup written: /var/lib/purge-warden/backups/config-20260505T091322Z.tar.gz
sudo warden config edit

The 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:

/etc/cron.d/purge-warden-backup
cron
# 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 -delete

A migration to a new host:

bash
# 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-warden

The 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.

bash
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 --latest

From the dashboard

The TUI Settings tab drives the same engine:

  • b: open a confirmation; press y to 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, press Enter, confirm with y; 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.

config.toml
toml
[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.timer fires hourly (OnUnitActiveSec=1h, OnBootSec=15min) and catches up at boot if the machine was off when a tick was due.
  • purge-warden-backup.service is Type=oneshot and runs warden config backup --auto as the daemon user.

The timer does not know your interval. Each --auto run reads the configuration and the state file, and decides:

  1. The configuration does not load: skip the run and exit 0 (see below).
  2. No valid auto_interval: exit 0.
  3. Take the lock. If another backup holds it: exit 75.
  4. disabled = true: exit 0.
  5. now - last_attempt < auto_interval: exit 0 (“not due”).
  6. 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:

text
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:

bash
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 --auto runs in a row have failed. A successful warden config backup or --auto run 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 clear disabled.
  • last_attempt: RFC 3339 UTC time of the most recent run. A manual warden config backup updates 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: true after disable_after_failures failures 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:

bash
sudo -u purge-warden warden config backup --reset-auto-failure

It 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) with backup 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

bash
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:

  1. 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.
  2. Check limits. An archive larger than 1 GiB, announcing more than 1 GiB of content, or listing more than 10,000 files is refused.
  3. Validate. The staged master is loaded with the full schema 5 checker, the same one warden config lint uses. 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 starts schema-4 archive (Warden 0.41 or earlier); live config untouched.
  4. 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 until sudo systemctl restart purge-warden. Use warden cluster (leave, join or create) to change membership, and restore an archive taken with the current membership.
  5. 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.
  6. 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. Run sudo systemctl reset-failed purge-warden and sudo systemctl start purge-warden; systemctl reload does not work on a stopped unit. If a running daemon could not be signalled, run sudo systemctl reload purge-warden. In both cases the restore still succeeded, and the command prints a note that names systemctl 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.

Restore deletes what the archive does not declare
Included files and Custom List packs that the live configuration uses and the archive does not declare are deleted. If you created a Custom List or an included file after the archive was taken, copy it aside first, restore, then add it back.
Take a backup before you restore
Restore keeps no copy of the current configuration that you can go back to. The undo data it writes serves only to finish or roll back the restore itself. To be able to return to the configuration you have now, run warden config backup before the restore.

Exit codes

CodeMeaning
0Success: backup written, restore applied, or an --auto run that was not due or that skipped a configuration that does not load.
1Restore 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.
75EX_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 600 on the destination directory.
  • No encryption. For encrypted off-host backup, wrap the archive yourself with gpg --encrypt or age, 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 rejected with reload requires daemon restart, for example because the archive has a different [node] id. Run sudo systemctl restart purge-warden. systemctl reload is refused the same way.
  • No reload was recorded. The SIGHUP was missed. Run sudo systemctl reload purge-warden.
  • The unit had failed. Run sudo systemctl reset-failed purge-warden and sudo 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.service shows the scheduled backup is skipped and writes nothing. Run sudo warden config lint and fix the error. The next tick backs up again.
  • The scheduler disabled itself after repeated failures. Check sudo cat <backup-dir>/.auto_state for "disabled": true and the last_outcome message, fix the cause (usually directory permissions), then run sudo -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

↑↓ to navigate · Enter to open · Esc to close See all results