DNSCrypt
3D render: three stacked filter discs with holes, a few marked in magenta

Filtering

DNS Filtering with dnscrypt-proxy: Blocklists, Rules and Cloaking

DNS filtering stops unwanted connections at the moment a device asks for a name. If an ad server, a tracker or a malware host cannot be resolved, the connection never starts. dnscrypt-proxy does this locally with plain text rule files, and a blocked query is answered on your own machine, so the upstream resolver never sees or logs it.

Hop 01

How DNS filtering works in dnscrypt-proxy

The proxy checks every incoming query against its lists before anything is encrypted and sent. That is how dns filtering works locally: a name rule that matches is answered locally, an IP rule inspects the addresses in the resolver’s answer, and an allow list rule lets a query through even when a broader blocklist entry would catch it.

Each list lives in its own section of dnscrypt-proxy.toml and points to a text file with one pattern per line. Every list can have its own log file in TSV or LTSV format, which shows what was blocked and why. Version 2.0.45 renamed the old blacklist and whitelist sections, and the 2.1 series requires the new names.

[blocked_names]
  blocked_names_file = 'blocked-names.txt'
  log_file = 'blocked-names.log'
[allowed_names]
  allowed_names_file = 'allowed-names.txt'
[blocked_ips]
  blocked_ips_file = 'blocked-ips.txt'
Hop 02

Writing name patterns

The pattern example.com blocks the domain and every name inside it, which is what you want most of the time. A leading = makes the match exact, so =example.com leaves www.example.com alone. A prefix such as ads.* matches any name that starts with it, and the characters *, ? and [] work anywhere in a pattern.

Prefix, suffix and exact rules stay fast even with very large lists, while patterns with wildcards in the middle are slower and worth keeping few. Version 2.1.20 added regex: rules for cases the glob syntax cannot express. If a regular expression is invalid or too slow, the proxy refuses to start, and a reload keeps the previous rules.

Since version 2.0.34, name rules also apply to CNAME aliases. A name that is only an alias for a hostname at a content delivery network is blocked when that target matches. Allow list entries, by contrast, are only compared with the name that was actually queried, which the wiki describes as a guard against allowing far more than intended.

Hop 03

IP rules, rebinding and schedules

The [blocked_ips] list drops answers that contain specific addresses, which catches trackers that rotate through many names on a few servers. Rules are full addresses or prefixes such as 192.168.*, and version 2.1.16 added CIDR ranges. The wiki uses the same list for DNS rebinding protection, blocking 127.0.0.0/8, 10.0.0.0/8 and 192.168.0.0/16 in public answers.

Any rule can be limited in time. You define named schedules in the [schedules] section, each a set of weekly time ranges, and append the name to a rule, for example *.youtube.* @time-to-sleep. To allow a site only at certain hours, block it permanently and add a time-limited entry for it to the allow list instead.

Hop 04

Cloaking: answering with your own addresses

Cloaking works like a hosts file that understands patterns. A line such as example.com 192.168.2.37 in the file named by cloaking_rules returns that address for the domain and its subdomains without asking any resolver. A name can also be cloaked to another name: www.google.* forcesafesearch.google.com enforces SafeSearch for every Google domain.

Hop 05

Building lists with generate-domains-blocklist.py

The source tree includes generate-domains-blocklist.py in its utils directory. Run python3 generate-domains-blocklist.py -o blocklist.txt and the script reads domains-blocklist.conf, downloads the listed sources, converts hosts-file formats, drops junk and duplicates, and merges overlapping entries. If a source is unreachable, it keeps the previous output instead of overwriting it with a partial list.

The script also takes an allow list with -a and a file of time-restricted names with -r, so the generated file never blocks something you explicitly need. The wiki’s advice is worth following: every third-party list contains false positives, and a few well-maintained lists beat the biggest possible combination. Rebuild the file regularly, for example from a weekly cron job.

Hop 06

Filtering resolvers instead of local lists

Some public resolvers filter on their side; RFC 8499 calls this a policy-implementing resolver. Quad9’s recommended address, 9.9.9.9, blocks malware domains and validates DNSSEC. Cloudflare’s 1.1.1.1 for Families uses 1.1.1.2 to block malware and 1.1.1.3 to block malware and adult content. The operator decides what is blocked, not you, for every query sent there.

dnscrypt-proxy avoids such servers unless you ask for them. The example configuration sets require_nofilter = true, and DNS stamps carry a property flag for servers that do not intentionally block domains. The resolver directory shows that flag per server, and an optional parental-control source lists resolvers that filter content unsuitable for children.

Hop 07

DNS filtering for home networks

For dns filtering for home use, run dnscrypt-proxy on the router so that every device benefits, including televisions and consoles that offer no DNS settings. The OpenWrt guide covers that setup, and the route builder generates a full configuration with a blocklist section. Check the blocked-names log after a week to find false positives.

Hop 09

Frequently asked questions

What does dnscrypt-proxy return for a blocked domain?

dnscrypt-proxy answers it itself, by default with an HINFO record; the blocked_query_response setting switches this to REFUSED or a fixed IP address. Since version 2.1.5 the response also carries an extended DNS error code. The query is never forwarded, so the upstream resolver never sees it. Check the blocked-names log to see which rule caught a query.

Can I block a domain but allow one subdomain?

Yes. Put example.com in the blocklist and api.example.com in the allow list. Allow list entries bypass both name and IP filters for that query. They are only matched against the query name itself, not against the CNAME aliases the name resolves to. Test the allowed name afterwards, and check the blocked-names log if it still fails.

Where do I get blocklists for dnscrypt-proxy?

The source code ships generate-domains-blocklist.py, which downloads public lists, removes junk and duplicates, and writes one optimised file. The project wiki keeps a page of public blocklists. A few high-quality, maintained lists work better than the largest possible combination. Every list has false positives, so pass your allow list with -a and rebuild the file regularly, for example weekly.

Do I need a filtering resolver if dnscrypt-proxy blocks locally?

No, the two are alternatives. Local lists give you full control and keep blocked queries private. A filtering resolver applies the operator's policy to everything you send it. If you want one, set require_nofilter to false or name the resolver explicitly in server_names. Keep require_nofilter at true when you rely only on local lists.

Does DNS filtering block ads inside apps?

It blocks any connection that needs a lookup for a blocked name, so ads and trackers served from their own domains disappear in apps as well as browsers. Content served from the same domain as the app itself cannot be separated by name and stays visible. Check the blocked-names log after a week to catch false positives.

↑