Skip to content

Fast path: lists

Every query takes the same short path through in-memory lists. No model runs here.

  1. Decoys first. If the name matches a decoy, Ward answers locally, logs an alert, and stops. An allowlist entry cannot hide a decoy hit.
  2. Allowlist. A match means the query is forwarded upstream, even if a blocklist also matches. This is how you fix a false positive.
  3. Blocklist. A match means Ward answers locally with the configured block response and logs which list matched.
  4. Forward. Everything else goes to your DNS-over-TLS upstreams. If the behavioral detector is on, it scores the name in the background. That never delays or changes the answer.

Entries match the name itself and everything beneath it. Blocking example.com also blocks ads.example.com. Lists are local files read when ward serve starts. Ward understands hosts-file lines whose address is 0.0.0.0 or 127.0.0.1 (0.0.0.0 ads.example.com), AdAway’s short 0 ads.example.com form, and AdGuard ||ads.example.com^ rules, so most public blocklists work unchanged. Other line shapes (plain hostnames, other addresses, AdGuard @@ exceptions or $ modifiers) are skipped and counted when the list loads. Decoy lists are different: they take plain hostnames and match exactly, not by suffix.

block_response.mode decides what a blocked client sees:

Mode A query AAAA query Other types
address (default) block_response.a (default 0.0.0.0) block_response.aaaa (default ::) NXDOMAIN
nxdomain NXDOMAIN NXDOMAIN NXDOMAIN

This is a DNS-layer answer, not a sinkhole server. The blocked program tries to connect to the address it was given and fails.

Every block names its rule. The log line carries the list id from your config, the entry that matched and the client that asked, and the local dashboard’s recent-decisions table shows the matched entry. There are no silent drops.