dnswizdocs

Query firewall

Per-zone (or workspace-default) rules that refuse DNS queries at the authoritative server. Useful to block ANY/AXFR amplification, lock an internal zone to corp ranges, or stop a single resolver from flooding you.

A refused query gets DNS REFUSED rcode, same as querying a server that isn’t authoritative for the zone. Refusals show up in the Refused-query forensics panel within seconds.

Where it lives

Config shape

The firewall config is a JSON object. Every field is optional:

{
  "allow_sources":   ["10.0.0.0/8", "203.0.113.5"],
  "deny_sources":    ["198.51.100.0/24"],
  "allow_countries": ["DE", "FR", "US"],
  "deny_countries":  ["RU", "KP"],
  "refuse_qtypes":   ["ANY", "AXFR"],
  "rate_limit_qps":  100
}

An empty list means “no rule,” not “deny all.”

Evaluation order

For each query the engine evaluates rules in this order; the first matching rule decides:

  1. IP deny: source matches deny_sources. → REFUSED (reason: ip-denied).
  2. IP allow: allow_sources is non-empty and source doesn’t match any entry. → REFUSED (reason: ip-not-allowed).
  3. Country deny: source country (via GeoIP) is in deny_countries. → REFUSED (country-denied).
  4. Country allow: allow_countries non-empty and source country isn’t in it. → REFUSED (country-not-allowed).
  5. Qtype refuse: query type matches refuse_qtypes. → REFUSED (qtype-refused).
  6. Rate limit: source IP has exceeded rate_limit_qps over the sliding 1-second window. → REFUSED (rate-limited).

Empty lists skip their step. rate_limit_qps: 0 disables rate limiting (any qps allowed).

ECS handling

When EDNS Client Subnet is present and looks plausible, the engine uses the ECS subnet as the source for CIDR + country rules. ECS makes DNS-level geo less coarse, but still coarse. Don’t depend on this for security-critical decisions; it’s correlation, not identity.

When ECS is absent or implausible (0.0.0.0/0, RFC-1918 from public resolvers), the engine falls back to the resolver’s source IP.

Common configs

Block amplification basics

{ "refuse_qtypes": ["ANY", "AXFR"] }

The classic “you have a public DNS server” hygiene minimum.

Lock an internal zone to corp ranges

{ "allow_sources": ["10.0.0.0/8", "192.168.0.0/16"] }

Combine with split-horizon by serving the zone only on internal resolvers.

Sanction-list geo block

{ "deny_countries": ["RU", "KP", "IR", "SY"] }

Needs a GeoIP database loaded on the edge, without one, all queries are treated as country=unknown (no match).

Rate-limit a chatty resolver

{ "rate_limit_qps": 200 }

Per source IP, not per zone. A misbehaving Cloudflare resolver gets its own bucket; one bad actor doesn’t burn budget for everyone else behind the same edge.

Inheritance

Each zone resolves to one effective policy:

A per-zone config replaces the default outright, it isn’t merged. So a zone can tighten the default with its own rules, or opt out entirely by enabling an empty config (no rules).

Per-rule audit

Every firewall change lands in Settings → Audit log with who made it, what changed (before and after), and when. Useful for the “who turned this on at 3 AM” question.