Blocking Ads using unbound(8) on OpenBSD

The Internet is full of Ads and Trackers. Some of them are useful to monetize free content. Some are used in a non-ethical manner. Savvy users will configure Ad-Blocker on their Web browser. Others won’t. Most Appliance and IoT modules won’t allow third-party blocking addons.

Here’s how to add an extra layer of privacy using OpenBSD and its unbound(8) DNS resolver.

Pi-Hole is an OpenSource project that enables blocking Ads at the network-level. Meaning users don’t even have to care about it. What happens is that is serves as a DNS server for your LAN which simply won’t allow IP resolutions for well-known Ad services. Read the Pi-Hole documentation to learn more about it.

After reading the sources, I decided that the core feature could be implemented using OpenBSD’s stock unbound(8) and a home-made script.

The Ad blocklist

The public blocklists used by Pi-Hole are the following:

I wrote a script that will fetch the blocklists content, parse it and create a local zone file for unbound(8). That file will contain all the blocked domains and use the redirect answer to resolve those as invalid. The final unbound(8) zone file looks like this:

local-zone: "" redirect
local-data: " A"
local-zone: "" redirect
local-data: " A"

The script is regularly run from cron and the zone file is included on every unbound(8) start/reload/restart.

Configure unbound(8)

Now that the local zone file is filled with unwanted domains, using it with unbound(8) is as simple as adding the following line to unbound.conf:

# grep include /var/unbound/etc/unbound.conf
include: /var/unbound/etc/unbound-adhosts.conf

That’s all. When you look at the logs, you’ll now see things like: redirect A IN redirect A IN redirect A IN redirect A IN redirect A IN redirect A IN redirect A IN redirect A IN redirect A IN redirect A IN

The Ad-blocker dashboard

Using syslog-ng, I parse my unbound(8) logs and store some metrics in InfluxDB. This way, it is possible to render the Ad-blocker activity using Grafana.

This is what happened when browsing sites like CNN, YouTube, TheVerge, LeMonde, LeParisien or Economie.Gouv.Fr without an AdBlocker enabled in Firefox-ESR.

I’ve been using the unbound blocklist for a month now and didn’t notice any drawbacks. Nor did I get any complains from the daughter and her smartphone…

Author: Joel Carnat

@work Technical Architect and SysAdmin ; @home OpenBSD and FOSS, Karate, Kobudō, Jōdō, Bodyweight workout, Photography & Music

31 thoughts on “Blocking Ads using unbound(8) on OpenBSD”

      1. Great script. I just installed it on my OBSD 6.6 home firewall.

        To complement the above comment, I have amended the ‘awk’ command in the ‘create unbound local zone file’ section as follows so that unbound returns an nxdomain reply. It offers the added benefit of decreasing the number of lines in the output file as there is no longer the need for a ‘local-data’ line in each case.

        # Create unbound(8) local zone file
        sort -fu $_tmpfile | grep -v “^[[:space:]]*$” | \
        awk ‘{
        print “local-zone: \”” $1 “\” static”
        }’ > $_unboundconf && rm -f $_tmpfile

  1. This seems to work well. However, if I leave all of the block lists enabled I get the following.
    May 19 13:11:32 unbound: [4229:0] error: out of memory adding local data
    May 19 13:11:32 unbound: [4229:0] fatal error: Could not set up local zones

    Is there someway to increase the memory available to unbound or another work around?

  2. Just thought I’d post an update. I tried adding additional ram. I found 4G in some old discard machines. After installing that and trying again I was still getting out of memory errors. I had faithfully updated Openbsd from 4.9 ->6.5 over the ensuing years. So I decided to grab a backup of var and etc and painstakingly modified config’s as needed to do a new install. Low and behold after the new install it ‘just works’. Thanks for your help. I must have missed something during the upgrades. Thanks for your help.

  3. Hello Joel,

    You have a very interesting website. Regarding this post, I have the following question.

    How did you manage to get logs in the form of “ redirect A IN” ?

    Even with verbose up to 11 and all log-related options enabled, I am getting the following on OpenBSD 6.5:

    Jun 30 10:35:44 dnsgw unbound: [10823:6] debug: using localzone redirect
    Jun 30 10:35:44 dnsgw unbound: [10823:6] info: AAAA IN NOERROR 0.000000 1 37

    I know I can do multi-line parsing, but the software options are not convenient as of now. telegra is still due to integrate the functionality.


  4. A word of caution when downloading the script. I did it the wrong way:
    – Under MacOS, click on the link, Safari will show the text
    – Select All, Copy
    – In Terminal, ssh to OpenBSD host
    – $ cat >
    – Paste and chmod 755
    Executing will give error:
    + sed -n /START/,$p
    + sed -e s/#.*$// -e /^[[:space:]]*$//d -e s/
    sed: 1: “/^[[:space:]]*$//d”: invalid command code /
    ftp: Writing -: Broken pipe
    Cause of error: script contained $0A as newline instead of $0D, which you obtain when downloading script with Safari menu “download linked file”.
    I am not a sed or regex guru, but isn’t there a more robust way of searching for newline?

    Thanks anyway for this great work :-)

    – Heinrich

    1. 0x0a (LF) is the correct Unix line ending. 0x0D (CR) is the Mac line ending, while windows and IMF use CRLF.

      So if your Mac „magics“ line endings into a locally fitting format, try to find a way in which it doesn‘t („paste unchanged“, „save as text with Unix line endings“ or something).

      The simplest way to transfer that file without the deviation over your desktop machine would be to ssh into your OpenBSD machine and ftp it anyway. There are other reasons, why unchecked copy&paste from a website is not a good idea.

  5. Suggestion for your script: add a command to remove duplicates as there are a number of them:

    sort $_tmpfile | uniq > $_unboundconf

    (You will probably need to add an additional intermediate $_tmpfile2, depending on how you integrate this in your script)

  6. Thank you for the script, I have a question… I am trying to use it, but get an error:

    /var/unbound/etc/unbound-adhosts.conf:1: error: syntax error
    read /var/unbound/etc/unbound.conf failed: 1 errors in configuration file

    I am not certain why, and would welcome any suggestion. I am working to see if I can figure it out too. New to config. unbound, so a fun challenge. Might be a user error on my end ;) Thanks.

    1. The data in unbound-adhosts.conf seem to not have to proper format.
      Mine looks like:
      local-zone: "" always_nxdomain
      local-zone: "" always_nxdomain
      local-zone: "" always_nxdomain
      local-zone: "" always_nxdomain
      local-zone: "" always_nxdomain

  7. @Clint

    I, too, get this issue.

    It must have something to do with the file length, as manipulating the first few lines results in the same outcome (IE; removing, editing, etc). HOWEVER;
    If you only include the first ~500 lines, for eg;
    `head -500 /var/unbound/etc/unbound-adhosts.conf > /var/unbound/etc/unbound-adhosts.conf`, and unbound-checkconf – it will work fine – noting that the syntax error on line 1 does not apply.

    The original file is >163k lines.

  8. Dear moderator; please consider this reply, over the last.

    @Clint, and users experiencing syntax errors.

    As per;

    It appears the unbound-checkconf will fail depending on WHERE the include is added, contrary to the man page for unbound.conf suggesting placement is not important.

    This does appear to be a bug, though I have not investigated further.

    I can include the full 163k file by adding the include reference in unbound.conf ABOVE the remote-control: parameter.

    The include reference at the end of the file results in a syntax error.


    1. @bsduser

      Thank you very much… and I can confirm that where the ‘add’ is DOES make the difference. I had fixed it shortly after my last post here and didn’t follow up. It took some tinkering to get it figured out… wish the man pages would’ve indicated that it matters.

      Either way… thank you for the help. IT is up and running as advertised. OF note there are 2 sources in the unbound-adhost.txt that are depreciated / no longer available. I removed them and now no error msg in the logs, and blocking most of the ad traffic quite nicely.


  9. > The script is regularly run from cron and the zone file is included on every unbound(8) start/reload/restart.

    Sorry, for us beginners, can you please tell us the best way to do exactly this?

Leave a Reply

Your email address will not be published. Required fields are marked *


This site uses Akismet to reduce spam. Learn how your comment data is processed.