Skip to content

Quickstart

Ward is a single Go binary. You need Go 1.25 or newer and dig (preinstalled on macOS; dnsutils or bind-utils on Linux).

Terminal window
go install protocolward.ai/ward/cmd/ward@latest
ward version

ward version prints ward dev for source installs. If your shell cannot find ward, add $(go env GOPATH)/bin to your PATH.

Make a working directory with one blocklist and one decoy:

Terminal window
mkdir -p ~/ward && cd ~/ward
printf '0.0.0.0 doubleclick.net\n0.0.0.0 google-analytics.com\n' > blocklist.txt
printf 'nas-backup.home.arpa\n' > decoys.txt

Save this as ~/ward/ward.yaml:

listen: "127.0.0.1:5354"
log_level: "info"
upstreams:
- address: "9.9.9.9:853"
server_name: "dns.quad9.net"
blocklists:
- id: "starter"
path: "./blocklist.txt"
decoys:
- id: "home-decoys"
path: "./decoys.txt"
model:
builtin: lexical
  • listen has no default; you always choose the bind address. Port 5354 avoids 5353, which macOS uses for mDNS.
  • Upstreams are DNS-over-TLS. server_name is the name on the upstream’s TLS certificate.
  • List paths are relative to the directory you run ward from.
  • model: builtin: lexical turns on the on-device behavioral detector.

Every key is documented in the configuration reference.

Terminal window
ward doctor --config ./ward.yaml
ward serve --config ./ward.yaml

ward doctor validates the file and checks that the bind address is free and each upstream accepts a TCP connection (the TLS handshake is not checked yet). Every error it prints names the fix.

In a second terminal:

Terminal window
dig @127.0.0.1 -p 5354 github.com +short
dig @127.0.0.1 -p 5354 doubleclick.net +short
dig @127.0.0.1 -p 5354 nas-backup.home.arpa +short
dig @127.0.0.1 -p 5354 xjw7qk2vzr9tplm4.com +short
Query What happens
github.com Not on any list. Forwarded upstream and answered with real addresses.
doubleclick.net Blocklist hit. Answered locally with 0.0.0.0; the log names list starter.
nas-backup.home.arpa Decoy hit. Answered locally and never forwarded. Ward logs an alert naming the client that asked.
xjw7qk2vzr9tplm4.com Not on any list, so it is forwarded as normal. The detector scores the name in the background and records a flag with its reasons. It is not blocked.

Open the local dashboard at http://127.0.0.1:18987 to see recent decisions and flags. It only binds to loopback.

  • Pull in a maintained list. Ward reads hosts-file lines that start with 0.0.0.0, 127.0.0.1 or AdAway’s short 0, and AdGuard ||example.com^ rules, so most public blocklists work unchanged. Plain one-name-per-line files are not accepted for blocklists or allowlists yet; Ward skips those lines and reports how many it skipped when it loads the list.
  • Add an allowlist for anything a list gets wrong. Allowlist hits beat blocklist hits.
  • Bind :53 on a machine your network can reach, and point your router’s DHCP DNS setting at it. Binding port 53 needs elevated privileges (for example CAP_NET_BIND_SERVICE on Linux).
  • Read how the detector works before acting on flags.