# 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.