Files
jarl/docs/blocking.md
T
adamandClaude Opus 5.5 76aaaf3ef5 Different blocking for different devices: steps and client groups
Client groups put devices (addresses, networks, or ACLs of them) at a
blocking step: off (only your rules), normal (the default), or steps
added above it, each including the lists of the steps below. Lists
start at a step. In each view, a gate policy zone in front of a step's
lists lets the devices below that step through (rpz-client-ip triggers
with rpz-passthru), so every list is still loaded once. The IPv6 part
of a gate leaves the IPv4-mapped range out: named matches IPv4 clients
as ::ffff:a.b.c.d.

Web UI: a Devices panel on the Blocking page and a step on each list.
CLI: jarl block step, jarl block group, jarl blocklist --step. API:
/blocking/steps, /blocking/groups. Database schema 8.

Also: copying the state kept empty lists as empty (not nil), so
reverting a changeset no longer adds restore operations for unchanged
blocklists, rules and views.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 21:33:16 +01:00

15 KiB

Blocking

Jarl can make named answer "this name doesn't exist" (NXDOMAIN) for ads, trackers and malware, like Pi-hole does, for every device that uses your server to resolve names. It has two parts:

  • Blocklists: lists of domains published by others, which jarld downloads and keeps up to date.
  • Your rules: domains you block or allow yourself. They take precedence over every list.

It uses BIND's own mechanism for this, response policy zones (RPZ), so named does the blocking itself; jarld only prepares the lists.

Quick start

In the web UI, open Blocking, pick one of the well-known lists, and click Add list. Or with the CLI:

jarl blocklist add hagezi-multi https://raw.githubusercontent.com/hagezi/dns-blocklists/main/adblock/multi.txt
jarl blocklist list                     # "ok" once downloaded, usually within seconds
jarl block check ads.doubleclick.net    # blocked? by what?
jarl block allow cdn.example.com        # something you need was blocked

Like every change in Jarl, adding a list or rule can be staged and reviewed first (the web UI does this by default: apply it under Changes).

Where blocking applies

Blocking changes the answers named gives when it resolves names for a client (recursion). It doesn't change what the internet sees of your own zones: other resolvers ask your server without recursion.

Views. A list or rule applies to the views you choose, or by default to every view that offers recursion. With the usual split (an internal view that resolves for your network, an external view that only answers for your zones) that is just internal. A view that doesn't offer recursion gets nothing, and naming one gives a warning. Without views, blocking applies to the whole server.

What it can't do:

  • Devices that don't use your server for DNS aren't affected. That includes browsers using their own DNS over HTTPS, and devices with a hard-coded resolver (8.8.8.8). A firewall rule that sends port 53 to your server, and your network's DNS settings, are the usual answers.
  • A device that validates DNSSEC itself sees a blocked name as a validation failure rather than "doesn't exist". It's still blocked.
  • A list can contain names in your own zones. They are blocked like any other; allow them with a rule if that's not what you want.

Your rules

A rule blocks or allows a domain and every name below it, or with --exact (web UI: "only this exact name") just the domain itself.

jarl block add doubleclick.net                       # doubleclick.net and everything below it
jarl block add telemetry.example.com --exact         # just that name
jarl block allow cdn.example.com --view internal     # an exception, in one view
jarl block list
jarl block delete doubleclick.net                    # or: --id ID

Precedence: your rules come before every blocklist, and among your rules the most specific one wins. With these rules:

Rule
block example.com and below
allow cdn.example.com exact

example.com and www.example.com are blocked, cdn.example.com is allowed, and img.cdn.example.com is blocked again (the allow rule was for the exact name). An allow rule beats a blocklist entry whatever the lists say.

There is one rule per domain and view; a second one is refused. A rule takes effect as soon as the change is applied.

Blocklists

Formats. Lists are read line by line; one list may mix formats:

Line Blocks
||ads.example^ (Adblock) the name and every name below it
*.ads.example the name and every name below it
0.0.0.0 ads.example (hosts file, any address) just the name
ads.example (plain domain) just the name

Comments (#, !), headers and blank lines are ignored. Lines that DNS can't act on are skipped and counted: Adblock exceptions (@@), cosmetic and URL rules, addresses, invalid names, and single labels (a broken list can't block com). Prefer lists in Adblock format: they say which entries cover the names below them.

Well-known lists (sizes in October 2026; they change):

List URL Domains Memory per view
Hagezi Light https://raw.githubusercontent.com/hagezi/dns-blocklists/main/adblock/light.txt 55,000 ~65 MB
Hagezi Multi Normal https://raw.githubusercontent.com/hagezi/dns-blocklists/main/adblock/multi.txt 159,000 ~190 MB
Hagezi Pro https://raw.githubusercontent.com/hagezi/dns-blocklists/main/adblock/pro.txt 195,000 ~235 MB
OISD Small https://small.oisd.nl/ 58,000 ~70 MB
OISD Big https://big.oisd.nl/ 241,000 ~290 MB
StevenBlack hosts https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts 73,000 ~45 MB

One good list is usually enough; lists overlap a lot. Hagezi Multi Normal is a balanced start; Light and OISD Small block less but almost never something you need.

Updates. jarld checks each list every blocking.refresh (a day by default) and downloads it only when the server says it changed. A new version is checked with named-checkzone and replaces the old one only when its content differs; named then reloads it. If a download or check fails, named keeps using the previous version, jarld tries again within the hour, and the dashboard shows the error. To update now:

jarl blocklist refresh              # every list; or: refresh hagezi-multi [--force]

Managing lists:

jarl blocklist update hagezi-multi --view internal     # --view auto: every view with recursion
jarl blocklist update hagezi-multi --url URL --name NEW-NAME
jarl blocklist disable hagezi-multi                    # stop using it, keep it (enable: back without a download)
jarl blocklist delete hagezi-multi

A list's state is ok (in use), waiting (not downloaded yet), error (the last update failed; the previous version stays in use), disabled, or idle (it applies to no view, so it does nothing).

Different blocking for different devices

Client groups give some devices less or more blocking than the rest, like Pi-hole's groups: no blocking for a work laptop, an extra list for the kids' tablets.

Steps decide which lists a device gets. Each step includes the lists of the steps below it, plus its own:

Step Gets
off (built in) only your rules
normal (built in) your rules and the normal lists; every device not in a group
strict (one you add) normal, plus the lists at step strict
very-strict (one you add) strict, plus the lists at step very-strict

Every list starts at a step (normal unless you choose one), and every group is at a step. Your rules apply at every step, off included.

jarl block step add strict
jarl blocklist update social-media --step strict
jarl block group add work 10.10.0.20 --step off
jarl block group add kids 10.10.0.50 10.10.0.51 2001:db8:1::/64 --step strict
jarl block group list

In the web UI, the Blocking page's Devices panel holds the steps and the groups, and each list has a step.

  • Members are addresses, networks (10.10.0.0/24), or ACLs made only of those (key, localnets and ! can't be used: blocking matches clients by address). A device is in one group: two groups with the same address are refused, but a more specific address in another group wins (10.10.0.0/24 in one group, 10.10.0.20 in another).
  • Steps only add. A group gets everything up to its step, so "kids get list A, guests get list B, and neither gets the other's" can't be done with steps. That needs a view per group, each with its own lists (and their memory).
  • Lists aren't copied per step: each is loaded once, and the steps cost a small zone each. Lists at a step no group reaches block nothing (Jarl warns, and shows them as "no effect").
  • named must see the device's own address. When a router hands out itself as DNS server and forwards to named, every query comes from the router, and groups can't tell devices apart. Devices also need fixed addresses (DHCP reservations); IPv6 privacy addresses change, so add a device's network, or rely on its IPv4 address.
  • jarl block check NAME (and the Blocking page) answers for normal devices, and says when a list of a higher step blocks the name.

Checking a name

jarl block check NAME, or the box at the top of the Blocking page, says for each view whether a name is blocked or allowed, and by which rule or list entry:

$ jarl block check img.ads.example.com
view internal:  blocked by blocklist hagezi-multi (entry *.ads.example.com)
view guest:     allowed by your rule 12 (entry *.ads.example.com)

Views without blocking aren't listed.

In the web UI a blocked name gets an Allow button, and the statistics' most blocked names link to this check.

Pausing

To see whether blocking is what breaks a site, pause it: every name is answered normally until the time is up (at most a day), then blocking resumes by itself, also if jarld was restarted in between. A pause can also last until someone resumes blocking (like Pi-hole's "indefinitely").

jarl block pause          # 10 minutes; or: jarl block pause 1h
jarl block pause indefinitely
jarl block resume         # end it now

In the web UI, Pause blocking is at the top of the Blocking page (with Until I resume among the lengths), and the dashboard says while blocking is paused. The lists stay loaded (the rules zone just lets everything through), so pausing and resuming take effect within a second. Pausing and resuming are recorded in the history like other changes.

Statistics

With query statistics on (including query_log), Jarl counts what blocking does: blocked queries over time (next to all queries) and as a share of them, which rules and lists blocked them, the most blocked names, and the clients with the most blocked queries (jarl stats summary, jarl stats top blocked, jarl stats top blocked-clients, jarl stats breakdown blocklist). Blocked answers are left out of the NXDOMAIN figures, which are about names that really don't exist.

The Blocking page shows the last 24 hours too: a Blocked (24h) column for each list, and one count for your rules together (named logs which policy zone matched, not which rule in it).

Without statistics named doesn't log what it blocks at all.

Memory and performance

Memory is the cost to watch. named keeps each list in memory, once per view it applies to: about 0.6 KB per policy record (an Adblock entry is two records: the name, and the names below it). The Blocking page and jarl blocklist list show the estimate per list. Hagezi Pro in one view took named from 25 MB to 260 MB in a test with BIND 9.20; while a list reloads, the old and new versions briefly coexist.

Answers aren't slowed: named looks blocked names up in memory, and answers them without asking the internet.

Updating a list (Hagezi Pro: download, parse, check) takes jarld about 2 seconds. While named loads the new version, the queries that the loading thread would have answered wait, up to half a second; this only happens when the list changed, at most once per blocking.refresh.

Other changes are unaffected: the lists' contents never go through Jarl's apply, so adding a record takes as long with a list as without.

Downloads are limited by blocking.max_download_mb and blocking.max_entries (configuration.md).

How it works

For each view (or the server, without views) Jarl writes a response-policy statement and the policy zones it lists, in this order:

response-policy {
	zone "rules.jarl.invalid" min-update-interval 0;              # your rules
	zone "hagezi-multi.blocklist.jarl.invalid" min-update-interval 0;
} qname-wait-recurse no break-dnssec yes;
  • Policy zones are named under .invalid, so they can't clash with real zones (zone names under jarl.invalid are refused). They allow no queries or transfers.
  • The rules zone of each view is a file next to your zone files (/var/lib/bind/jarl/VIEW/rules.jarl.invalid.rpz) and shows up in the changeset diff like any other.
  • A list's file is /var/cache/bind/jarl/blocklists/list-ID.rpz, shared by the views that use it. Until the first download it is an empty policy zone, so named always starts. Files of deleted lists are removed.
  • qname-wait-recurse no blocks without first resolving the real answer; break-dnssec yes also blocks for clients that ask for DNSSEC data; min-update-interval 0 applies a new version at once.
  • named looks names up in a policy zone like in any zone, so a wildcard doesn't apply below a name the zone also contains. Jarl writes the zones so that the most specific rule still wins.
  • A response-policy option of your own can't be combined with Jarl's blocking in the same view: Jarl refuses the change and says so.
  • Rewrites are logged to Jarl's response log when statistics read it, and not at all otherwise.
  • With client groups, a gate zone stands in front of the lists of each step: off.gate.jarl.invalid before the normal lists, STEP.gate.jarl.invalid before a step's. named checks the zones in order and the first match decides; a gate matches the devices below its step by address (rpz-client-ip triggers) with rpz-passthru, so they never reach the lists behind it. A gate is never logged, and its file is next to the rules zone (/var/lib/bind/jarl/VIEW/strict.gate.jarl.invalid.rpz). For "everyone except these devices", a gate lists the address ranges around them, at most a few hundred entries. named matches IPv4 clients as IPv4-mapped IPv6 addresses, so the IPv6 part leaves ::ffff:0:0/96 out.

Lists and rules need the blocking permission (permissions.md).

Troubleshooting

  • "no domains found in N lines": the URL didn't return a list (often an HTML error page). Check it in a browser.
  • A list has no effect / is "idle": no view it applies to offers recursion. Pick the view your devices use (--view internal).
  • Something is still answered: jarl block check NAME shows whether Jarl blocks it. If it does, the device probably doesn't use your server (DNS over HTTPS, another resolver), or has the answer cached for a while.
  • Something you need is blocked: jarl block check NAME shows which list; jarl block allow NAME overrides it.