Setting up a Pi-hole ad-blocker on a Khadas VIM1S SBC

-- 2025-10-24 --

This guide describes how I set up ad-blocker for my home network on a dedicated machine. I used Pi-hole to do the filtering with Unbound as its DNS resolver. I decided to set it up on a small SBC from Khadas, the VIM1S. This is quite cheap, has an simple OS setup system and operates silently.

Pi-hole
Unbound
Khadas

Setting up the server

The VIM1S has Khadas's OOWOW service embedded within it. This makes it simpler to perform maintenance tasks such as installing an operating system. The VIM1s will boot straight into OOWOW on first use. There is a straightforward installation guide which allows you to choose the OS to install. I chose the "trixie_legacy" option from the Armbian sub-menu.

Once the OS is installed the machine will reboot into it. Once again Armbian has a straightforward setup procedure. The only thing to take extra care over is setting up a static IP address. Pi-hole won't work properly if the host machine has a IP address assigned by DHCP that may change. (You may also need to make changes on your router to accomodate the static IP.)

I usually do the initial setup with the machine plugged into a keyboard and monitor (although the Khadas website describes other ways to do it). After I'm sure everything is running properly I make sure that SSH access works and then close down the machine, unplug the peripherals and do all the other set up work over SSH.

Opening ports

Pi-hole needs several ports open to function. I don't use Pi-hole's DHCP function or its NTP function. Also I only allow acess over HTTPS, not HTTP. So I need to open the following ports:

ufw allow 53/tcp comment "DNS (for pi-hole)"
ufw allow 53/udp comment "DNS (for pi-hole)"
ufw-allow 443/tcp comment "HTTPS (for pi-hole)"

Installing Pi-hole

The simplest way to install Pi-hole is by using the command below. This means running downloaded code blind. If you have security concerns about this it's probably better to run install by one of the alternative options given on the Pi-hole website.

doas curl -sSL https://install.pi-hole.net | bash

When going through the installation you will be asked to choose an upstream DNS provider. It doesn't matter much which one is chosen as you will be changing it to a locally run instance of 'Unbound' later in this guide.

Accept the suggested blocklist as this can be changed after installation and it's easier to do it from the web interface once it's set up.

When asked, enable query logging. This has some privacy implications but you don't want to switch it off until you're sure everything is running smoothly as query logging can give you some clues as to the source of problems. You'll also be asked what privacy mode to enable for FTL (the Pi-hole blocker); I choose 'Show everything'. Once I'm sure that Pi-hole is working properly I can change this to a more private setting later.

After installing Pi-hole

First add your unprivileged user to the Pi-hole group to avoid having to enter the Pi-hole password for every Pi-hole CLI command. Either:

doas usermod -aG pihole $USER

Or edit the /etc/group and /etc/gshadow files directly.

Next update Pi-hole:

doas pihole -up

Start Pi-hole:

doas pihole enable

Pi-hole is now running and will always start up at boot.

Set a password for the web interface:

doas pihole setpassword

Run Pi-hole's 'Gravity' to get the blocklist in the proper format:

doas pihole -g

If you want your Pi-hole server to do its own DNS resolving (you do!) you need to remove the /etc/resolv.conf symlink, which is managed by systemd and replace it with a static resolv.conf file. (Remember, this is Armbian, other distributions will handle this differently.)

/etc/resolv.conf -> /run/systemd/resolve.stub-resolv.conf
doas rm /etc/resolve.conf

This /etc/resolve.conf file should suffice:

# /etc/resolv.conf

nameserver 127.0.0.1

Finally, log out of the Pi-hole server and point one of your network's computer's browser at the Pi-hole instance:

https:///admin/login

From here you can login using the password set above and tweak the system to your liking.

A useful feature when finishing the set up is that if you have another instance of Pi-hole running elsewhere you can use the 'Teleporter' option in 'Settings' menu to export the settings (including blocklists) from there and then import them from the saved file into your new instance. This is particularly handy if you use a lot of blocklists.

Unbound

Unbound is a recursive DNS resolver which supports DNSSEC and DNS over TLS (DoT) which improves the security and privacy of DNS requests. By installing your own resolver you stop sharing your DNS traffic with third parties and increase your DNS privacy. When you run your own resolver your DNS cache will be kept on your own machine rather than stored on someone else's computer. The downside is that the first time you resolve a domain name it will probably be slower than using your ISP’s resolver. However all subsequent queries for that domainn should be much faster.

Install unbound and apparmor (to improve security):

doas apt install unbound apparmor

Configure Unbound

Now create a config file at /etc/unbound/unbound.conf.d/pi-hole.conf (this one is based on the example provided on the Pi-hole website):

# /etc/unbound/unbound.conf.d/pi-hole.conf
# Configure unbound for use with Pi-hole

server:
  # If no logfile is specified, syslog is used
  # Level 0 means no verbosity, only errors
  # Level 1 gives operational information
  # Level 2 gives detailed operational information
  # Level 3 gives query level information
  # Level 4 gives algorithm level information
  # Level 5 logs client identification for cache misses
  # Anything above level 1 creates massive logs
  logfile: "/var/log/unbound/unbound.log"
  log-time-ascii: yes
  verbosity: 0

  interface: 127.0.0.1
  port: 5335
  do-ip4: yes
  do-udp: yes
  do-tcp: yes

  # May be set to no if you don't have IPv6 connectivity
  do-ip6: yes

  # You want to leave this to no unless you have *native* IPv6. With 6to4 and
  # Terredo tunnels your web browser should favor IPv4 for the same reasons
  prefer-ip6: no

  # Use this only when you downloaded the list of primary root servers!
  # If you use the default dns-root-data package, unbound will find it
  # automatically
  #root-hints: "/var/lib/unbound/root.hints"

  # DNS over TLS
  tls-upstream: yes
  tls-port: 853
  tls-cert-bundle: /etc/ssl/certs/ca-certificates.crt

  # Trust glue only if it is within the server's authority
  harden-glue: yes

  # Require DNSSEC data for trust-anchored zones, if such data is absent,
  # the zone becomes BOGUS
  harden-dnssec-stripped: yes

  # Don't use Capitalization randomization as it known to cause DNSSEC
  # issues sometimes.
  # See https://discourse.pi-hole.net/t/unbound-stubby-or-dnscrypt-proxy/9378
  # for further details
  use-caps-for-id: no

  # Reduce EDNS reassembly buffer size.
  # IP fragmentation is unreliable on the Internet today, and can cause
  # transmission failures when large DNS messages are sent via UDP. Even
  # when fragmentation does work, it may not be secure; it is theoretically
  # possible to spoof parts of a fragmented DNS message, without easy
  # detection at the receiving end. Recently, there was an excellent study
  # >>> Defragmenting DNS - Determining the optimal maximum UDP response size
  # for DNS <<< by Axel Koolhaas, and Tjeerd Slokker
  # (https://indico.dns-oarc.net/event/36/contributions/776/)
  # in collaboration with NLnet Labs explored DNS using real world data from
  # the RIPE Atlas probes and the researchers suggested different values for
  # IPv4 and IPv6 and in different scenarios. They advise that servers should
  # be configured to limit DNS messages sent over UDP to a size that will not
  # trigger fragmentation on typical network links. DNS servers can switch
  # from UDP to TCP when a DNS response is too big to fit in this limited
  # buffer size. This value has also been suggested in DNS Flag Day 2020.
  edns-buffer-size: 1232

  # Perform prefetching of close to expired message cache entries
  # This only applies to domains that have been frequently queried
  prefetch: yes

  # One thread should be sufficient, can be increased on beefy machines.
  # In reality for most users running on small networks or on a single
  # machine, it should be unnecessary to seek performance enhancement by
  # increasing num-threads above 1.
  num-threads: 1

  # Ensure kernel buffer is large enough to not lose messages in
  # traffic spikes
  so-rcvbuf: 1m

  # Ensure privacy of local IP ranges
  private-address: 192.168.0.0/16
  private-address: 169.254.0.0/16
  private-address: 172.16.0.0/12
  private-address: 10.0.0.0/8
  private-address: fd00::/8
  private-address: fe80::/10

  # Ensure no reverse queries to non-public IP ranges (RFC6303 4.2)
  private-address: 192.0.2.0/24
  private-address: 198.51.100.0/24
  private-address: 203.0.113.0/24
  private-address: 255.255.255.255/32
  private-address: 2001:db8::/32

forward-zone:
  # Using "." for the name will forward all queries not in the cache
  # to 'forward-addr'
  name: "."
  forward-tls-upstream: yes
  # Note that the hostname after "#" in the following list of addresses
  # is not a comment, it is used for TLS checks
  # Using Cloudflare...
  forward-addr: 1.0.0.1@853#cloudflare-dns.com
  forward-addr: 1.1.1.1@853#cloudflare-dns.com

Check that the config file is OK:

doas unbound-checkconf

Now create the logfile:

doas mkdir -p /var/log/unbound
doas touch /var/log/unbound/unbound.log
doas chown unbound /var/log/unbound/unbound.log

Next create and AppArmor exception for the logfile. Edit /etc/apparmor/local/usr.sbin.unbound, adding the line:

/var/log/unbound/unbound.log rw,

Then reload AppArmor, making sure it reads the new exception:

doas apparmor_parser -r /etc/apparmor.d/usr.sbin.unbound
doas systemctl restart apparmor

Get Unbound running and working with Pi-hole

Enable and start unbound:

doas systemctl enable unbound
doas systemctl start unbound

Run some tests:

# Is it working?
dig pi-hole.net @127.0.0.1 -p 5335

# Is DNSSEC working?
# This should respond with SERVFAIL
dig fail01.dnssec.works @127.0.0.1 -p 5335
# This should respond with NOERROR, the IP address and 'ad' in the flags
dig +ad dnssec.works @127.0.0.1 -p 5335

Now configure Pi-hole to use Unbound. Use the web interface and go to the 'Settings >> DNS >> Custom DNS servers' section. Enter '127.0.0.1#5335' and untick all the other DNS servers. Click on 'Save & Apply'.

Use a browser on one of your network's computers to visit 'https://one.one.one.one/help/' which will show you if Pi-hole is now using DoT.

Linux tips and guides
Home page