Configuration reference
Ward reads one YAML file, validates every field at startup, and refuses to run on a bad value. The error names the field and the fix:
config validation error: field "upstreams[0].server_name": must not be empty — set to the TLS SNI name of the upstream, e.g. "dns.quad9.net"Unknown keys are ignored, so check spelling: a misspelled blocklists runs Ward with no blocklist.
Where Ward looks
Section titled “Where Ward looks”The first match wins:
--config <path>$XDG_CONFIG_HOME/protocol-ward/ward.yaml(~/.config/protocol-ward/ward.yamlwhenXDG_CONFIG_HOMEis unset)/etc/protocolward/ward.yaml
If you pass --config, Ward uses that path and does not fall back to the others.
Complete example
Section titled “Complete example”listen: "127.0.0.1:5354"log_level: "info"
upstreams: - address: "9.9.9.9:853" server_name: "dns.quad9.net" - address: "1.1.1.1:853" server_name: "one.one.one.one"
blocklists: - id: "trackers" path: "./blocklists/trackers.txt"
allowlists: - id: "my-overrides" path: "./allowlists/overrides.txt"
decoys: - id: "home-decoys" path: "./decoys.txt"
block_response: mode: "address" a: "0.0.0.0" aaaa: "::"
timeouts: dial: "3s" query: "2s" shutdown: "5s"
model: builtin: lexicallisten
Section titled “listen”Required. A string of the form host:port, with the port between 1 and 65535. There is no default; the bind address is always your choice. Use 127.0.0.1:5354 to try Ward locally (5353 belongs to mDNS on macOS) and :53 to serve a network, which needs privileges to bind.
log_level
Section titled “log_level”One of debug, info, warn, error. Default info.
upstreams
Section titled “upstreams”Required, at least one. DNS-over-TLS resolvers that Ward forwards to.
| Key | Type | Notes |
|---|---|---|
address |
host:port |
Required. DoT is usually port 853. |
server_name |
string | Required. The name on the upstream’s TLS certificate (SNI). |
ca_bundle |
path | Optional. A PEM file of CA certificates that pins trust for this upstream. Omit it to use system roots. Ward reads and checks the file at startup. |
blocklists
Section titled “blocklists”Optional. Omit the key to run Ward as a plain forwarder. If the key is present it must have at least one entry.
| Key | Type | Notes |
|---|---|---|
id |
string | Required and unique. Appears in every block log line. |
path |
path | Required. A local file of hosts-file lines (0.0.0.0, 127.0.0.1 or AdAway 0 prefix) or AdGuard ||host^ rules. Plain one-name-per-line files are not accepted; skipped lines are counted at load. Relative paths resolve against the directory you run ward from. |
allowlists
Section titled “allowlists”Optional, with the same shape and rules as blocklists. An allowlist hit beats a blocklist hit.
decoys
Section titled “decoys”Optional, with the same shape and rules as blocklists. Files hold hosts-file lines or plain hostnames, and each entry matches that exact name only, not names beneath it. Decoys are checked before everything else, are never forwarded, and are never included in ward config export. See Decoys.
block_response
Section titled “block_response”Optional. Shapes the answer to a blocked query.
| Key | Default | Notes |
|---|---|---|
mode |
address |
address or nxdomain. |
a |
0.0.0.0 |
IPv4 returned for A queries in address mode. Must be omitted in nxdomain mode. |
aaaa |
:: |
IPv6 returned for AAAA queries in address mode. Must be a real IPv6 address, not IPv4-mapped. Must be omitted in nxdomain mode. |
In address mode, query types other than A and AAAA get NXDOMAIN. In nxdomain mode every type does.
timeouts
Section titled “timeouts”Durations in Go syntax (500ms, 3s). Every value must be greater than zero.
| Key | Default | Meaning |
|---|---|---|
dial |
3s |
TCP connect plus TLS handshake to an upstream. |
query |
2s |
End-to-end budget for one forwarded query. |
shutdown |
5s |
How long Ward drains in-flight queries on SIGTERM. |
Optional. Omit it to run without a behavioral detector. When model is present, set exactly one of builtin or command.
| Key | Type | Notes |
|---|---|---|
builtin |
string | lexical runs Ward’s built-in hostname detector in-process. See Behavioral detector. |
command |
list of strings | argv for an external classifier that Ward runs as a separate sibling process and talks to over newline-delimited JSON on stdio. |
env |
list of KEY=value |
Environment for command only; setting it together with builtin is a validation error. Omit it to inherit Ward’s environment; [] starts the classifier with an empty one. |
Verdicts from either kind are recorded as flags and are never enforced in the beta.
Not in the file
Section titled “Not in the file”- Dashboard address. The dashboard serves on
127.0.0.1:18987. Override it withward serve --dash-listen-addr. Only loopback addresses are accepted, because the dashboard shows your DNS activity.
Commands
Section titled “Commands”| Command | What it does |
|---|---|
ward serve |
Run the resolver and dashboard. |
ward doctor |
Validate the config, check that the bind address is free, and check TCP reachability of each upstream. |
ward config export |
Print the validated config as YAML, with decoys left out. The model stanza is not exported either, so re-add it if you restore from an export. ca_bundle paths are printed as written. |
ward update verify <dir> |
Verify a signed update bundle on disk. No network access. |
ward eval |
Score a labelled hostname suite against the configured classifier and report accuracy. |
ward version |
Print the version. |
Every command that reads config accepts --config <path>.