jarld downloads blocklists (Adblock, hosts or plain domains), checks them daily and reloads named only when a list changed; your own block and allow rules take precedence, and the most specific rule wins. Lists and rules apply per view (default: every view that offers recursion). Web UI Blocking page with name checks, jarl blocklist and jarl block, the /blocking API, blocked counts in the statistics. Database schema 6. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
246 lines
10 KiB
Markdown
246 lines
10 KiB
Markdown
# 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](#quick-start)
|
|
- [Where blocking applies](#where-blocking-applies)
|
|
- [Your rules](#your-rules)
|
|
- [Blocklists](#blocklists)
|
|
- [Checking a name](#checking-a-name)
|
|
- [Statistics](#statistics)
|
|
- [Memory and performance](#memory-and-performance)
|
|
- [How it works](#how-it-works)
|
|
- [Troubleshooting](#troubleshooting)
|
|
|
|
## Quick start
|
|
|
|
In the web UI, open **Blocking**, pick one of the well-known lists, and
|
|
click **Add list**. Or with the CLI:
|
|
|
|
```sh
|
|
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.
|
|
|
|
```sh
|
|
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:
|
|
|
|
```sh
|
|
jarl blocklist refresh # every list; or: refresh hagezi-multi [--force]
|
|
```
|
|
|
|
**Managing lists:**
|
|
|
|
```sh
|
|
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).
|
|
|
|
## 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.
|
|
|
|
## Statistics
|
|
|
|
With [query statistics](stats.md) on (including `query_log`), Jarl counts
|
|
what blocking does: blocked queries over time and as a share of all
|
|
queries, which rules and lists blocked them, and the most blocked names
|
|
(`jarl stats summary`, `jarl stats top blocked`, `jarl stats breakdown
|
|
blocklist`). Blocked answers are left out of the NXDOMAIN figures, which
|
|
are about names that really don't exist.
|
|
|
|
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](configuration.md#blocking)).
|
|
|
|
## 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.
|
|
|
|
Lists and rules need the `blocking` permission ([permissions.md](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.
|