EN

Configuration

Warden is a single-binary DNS filter configured by one plain-text TOML file. This page covers where Warden finds that file, the smallest config worth running, what changes apply without a restart, how to validate before you reload, and where the full field reference lives.

For installing the binary first, see the install guide. For every field and value, see the TOML reference.

Where Warden reads config

Warden resolves the config path in this order. The first existing file wins — there is no merging across paths.

  1. The --config flag on the command line.
  2. /etc/purge-warden/config.toml (the standard system path).
  3. /var/lib/purge-warden/config.toml (older installs, kept for backward compatibility).
  4. ./config.toml (the working directory, handy in development).

Pass an explicit path when you want to be sure:

warden --config /etc/purge-warden/config.toml start

Ask the running daemon which file it loaded:

warden status

Splitting one big file into many

For more than ~20 devices, split the config across a directory. The master file declares what to pull in:

/etc/purge-warden/config.toml
toml
schema_version = 1

[server]
listen = "0.0.0.0:53"
default_profile = "default"
allow_from = ["192.168.1.0/24", "127.0.0.0/8"]

includes = [
  "devices.d/*.toml",
  "profiles.d/*.toml",
  "subnets.d/*.toml",
  "groups.d/*.toml",
  "blocklists.d/*.toml",
]
Globs are resolved relative to the master file. One file per room, family member, or VLAN keeps diffs small.

A minimal config

The smallest config that protects a home LAN with the three default purge.cc lists:

/etc/purge-warden/config.toml
toml
schema_version = 1

[server]
listen = "0.0.0.0:53"
default_profile = "default"
allow_from = ["192.168.1.0/24", "127.0.0.0/8"]

[[blocklists]]
id = "privacy-ads"
url = "https://lists.purge.cc/privacy/ads.txt"
format = "domains"

[[blocklists]]
id = "privacy-tracking"
url = "https://lists.purge.cc/privacy/tracking.txt"
format = "domains"

[[blocklists]]
id = "security-malicious"
url = "https://lists.purge.cc/security/malicious.txt"
format = "domains"

[profiles.default]
display_name = "Home default"
blocklists = ["privacy-ads", "privacy-tracking", "security-malicious"]
block_response = "zero"
Validate it, then start the daemon:

warden config lint
sudo systemctl start purge-warden

Smoke test from any machine on the LAN:

dig @<warden-ip> google.com           # normal answer
dig @<warden-ip> doubleclick.net      # 0.0.0.0 — blocked
Warning
if you bind to 0.0.0.0, you must set allow_from. Warden refuses to start as an open resolver.

Hot reload

Most config changes apply without restarting the daemon. After editing the file:

warden reload

Or send a signal directly:

sudo kill -SIGHUP $(pidof warden)

If you change config through a CLI mutation (warden device add, warden profile blocklists, warden blocklist add…), the reload happens automatically — nothing further to run.

Reloads cleanly, no downtime:

  • adding or removing blocklists, profiles, devices, groups, subnets, schedules
  • changing default_profile, block_response, TTLs
  • editing allow_from, upstream servers, upstream mode
  • toggling tracking, query log, local DNS records

Requires a full daemon restart:

  • the listen address or port (the socket has to be rebound)
  • the user or group the daemon runs as
  • schema_version upgrades

If the new config fails validation, the reload is rejected and the previous config keeps serving traffic. The reason lands in the journal:

journalctl -u purge-warden -n 30

Validating the file

Always lint before reloading:

warden config lint

Exit codes:

  • 0 — clean, safe to reload.
  • 1 — errors, the daemon would reject this file.

The linter catches the mistakes that quietly break a setup:

  • Unknown references. A device pointing at a missing profile id, a profile listing a blocklist that isn’t declared, a schedule targeting a group that doesn’t exist.
  • Duplicate ids. Two [[devices]] or [[blocklists]] sharing an id.
  • Malformed CIDRs. 192.168.1.0/33, missing prefix length, IPv4 written like IPv6.
  • Group priority collisions. A device that ends up in two groups with the same priority and different profiles — Warden can’t pick.
  • Insecure secrets. A secrets.toml readable by anyone other than the owner.
  • Open-resolver footgun. listen = "0.0.0.0:53" with an empty allow_from.

Preview what would change between the file on disk and the running daemon before you reload:

warden config diff --live
Info
warden config validate still works as an alias of lint. New scripts should prefer lint.

Common patterns

Most setups combine a handful of entities. Each is documented in full in the TOML reference — here’s the shape:

  • One device, one profile. Pin a laptop or phone by IP or MAC, point it at a profile.
  • Group several devices. Put the kids’ tablets into a group; change one line to retune the whole group.
  • VLAN-wide defaults. Use [[subnets]] to assign a profile to a CIDR; longest-prefix wins, so a tighter range overrides a broader one.
  • Time-based rules. Use [[schedules]] to swap a group’s profile between hours, e.g. “kids-night from 21:00 to 07:00”.
  • Per-device exceptions. Add an allow_rules or deny_rules entry on the device for a one-off domain that doesn’t deserve a whole profile.

When something doesn’t behave — wrong profile applied, blocklist not loading, the default catching a device you thought was mapped — start with troubleshooting.

Importing configuration files
SafeSearch
Tags
Local DNS records
Blocklists
Server globals