EN

Server globals

Work in progress — content may be incomplete.

The handful of knobs that apply to the whole resolver — listening interfaces, upstream servers, defaults.

What this is

Server globals are the top-level [server] and [upstream] sections of the master config.toml. They control how the daemon listens (which addresses and ports answer DNS), who is allowed to ask (allow_from), where Warden forwards queries it cannot answer locally (upstream.servers), and which profile applies when nothing else matches (default_profile).

Unlike devices, profiles, or blocklists — which Warden exposes through per-entity CLI verbs — these globals have no flag-based setter. There is no warden config set server.allow_from "...". The supported way to change them is warden config edit, which opens the master TOML in $EDITOR, validates at save, and (after a follow-up warden reload) applies them to the running daemon.

When you reach for it

  • First boot, after warden init. Pick the listening interfaces, set the upstream resolvers, set allow_from to your LAN’s CIDR. Everything else can wait.
  • Restricting access. You moved Warden to a host that’s reachable from more than one network and you want to refuse queries from the WAN. Tighten allow_from.
  • Changing upstreams. Switching from your ISP’s resolver to a privacy resolver (Quad9, Mullvad, your own DoT) — change upstream.servers, optionally upstream.protocol, reload.
  • Setting a global fallback. Devices that don’t match any [[devices]], [[groups]], or [[subnets]] entry hit [server].default_profile. If left unset, unmapped queries get REFUSED.

You don’t edit globals to add a single device exception, schedule a block window, or import a new blocklist — those are entity-level changes with their own CLI verbs (warden device add, warden schedule add, warden blocklist add).

Schema

The two relevant sections of the master config.toml:

/etc/purge-warden/config.toml
toml
[server]
listen        = ["0.0.0.0:53", "[::]:53"]   # interfaces + ports answering DNS
allow_from    = ["192.168.1.0/24"]          # CIDRs allowed to query
default_profile = "default"                 # profile for unmapped clients
enforce_client_mac = true                   # downgrade on MAC mismatch (see Devices)
log_level     = "info"                      # error|warn|info|debug|trace

[upstream]
servers       = ["9.9.9.9:53", "149.112.112.112:53"]  # ordered fallback list
protocol      = "udp"                       # udp|tcp|dot|doh
timeout_ms    = 1500                        # per-server timeout
retries       = 2                           # attempts before NXDOMAIN

[server] field reference

FieldTypeRequiredDefaultPurpose
listenlist of host:portyes["0.0.0.0:53", "[::]:53"]Sockets the daemon binds. IPv4 and IPv6 must be specified explicitly.
allow_fromlist of CIDRsno["0.0.0.0/0", "::/0"] (open)Source addresses allowed to query. Empty list refuses every client.
default_profileprofile idnounset (REFUSED)Profile applied to clients not matched by device/group/subnet.
enforce_client_macboolnotrueIf the live ARP MAC doesn’t match a device’s mac / mac_aliases, the device is downgraded to subnet-level matching.
log_levelenumnoinfoVerbosity of the daemon log.

[upstream] field reference

FieldTypeRequiredDefaultPurpose
serverslist of host:portyesRecursive resolvers Warden forwards to when it can’t answer locally. Tried in order.
protocoludp / tcp / dot / dohnoudpTransport for upstream queries. dot and doh require host:port plus a hostname for SNI/SANs.
timeout_msu32no1500Per-server timeout in milliseconds.
retriesu32no2How many servers to try before returning NXDOMAIN.

For the canonical schema with all constraints and defaults, see the TOML reference.

Editing the master — the warden config edit flow

Because there is no flag-based setter for [server] and [upstream], the canonical flow is:

  1. Take a backup first. A bad [server] edit can leave the LAN without DNS — warden config backup takes seconds and gives you a one-command rollback.
  2. warden config edit opens the master in $EDITOR. On save, the handler runs the full v1 validator. If it rejects the change, the live file is unchanged and the validator prints offending file:line.
  3. warden reload signals the daemon to swap to the new config. Unlike per-entity CRUD verbs, warden config edit does not trigger a reload automatically — you reload deliberately, after re-reading the diff.
  4. Verify. A quick warden config show --resolved confirms the live value, and warden resolve <client-ip> confirms the resolver still picks the expected profile.
sudo warden config backup
sudo warden config edit
sudo warden reload
warden config show --resolved --section server
Why `edit` doesn't auto-reload
Per-entity verbs like warden device add apply one well-defined change, so reloading immediately is safe. warden config edit lets you change anything — possibly several things at once — and the operator usually wants to confirm the diff before the daemon picks it up. The two-step flow exists so you can lint, diff, or back out before reloading.

allow_from — restrict who can query

A common first edit. Open the master, then change the value:

/etc/purge-warden/config.toml
toml
[server]
allow_from = ["192.168.1.0/24"]

Multiple ranges are listed as TOML array entries. Both v4 and v6 CIDRs are accepted; an empty list (allow_from = []) refuses every client and is normally only useful in test environments.

# preview, edit, reload
sudo warden config diff /etc/purge-warden/backups/config-20260505T091322Z.tar.gz
sudo warden config edit
sudo warden reload

upstream.servers — change the upstream resolvers

Same flow, in the [upstream] section:

/etc/purge-warden/config.toml
toml
[upstream]
servers = ["9.9.9.9:53", "149.112.112.112:53"]
protocol = "udp"

For DoT / DoH, the entries take the form hostname:port, and the validator enforces protocol-specific constraints — see the TOML reference.

default_profile — the global fallback

A short edit, but think before saving: it’s the profile that any unidentified client gets, including a phone joining the guest VLAN for the first time.

/etc/purge-warden/config.toml
toml
[server]
default_profile = "guest"

If you leave it unset and a client doesn’t match any subnet either, Warden answers REFUSED — quiet, but it tends to confuse non-technical users.

CLI

Globals don’t have entity-level CRUD. The relevant verbs are all under warden config:

CommandWhat it does
warden config show [--section server]Print the live [server] (or any other section).
warden config show --resolvedShow the effective config after include resolution and defaults.
warden config editOpen the master in $EDITOR; validate at save. Does not auto-reload.
warden config lintValidate the current on-disk tree. Exit 0 clean, 1 errors, 2 warnings.
warden config diff <other>Compare the live master against another file or extracted backup.
warden reloadTell the daemon to re-read from disk after edit.

For backups and restore around risky edits, see backup & restore. For the full warden config surface, see the CLI reference.

Decision precedence

[server].default_profile is level 5 — the lowest in the resolver chain. Any device, schedule, group, or subnet match wins over it. That’s intentional: globals are the floor, not the ceiling.

[server].listen and [upstream].* are not part of the resolver chain at all — they govern how queries are received and forwarded, not which profile is applied. Changing them affects every client equally.

For the full chain, see Devices → Decision precedence.

Security

  • allow_from is enforced at the listener level. Refused clients receive nothing — no REFUSED response, just a dropped packet — and appear in the audit log under LISTENER_REFUSED.
  • Upstream credentials. If you use a DoH endpoint that requires a bearer token, store the token in a *.secret file and reference it from the TOML — backup archives never include *.secret.
  • enforce_client_mac is ergonomic, not cryptographic. A determined attacker with physical access can spoof any MAC. The flag’s job is to stop a curious teenager from changing their static IP to escape the kids profile. See the threat model for the precise guarantee.

Troubleshooting

warden config edit exits 0 but the daemon still serves the old upstream. You skipped warden reload. Per-entity CRUD verbs reload automatically; warden config edit does not.

Validation rejects the new TOML and points at a CIDR you didn’t touch. Check allow_from and upstream.servers are TOML arrays (square brackets, comma-separated), not bare strings. The validator is strict about types.

Clients on the LAN suddenly get REFUSED for everything. You probably tightened allow_from and forgot to include the LAN CIDR. Run warden config show --section server to see the live value; restore from the pre-edit backup if needed.

The daemon refuses to start with bind: address already in use. Another process is on port 53. Either stop it (systemd-resolved is the usual culprit on Debian/Ubuntu — see systemd service) or change [server].listen.

For more, see troubleshooting.

See also