Query log
Warden logs one line per DNS query it handles — including why it answered the way it did.
What each outcome means
The result field on every query-log line isn’t the DNS RCODE — it’s Warden’s own record of which code path answered. Several outcomes can share one RCODE, and one outcome (BLOCKED) can produce four different RCODEs depending on config.
| Outcome | Fires when | Wire RCODE | What to do |
|---|---|---|---|
ALLOWED | logged the instant the upstream fetch returns, before Warden inspects the answer | not fixed — depends on what upstream returned | means “forwarding attempted”, not “clean” — see the note below |
BLOCKED | profile/admin-rule/blocklist match, CNAME-chain block, or a post-fetch IP-blocklist re-check | depends on the profile’s block_response: zero→NoError+0.0.0.0, nxdomain→NXDOMAIN, refused→REFUSED, soa_nodata→NoError+SOA | expected if policy is working as intended; check the line’s rule field, and the profile’s list policy, if it shouldn’t have blocked |
REFUSED | [anti_bypass] pre-query refusal, no profile matched the client (L5 miss on the resolver ladder), or DNS-tunneling rate exceeded | always REFUSED | unexpected for a known client → check its device/group/subnet mapping, or whether default_profile is set |
RRL_DROP | rate-limit hard drop | none at all — the packet is never sent, a true silent drop | frequent for a legit client → check [security] rate-limit thresholds |
RRL_SLIP | rate-limit anti-spoof slip | NoError, TC=1 (forces the client to retry over TCP) | normal RRL behaviour, not an error |
LOCAL | a per-profile or global local_records hit, or NODATA synthesis for a locally-authoritative name | NoError | expected for local DNS config |
CACHED | cache hit | passthrough of whatever RCODE the cached entry stored | nothing to do |
HINFO | RFC 8482 — a QTYPE=ANY query was intercepted | NoError | expected hardening, not an error |
STALE | the upstream fetch failed and Warden served an expired cache entry instead | passthrough of the stale entry’s RCODE | recurring → check upstream health and [[forwarding]] |
A single lookup can produce two log lines. ALLOWED is stamped the moment the upstream fetch returns — before Warden checks the answer for a CNAME-chain or IP-blocklist hit. If that check fires, an immediate BLOCKED line follows for the same query. ALLOWED means “forwarding was attempted,” not “traffic passed through clean.”
TUI colour coding. The query-log tab buckets these nine values into three severities for the RESULT cell’s colour: red for BLOCKED (and any CNAME-chain block, even on a line whose result string isn’t literally BLOCKED), amber for STALE and REFUSED, grey for everything else — including RRL_DROP and RRL_SLIP, which the colouring doesn’t distinguish from a clean serve even though RRL_DROP is a genuine silent drop.
Names in the file. Warden writes each name as ASCII. An internationalised name stays in its xn-- (punycode) form, so münchen.de is logged as xn--mnchen-3ya.de. A control character, a space or a non-ASCII byte in a name is escaped as a backslash and three octal digits (for example \033 for the escape character, which dig writes in decimal as \027); other punctuation gets a backslash in front. The query log tab in the dashboard also replaces any such character with � before drawing it.
This page is about the file. To read the same lines inside Warden — filtered by domain, client or time, with one key to hold an allow or block rule for the name you are looking at — see the query log tab in the dashboard.
How much the log keeps
The log starts a new file every day at UTC midnight and keeps the past week by default. A size cap protects the disk on a busy day, at the cost of that day’s oldest lines.
The log is query.log in /var/lib/purge-warden (see File paths). Three settings in [tracking] decide how much of it stays on disk:
| Setting | Default | What it does |
|---|---|---|
retention_days | 7 | How many past days to keep, from 1 to 365. |
query_log_max_size_mb | 100 | The size in MiB at which Warden moves the day’s current file aside and starts a new one. |
query_log_max_files | 7 | How many moved-aside files one day can have. 0 turns the size cap off. |
Over one day, this is what happens:
- Warden appends each line to
query.log. - When
query.logreachesquery_log_max_size_mb, Warden renames it toquery.log.YYYY-MM-DD.1(then.2,.3and so on) and starts a newquery.log. - When the day already has
query_log_max_filesof these files, Warden deletes the oldest one,.1, renumbers the others, and moves the current file to the last number. Nothing records this deletion: no log line, no counter. Theqlog dropsline ofwarden statuscounts queries and batches that Warden could not write, not files that the cap removed. - At UTC midnight,
query.logbecomesquery.log.YYYY-MM-DD, and Warden deletes the files whose date is more thanretention_daysdays in the past.
So one day keeps at most query_log_max_files + 1 files of about query_log_max_size_mb each: 8 × 100 MiB = 800 MiB with the defaults. A busier day keeps only its last 800 MiB. On disk, the log holds the past retention_days days plus today, so with the defaults it uses at most (7 + 1) × 800 MiB = 6,400 MiB, about 6.25 GiB. Warden checks the size before it writes each batch of lines, so a file can end a little over the cap.
Work out what your network needs
How many queries fit in 800 MiB depends on your network: long names and CNAME chains make longer lines. Measure your own log after a normal day:
D=$(date -u -d yesterday +%F)
sudo sh -c "ls -l /var/lib/purge-warden/query.log.$D*"
sudo sh -c "cat /var/lib/purge-warden/query.log.$D* | wc -lc"
sudo head -n 1 /var/lib/purge-warden/query.log.$D.1The first command lists yesterday’s files. If there are files that end in .1, .2 and so on, the day reached the size cap. The second command prints yesterday’s number of lines (one per logged query) and their size in bytes. Divide the bytes by the lines to get your average line size, then divide 800 MiB (838,860,800 bytes) by it: that is the number of queries one day can keep with the defaults.
If the day has query_log_max_files files with a number at the end, it may have lost its oldest lines. The third command prints the first line of .1: if its timestamp (in UTC) is well after 00:00, the lines before it were deleted.
Keep more, or write less
- To keep more of each day, raise
query_log_max_size_mborquery_log_max_files. The disk use grows with them: plan for(retention_days + 1) × (query_log_max_files + 1) × query_log_max_size_mbMiB. - To write less, set
log_mode = "blocked_only"to log only blocked queries, orlog_mode = { sampled = { allowed_rate = 0.1 } }to log every blocked query and one in ten of the others. - To keep fewer days, lower
retention_days. The older files go at the next UTC midnight. query_log_max_files = 0removes the cap within a day: nothing is deleted before midnight, and a very busy day can fill the disk.
sudo warden config edit # change the settings in [tracking]
sudo systemctl restart purge-wardenA change to these settings needs a restart. warden reload does not apply it: the journal shows reload requires daemon restart for runtime sections: tracking runtime, and the previous settings keep running. For the other limits of a busy network, see Host tuning.
Verify it in the code
Every claim on this page is one function away in the source. Where to look:
src/dnshandler.rs → record_outcome()src/trackingengine.rs → log_query_event()src/dnshandler.rs → dispatch_cname_block()src/dnshandler.rs → dispatch_ip_block()src/dnshandler.rs → send_refused()TC=1)src/dnshandler.rs → send_truncated()HINFO)src/dnshandler.rs → send_rfc8482()src/tui/tabsquery_log.rs → result_severity()src/trackingquery_log.rs → rotate_on_size_backstop()src/trackingquery_log.rs → rotate_daily()src/trackingquery_log.rs → prune_old_files()