Set up local DNS and ad blocking

As mentioned in my Valid TLS cert for local services with Traefik post, I run local services with a valid domain. I could’ve set up a custom non-existent domain + funny TLD, but creating a certificate for that would’ve made things much harder. Now I wanted to make a few services available via subdomains. I didn’t really like the fact of setting up these records on Cloudflare, where my domain is hosted, so a local DNS server was the way to go.

There are DNS servers like BIND, Knot and a few more alternatives but when setting this up, why not also block Ads and possible malicious domains at the same time? There are options like Pi-hole or AdGuardHome. For whatever reason I went with something different: blocky. I can’t even give a proper reason why I choose it. Maybe it had something to do with the fact that it is entirely written in Go and or that some benchmarks compared it against Pi-hole putting it in the first place when it comes to speed and performance.

Blocky doesn’t provide a web interface. It does have an API though. Said API provides the ability to enable/disable blocking, has an extra query endpoint and provides an endpoint to refresh the configured blocking lists. That’s it. Everything else has to be configured via its configuration file.

This is the first part of my configuration which basically handles the name resolution.

    - tcp-tls:
    - tcp-tls:


  mapping: tcp+udp: tcp+udp: tcp+udp:

    - 1

As an upstream DNS, I stick to Cloudflare’s Blocky has the ability to perform upstream requests via standard DNS over UDP/TCP, DoH and DoT. In my case it’s the latter: DNS over TLS. The default keyword indicates the DNS servers that are used as default (duh), but Blocky has the ability to group clients. If I want all my iot devices to use a different DNS server, I could in theory do so.

The customDNS is the main option, that makes my setup work the way it is. Blocky will return for all queries for, which also includes all subdomains like or This is the behaviour I want because on this IP address, a traefik server is listening and will handle the proxying based on the host name / subdomain.

Those conditional entries are another nice feature. DNS lookups like will be handled by instead of my configured DNS servers. The same thing works for reverse DNS lookups. In my case it’s again for normal clients in my network and for an experimental Docker reverse DNS resolver. Somewhat redundant, but for blocky’s internal client lookup that is being used in logs, Prometheus output etc, the clientLookup should be configured as well.

Second part of my configuration:

    - AAAA

  blockType: zeroIP
  refreshPeriod: 4h
  downloadTimeout: 4m
  downloadCooldown: 10s
      - ads
      - malware

httpPort: 4000
bootstrapDns: tcp+udp:

  enable: true

  type: none

The filtering settings allow me to filter out certain DNS queries. Since I didn’t have much luck with IPv6 in the past, I simply kill all IPv6 requests.

The blocking part is probably the most interesting one. You can configure blacklist sources. Only lists of the hosts type are supported. So in theory these lists can be placed in /etc/hosts or the Windows’ hosts file. Instead of using the IP that is configured in said lists, blocky will replace it with the IP that is configured via blockType. Again, things can be grouped and then applied to all or just a certain list of hosts as you can see in clientGroupsBlock. This repository is a good source for block lists. Blocky will handle duplicate entries by itself.

Beside that, you can configure the listening ports. Standard DNS will listen on port 53, DoT on 853 etc. The httpPort exposes blocky’s internal API and prometheus endpoint. The query log can be enabled and requires further configuration. This option will enable logging for all DNS queries to a database, console or log file. I don’t want to log anything, which is why it’s disabled.

In order configure all my local clients to use my local DNS, I simply announce it via DHCP via my FRITZ!Box router.