EN

Blocklists

Work in progress — content may be incomplete.
This page is outdated — do not follow it

Everything below describes the retired v1 list model: the category field, the [[categories]] section, [profiles.<id>].blocklists, [profiles.<id>].categories, the warden category … verbs, and warden blocklist set-category. None of them exist any more, and a config that uses them is rejected at load with unknown field.

Lists are now selected by tag intersection: a blocklist carries tags, a profile asks for tags, and the list applies when the two sets overlap. That model is documented correctly under profiles. Run warden migrate to convert a pre-tag config.

A rewrite of this page is specified and in progress.

A blocklist is a domain feed. Profiles decide which feeds to enforce, and Warden decides what to do with the matches.

What this is

A blocklist is a named, declarative entry that tells Warden where to fetch a list of domains, how to parse it, how often to refresh it, and how it should participate in the filter — as a deny source (the default) or, with trust = "local", as an allow source. Every [[blocklists]] row gets an id, lives in the merged config, and is referenced from one or more profiles via [profiles.<id>].blocklists (direct subscription) or [profiles.<id>].categories (indirect, by category tag).

This page covers when to add a [[blocklists]] entry, every dimension you can tune on it, the CLI / TUI / API surfaces that read and write blocklists, how a list participates in the filter chain, and how Warden sandboxes external sources so a hostile feed cannot punch through your overrides. For how a profile picks the lists it enforces in the first place, see profiles; for the operator-authored counterpart that can express allows, regexes, and $important, see admin rules.

When you reach for it

You add or replace blocklists when:

  • Setting up. A fresh install ships subscribed to security/malicious, privacy/ads, and privacy/tracking from the purge.cc catalogue. That covers the homelab default. Browse the rest with warden lists catalog.
  • Tightening a profile. Layer category-specific feeds on top — content/adult for a kids profile, content/gambling for a small office, services/tiktok for a focus profile.
  • Bringing your own list. Either a remote URL you trust (https://big.oisd.nl/, a corporate-internal allowlist) or a local file authored by you and imported with warden blocklist import-local.
  • Migrating from Pi-hole or AdGuard Home. The domains, hosts, and adguard formats cover virtually every adlist out there. Existing files import as-is.

You don’t reach for a [[blocklists]] entry when you want to:

  • Allow or deny a single domain for one device or one profile — that’s an admin rule referenced from [profiles.<id>].admin_rules or [[devices]].allow_rules / deny_rules.
  • Express @@ allow rules, $important, regex, or wildcards. External lists are sandboxed away from those; only admin rules honour them. See security notes.
  • Tune the block response (RCODE shape, TTL) — that lives on the profile, not the list.

Schema

A blocklist is an array-of-tables row[[blocklists]], repeatable. Each row needs at minimum an id, a display name, and a URL.

[[blocklists]]
id           = "privacy-ads"
display_name = "Privacy: ads"
url          = "https://lists.purge.cc/privacy/ads.txt"
format       = "domains"
kind         = "block"
trust        = "remote-unsigned"
category     = "default"

Dimensions

A blocklist is a vector across these independent axes. Mix and match — only id, display_name, and url are mandatory; everything else has a sensible default.

GroupDimensionTOML fieldWhat it tunes
IdentityididStable cross-reference. Profiles subscribe by this id. Charset [a-z0-9-], ≤64 chars, no leading/trailing -.
Display namedisplay_nameOperator-facing label rendered in warden blocklist list, the TUI Lists tab, and audit lines. Must not be empty.
SourceURLurlHTTPS endpoint to fetch from. At runtime only https:// is allowed; loopback / RFC1918 / link-local / CGNAT / multicast / ULA hosts are refused. Up to 3 redirect hops.
Auth token (optional)auth_token_refNames a key in secrets.toml. Resolved at fetch time and sent as Authorization: Bearer <value>. The secret never appears in the merged config.
ParsingFormatformatdomains (one host per line), hosts (/etc/hosts-style with leading IPs), or adguard (`
Entry capmax_entriesHard truncation cap per list. Beyond the cap the parser stops with a WARN. Default 5_000_000 (the CLI cap; the engine bitmask supports up to 64 list sources system-wide).
LifecycleRefresh cadenceupdate_interval_hoursHow often the daemon re-downloads. Default 12. Runtime floor is 60 seconds. The legacy alias refresh_interval_hours is still accepted with a deprecation warning.
Enabled flagenabledDefault true. Declared in config but disabled — kept as a row so referencing profiles still validate.
Trust modelDirectionkindblock (default) — domains added to the deny set. allow — domains added to the allow set; only valid with trust = "local".
Provenancetrustremote-unsigned (default, HTTPS only), local (file authored by the operator, imported via warden blocklist import-local), or signed (reserved; not yet supported).
OrganisationCategory tagcategoryCross-reference to a [[categories]] id. A list belongs to at most one category. Profiles can subscribe by category instead of by list id.
Cross-reference (external)Profiles that subscribe[profiles.<id>].blocklistsDirect: a profile names this id.
Profiles that match by category[profiles.<id>].categoriesIndirect: a profile names a category id; every list tagged with that category is unioned in.

Things that look like blocklist dimensions but aren’t:

  • Cache directory and per-source byte cap are global ([lists].cache_dir, [lists].max_body_bytes) — not per row.
  • TLS pinning, sha256 integrity, license metadata — not on the schema today. Integrity beyond TLS is the design behind the parked trust = "signed" variant.
  • Per-list block response — that lives on the profile (block_response, blocked_ttl_secs), not on the list.

Field reference

FieldTypeRequiredDefaultPurpose
idstring, [a-z0-9-], ≤64 charsyesStable cross-reference.
display_namestring, non-emptyyesOperator-facing label.
urlstring, http(s)://…yesSource URL. Runtime enforces HTTPS-only and refuses private hosts.
formatenumnodomainsdomains | hosts | adguard.
update_interval_hoursu32, > 0no12Refresh cadence. Legacy alias refresh_interval_hours accepted with WARN.
max_entriesu64, > 0no5_000_000Per-list parser cap.
enabledboolnotrueActive flag.
auth_token_refstringnoKey into secrets.toml.
kindenumnoblockblock | allow. allow requires trust = "local".
trustenumnoremote-unsignedlocal | remote-unsigned. signed is reserved.
categorycategory idnoCross-reference to [[categories]].id.

#[serde(deny_unknown_fields)] is on — typos fail at config load with unknown field. Source: src/config/schema/blocklist.rs:97-141.

Categories live next to blocklists

Categories are operator-defined, not a closed enum, and they are pure organisation — no policy fields. A category is what lets a profile subscribe to “everything tagged family” instead of naming each list by id.

/etc/purge-warden/categories.d/default.toml
toml
[[categories]]
id           = "default"
display_name = "Default"
description  = "Lists shipped by the curated catalogue"

[[categories]]
id           = "family"
display_name = "Family"
description  = "Relaxed filters for adults' devices"
color        = "#3a86ff"

Resolution at config load is deterministic: [profiles.<id>].categories is walked alphabetically by category id, then within each category every matching [[blocklists]] entry is appended alphabetically by list id, then [profiles.<id>].blocklists is appended in config order, deduping along the way. A dangling category reference fails the load with CATEGORY_NOT_FOUND. Source: src/config/schema/category.rs:1-55, src/config/schema/validator.rs:811-904.

Direction and trust constraints

kindrequired trustWhat it doesWhen to reach for it
block (default)remote-unsigned or localDomains feed the deny set. The standard case.Curated catalogue lists, third-party adlists, your own deny file.
allowlocal onlyDomains feed the allow set. Wins over list-direction deny unless an admin $important deny outranks it.A long allowlist authored by you (school domains, work SaaS) that you’d rather express as a list than as 200 admin rules.

The validator refuses kind = "allow" paired with trust = "remote-unsigned" at load with ALLOW_LIST_REQUIRES_LOCAL_TRUST. The frozen reason — verbatim from src/config/schema/validator.rs:734-738 — is:

Blocklist '<id>' has kind=allow but trust='<got>'. Allow-direction lists
require trust=local. Use `warden blocklist import-local` to import a local file.
Allowlists today: prefer admin_rules
The schema accepts kind = "allow" and the validator enforces the trust pairing. The filter engine’s allow-mask path is wired but the upstream public docs flag end-to-end engine support as “in progress” until the next release. For stable allow semantics today, route operator-curated allows through [[admin_rules]] and reference them from [profiles.<id>].admin_rules. The kind = "allow" path is the right long-form choice for large curated allowlists; for one or two domains, an admin rule is simpler and proven.

Examples

A subscription to a curated purge.cc list:

/etc/purge-warden/blocklists.d/curated.toml
toml
[[blocklists]]
id           = "privacy-ads"
display_name = "Privacy: ads"
url          = "https://lists.purge.cc/privacy/ads.txt"
format       = "domains"

A third-party AdGuard-syntax list, refreshed every 6 hours:

/etc/purge-warden/blocklists.d/oisd.toml
toml
[[blocklists]]
id                    = "oisd-big"
display_name          = "OISD Big (AdGuard)"
url                   = "https://big.oisd.nl/"
format                = "adguard"
update_interval_hours = 6

A private corporate list with bearer auth — the secret stays out of the merged config:

/etc/purge-warden/secrets.toml
toml
# mode 0600 — the daemon refuses to start otherwise
corp-ads-token = "bearer-xxxxxxxxxxxxxxxx"
/etc/purge-warden/blocklists.d/corp.toml
toml
[[blocklists]]
id             = "corp-ads"
display_name   = "Corporate ads"
url            = "https://internal.example.com/blocklists/ads.txt"
format         = "domains"
auth_token_ref = "corp-ads-token"

A category-driven setup — profiles subscribe by category, individual list ids stay local to the file that declares them:

/etc/purge-warden/categories.d/_index.toml
toml
[[categories]]
id           = "default"
display_name = "Default"

[[categories]]
id           = "iot-strict"
display_name = "IoT strict"
description  = "Block everything except the minimum vendor cloud"
/etc/purge-warden/blocklists.d/iot.toml
toml
[[blocklists]]
id           = "iot-telemetry"
display_name = "IoT telemetry"
url          = "https://lists.example.org/iot.txt"
format       = "domains"
category     = "iot-strict"
[profiles.iot]
display_name   = "IoT"
categories     = ["iot-strict"]   # no explicit blocklists; the category resolves
block_response = "soa_nodata"

A locally-authored allowlist imported from a file you keep in version control:

sudo warden blocklist import-local ./school-domains.txt \
  --id           school-allow \
  --kind         allow \
  --category     family \
  --display-name "School-approved domains"

The import copies the file under <config-parent>/lists/<id>.txt, registers a [[blocklists]] row with trust = "local", auto-detects format, and assigns a synthetic https://imported.local/<id>.txt URL so the schema’s http(s):// requirement is satisfied. That synthetic URL is documented; if you read the generated TOML and wonder why a “local” import has an https:// URL, that’s why. Source: src/cli/commands/blocklists.rs:855-866.

Tip
Split blocklists across blocklists.d/<theme>.toml (one file per source bundle — curated.toml, corp.toml, iot.toml). The master config.toml pulls them via includes. CLI verbs accept --into <file> to route the write to the right fragment.

CLI

Every mutating subcommand triggers a hot reload (atomic config swap, no daemon restart). Read-only subcommands work without the daemon.

CommandWhat it does
warden blocklist listList configured [[blocklists]] rows with id, display name, format, refresh interval, kind, trust, category.
warden blocklist show <id>Full detail dump including runtime telemetry queried over IPC: entries parsed, parsed_skipped, last fetch timestamp, last outcome, delta vs previous refresh, plus a “Used by:” reverse lookup of profiles that subscribe.
warden blocklist add <id> --url <url> [--display-name --format --update-interval-hours --max-entries --enabled --auth-token-ref --into <file>]Add a new declared blocklist. Refuses if the id already exists. Cross-checks auth_token_ref against secrets.toml and warns (does not error) on a miss.
warden blocklist set <id> <field> <value>Mutate one field. Supported: display_name, url, format, update_interval_hours (legacy refresh_interval_hours accepted with WARN), max_entries, enabled, auth_token_ref.
warden blocklist set-category <list-id> <category-id> [--into <file>]Move a list between categories. Idempotent. Audited as blocklist.set_category.
warden blocklist set-kind <list-id> <block|allow> [--into <file>]Flip the direction. Reload validates the kind = allow ⇒ trust = local constraint; rejected attempts roll the file back and audit-log a Rejected event.
warden blocklist set-trust <list-id> <local|remote-unsigned> [--into <file>]Adjust provenance. signed is rejected at the parser with the parked-feature message.
warden blocklist remove <id> [--cascade] [--into <file>]Refuses if any profile still references the id. --cascade drops the id from every referencing profile in one transaction with a single shared reload.
warden blocklist import-local <path> --id <list-id> --kind <block|allow> --category <cat-id> [--display-name --into <file>]Copy a local file into the config tree, register a trust = "local" row, auto-detect format. The only path to a local-trust list.
warden category list / add <id> / rm <id> [--cascade] / rename <old> <new>Manage categories. Cascade-rm reassigns referencing blocklists to “Uncategorized” and drops the id from referencing profiles.
warden lists catalog [--scope <scope>]Browse the purge.cc catalogue (16 lists across privacy/*, security/*, content/*, services/*). Read-only, network-touching, daemon-independent.
warden updateForce a refresh now. Sends SIGHUP to the running daemon if it’s up, or downloads in the foreground to validate the config.

Common patterns:

# Browse the curated catalogue, then subscribe
warden lists catalog --scope privacy
sudo warden blocklist add privacy-tracking \
  --url "https://lists.purge.cc/privacy/tracking.txt" \
  --format domains \
  --display-name "Privacy: tracking"

# Bind a list into the kids profile (uses the profiles CLI surface)
sudo warden profile blocklists kids add privacy-tracking

# Move a list to a category and force a refresh now
sudo warden blocklist set-category privacy-tracking family
sudo warden update

# Remove a list cleanly, dropping it from every profile that referenced it
sudo warden blocklist remove privacy-tracking --cascade

After every hot-reload-aware mutation, exactly one of four messages is printed — the same set used by every CLI write path:

  • daemon reloaded — change is live
  • daemon not running — change will take effect on next start
  • note: change landed on disk but no admin token is available to request a daemon reload. Run warden token generate or restart the daemon to activate.
  • warning: change landed on disk but the daemon rejected the reload (<msg>). Check journalctl -u purge-warden and consider systemctl restart purge-warden.

There is also a legacy warden lists add <slug> that mutates the global [lists].sources table rather than [[blocklists]]. It does not hot-reload — it tells you to run warden update or kill -HUP the daemon. Prefer warden blocklist add for new work; the legacy verb is kept for the curated catalogue’s quick-subscribe slug syntax. Source: src/cli/mod.rs:743-849, src/cli/commands/blocklists.rs.

TUI

The Lists tab is reachable with 7 from the top tab bar. It’s a category-grouped table — rows are blocklists, separator headers carry the category display name and aggregate entry counts.

ElementWhat it showsUseful keys
Rowid, display name, entry count, status, last update, profiles using itj / k / / scroll (skips category headers); Enter toggle the per-row drill-down (full skipped-line samples up to 8, fetch errors, delta-pct vs previous refresh)
Profile assignment modalCheckbox per profile; select multiple, commit oncep open, Space toggle, Enter commit (single shared reload)
Category modalsCreate category / move focused listc create, m move, k toggle BLOCK ↔ ALLOW
RefreshForce a re-download of the focused listr

Stats poll over IPC every 30 seconds; the daemon also pushes an IpcNotification::ListStatsUpdated after each refresh cycle so the table reflects success or failure within seconds.

What only the CLI can do today: add a row, set arbitrary fields, remove, set-trust, import-local, category rename / rm --cascade. The TUI’s S50 modals add set-category, set-kind, and category-create. Source: src/tui/tabs/lists.rs.

REST API

MethodPathWhat
GET/api/blocklists/{id}/statsPer-list runtime telemetry: entries, parsed_ok, parsed_skipped, fetched_at, last_outcome, delta_pct_vs_prev. Resolves id by exact source string, then by [[blocklists]].id, then case-insensitive substring.
GET/api/listsThe legacy [lists].sources array as JSON.
POST/api/lists/addAppend a slug or URL to [lists].sources. Triggers a reload.
DELETE/api/lists/removeSymmetric. Triggers a reload.
POST/api/updateTrigger a refresh.

There is no REST CRUD for [[blocklists]] rows — no POST /api/blocklists, no PATCH, no DELETE against the v1 entity. The REST surface for the v1 model is read-only telemetry; declarative authoring runs through CLI / TOML edit / TUI (where supported). All /api/* routes are gated by Bearer token (Authorization: Bearer ps_<64hex>), constant-time-verified, with a 10-failure / 5-minute lockout. Source: src/api/handlers.rs:623-852.

IPC

The Unix socket exposes IpcCommand::BlocklistStats { source_id: Option<String> } as a read-only, no-token-required verb — the TUI uses it for its 30-second poll. Mutations go through the standard IpcCommand::Reload { token } path that every CLI write triggers. Source: src/ipc/protocol.rs:240-260, 549-608.

Decision precedence

A blocklist contributes to the filter only after a profile is chosen. The five-level chain that picks the profile is documented under profiles — a query first matches a device, group, subnet, schedule, or default; then the chosen profile’s lists are evaluated.

Inside the chosen profile

A single subdomain walk over the lowercased query name probes every enabled set per dot-position. The first decision wins, in this order:

  1. block_all short-circuit. If the profile sets block_all = true: deny unless an admin allow rule or local_records covers the domain.
  2. Admin rules in 4-tier priority — $important allow > $important deny > normal allow > normal deny. $important allow short-circuits the rest.
  3. Profile allow set (allow_domains HashSet, contributed by the profile’s admin allow rules and any kind = "allow" lists’ synonyms): forward.
  4. Profile deny set (deny_domains HashSet, contributed by admin deny rules): block.
  5. Tier 1 list bitmask — for each suffix probed, the list bitmask is consulted: an allow_mask bit (from a kind = "allow" list) and a block_mask bit (from any kind = "block" list) accumulate; allow-direction wins ties at this tier unless an upstream admin $important deny outranked it.
  6. Default forward — anything that didn’t match goes upstream.

The walk is unified — pre-S50 was three independent suffix scans; the post-S50 path probes every set in one pass. Source: src/filter/engine.rs:240-443.

Memory model: bitmask collapse

Every distinct domain across every subscribed list is stored once in a HashMap<CompactString, DomainMasks { allow_mask: u64, block_mask: u64 }>. When list A and list C both contain ads.com, the entry is ads.com → block_mask = 0b101. Subscribe to two lists that overlap 90% and the merged filter uses roughly 110% of one list’s memory — not 200%. The bitmask is 64 bits wide, so the engine supports up to 64 distinct list sources per profile. Source: src/lists/parser.rs:106-281, DOCUMENTATION.md:437-449.

List allow vs admin allow

A kind = "allow" list contributes to the allow path at Tier 1 — after admin rules settle. So:

  • An admin $important deny still wins over an allow-list match (priority 2 beats Tier 1).
  • A profile-level deny_domains HashSet hit (admin normal deny, priority 0) still wins over an allow-list match by design — the operator’s curated deny rules are sovereign over external allow contributions.
  • block_all = true is not pierced by an allow-direction list — only admin allows can punch through block_all.

Source: src/filter/engine.rs:280-443, plus the W1.2 invariant pinned by tests::w1_2_admin_important_deny_overrides_allow_list.

Security notes

External lists are sandboxed

The AdGuard parser strips and counts every syntax token Warden reserves for admin rules. From src/lists/parser.rs:228-269, when a remote list is parsed in format = "adguard":

  • @@… allow rules → skipped, counted in parsed_skipped.
  • $important modifier (anywhere in the modifier list) → skipped, counted.
  • /regex/ rules → skipped, counted.
  • Wildcard * rules → skipped, counted.

Only ||domain^ and ||domain^$<modifiers-without-important> lines extract a domain. A remote list cannot inject @@, $important, regex, or wildcards even if its content claims AdGuard format.

The parsed_skipped counter is the operator’s audit lever: it surfaces in warden blocklist show <id> and in the TUI drill-down (up to 32 sample lines). A list source that suddenly starts emitting skipped lines is a supply-chain canary — investigate before the next refresh.

The domains and hosts formats have no syntax surface to sandbox; lines are validated only against is_valid_domain and skipped if malformed. Source: src/lists/parser.rs:330-348.

TLS and SSRF

Every fetch goes through reqwest with default TLS verification on. Plus the runtime SSRF guard in validate_list_url (src/lists/http_client.rs:62-105) refuses, at every redirect hop:

  • Any non-HTTPS scheme.
  • Loopback / RFC1918 / link-local / CGNAT / multicast / ULA / IPv6 link-local hosts.
  • More than 3 redirect hops.

Schema-level the field accepts http(s)://, but the runtime tightens that to HTTPS-only. The synthetic https://imported.local/<id>.txt URL used by warden blocklist import-local short-circuits this path because the file is read from disk, not fetched.

Refresh failures keep stale content

A failed download — DNS error, HTTP 404, body too large, redirect loop — does not blank the list. The previously cached body keeps serving and last_outcome is recorded as Failed { reason } in the registry, surfaced via IPC and warden blocklist show. The operator sees red rows in the TUI Lists tab; queries in flight see no behaviour change.

If no cache exists yet (first refresh of a brand-new row failed), the source contributes nothing for this load. A tracing::error! is logged. Source: src/lists/manager.rs:464-501, 478-501.

Audit log

Mutations of blocklists and categories are recorded as tracing::info!(target = "audit", action = "...") lines:

  • blocklists.remove
  • blocklist.set_category / blocklist.set_kind / blocklist.set_trust
  • blocklist.import_local
  • profile.blocklists.add / profile.blocklists.remove / profile.blocklists.cascade_remove / profile.blocklists.cascade_skip
  • category.add / category.remove / category.rename

Refresh activity is logged but not as audit records — it goes through tracing::info! / warn! / error! in the lists subsystem. Treat the audit log as the operator-mutation trail; treat journald lists logs as the network-activity trail.

warden audit tail [-n N] prints recent audit lines as tab-aligned rows: timestamp, event tag, uid, ok / rejected, detail.

Troubleshooting

A reload fails with profile "<id>" references blocklist "<id>" which is not defined. The profile names a list id that no [[blocklists]] row provides. Either typo, or the list row was hand-deleted. The validator refuses the reload and the daemon stays on the previous good config. Run warden config lint for a list of every dangling cross-reference, or grep:

grep -RnE 'blocklists\s*=' /etc/purge-warden

A reload fails with Blocklist '<id>' has kind=allow but trust='remote-unsigned'. Allow-direction lists must be locally trusted. Either flip trust to local on a row that’s actually a local file, or — more commonly — re-register it via warden blocklist import-local, which sets both fields atomically.

A reload fails with trust=signed is not supported in this version. The signed variant is reserved for a future integrity-attested flow. For now, use local for trusted allowlists and remote-unsigned for everything fetched over HTTPS.

warden blocklist add warns about a missing auth_token_ref. The CLI cross-checks secrets.toml at write time and emits a stderr warning if the key isn’t there. The warning is not an error — the row still lands on disk and the list will fetch anonymously until you add the secret. Add it with the editor of your choice (secrets.toml must be mode 0600) and run warden update to refresh.

warden blocklist remove <id> is refused. Some profile still references the id. The error names them. Either reassign first, or pass --cascade to drop the reference from every profile in one transaction.

A list shows parsed_skipped > 0 in warden blocklist show. That’s by design for AdGuard-format remote lists — Warden strips @@, $important, /regex/, and wildcards. The first 8 samples are visible in the TUI drill-down, and up to 32 are kept in the registry. Investigate if the count grows unexpectedly: a list that wasn’t shipping fancy syntax yesterday and is today is worth a look.

A list refresh fails with HTTP 404 or TooLarge. Stale content keeps serving and the row goes red in the TUI. If the URL has moved, fix it with warden blocklist set <id> url <new-url>. If the list grew past the body cap, raise [lists].max_body_bytes (global, default 200 MB) — not the per-row max_entries, which gates parsing not download.

The same list is declared twice. The validator rejects duplicate ids at load with DuplicateId. Pick one, hand-delete the other.

The list grows unbounded. The parser truncates at max_entries (default 5_000_000) with a WARN. The engine bitmask supports up to 64 distinct list sources per profile — beyond that, additional subscriptions are not representable. If you’re approaching that limit, fold sources into categories so each profile names a handful of categories instead of dozens of list ids.

warden blocklist import-local writes a TOML row with url = "https://imported.local/<id>.txt". Documented quirk: the validator requires http(s)://, so the import synthesises a placeholder URL. The actual fetch reads the copied file under <config-parent>/lists/<id>.txt. Source: src/cli/commands/blocklists.rs:855-866.

Reusing an id that was retired recently. Warden quarantines retired ids for 90 days to prevent stale references from quietly re-binding. The error names the original retirement timestamp. Wait it out, or pick a new id.

For more, see troubleshooting.

See also

  • Profiles — how a profile names the lists and categories to enforce.
  • Admin rules — the only place @@, $important, regex, and wildcards live.
  • Importing lists — operational guide to bringing many lists in at once.
  • Schedules — time-based profile swaps that change which lists apply when.
  • Server globals — the [lists] block (cache directory, body cap, default refresh interval).
  • TOML reference — every field, every constraint.
  • CLI reference — the full warden blocklist … and warden category … surfaces.