Self-Hosting a Recursive DNS Resolver with Unbound DNS
Author
robeeds
Date Published

Introduction
Unbound is a validating, caching, and recursive DNS resolver. This means that Unbound is capable of verifying the authenticity and integrity of DNS data, storing frequently accessed DNS records to speed up future lookups, and translating user-friendly domain names into IP addresses. In doing so, Unbound communicates directly with the authoritative nameserver (a server that holds definitive information for a specific domain or zone), preventing third-party companies (such as Google) from logging our traffic. In conjunction with that, operating our own DNS resolver can help reduce our likelihood of being targeted for a DNS poisoning attack.
As part of our homelabbing series, we will install Unbound DNS through Portainer on our home server.
NOTE: I'll be testing a configuration with Unbound and Adguard Home on separate Docker containers. For these containers to communicate, they will have to be on the same Docker network and be visible to each other. If you're using Portainer like me, this can be viewed under Networks on the left-hand side, then click on the network name that your container(s) are connected to (default being bridge).
Installation
We'll be continuing with installing Unbound through Portainer.
First, let's log into Portainer and pull the Docker image.
We'll navigate to Images (on the left hand side) and use the alpinelinux/unbound image. Click 'Pull Image'.

Next, we'll go to Containers (once again on the left hand side), and create a new container using the image we just pulled.

After, set the Port Mappings as such.

Port Mappings:
- 5335:5335 TCP
- 5335:5335 UDP
Then, set the restart policy to 'Always'.

Finally, deploy the container.
Configuring Unbound
Since we didn't install Unbound from a package manager, we will need to install the root.hints file. We'll need a shell in our Unbound Container.
In the container list, click on the Unbound instance. Under container details > container status > console. Set the command to /bin/sh, then click 'connect'. Alternatively, SSH into your server and connect to the container's shell (this is much more stable).

Create the unbound directory in /var/lib/
1mkdir /var/lib/unbound
Run the following command to install the root.hints file.
1wget https://www.internic.net/domain/named.root -qO- | tee /var/lib/unbound/root.hints
Now, let's create and configure our unbound.conf file
1vi /etc/unbound/unbound.conf
Copy and paste the following text
1server:2 # If no logfile is specified, syslog is used3 # logfile: "/var/log/unbound/unbound.log"4 verbosity: 056 # Enable visibility to other containers on the same network7 interface: 0.0.0.08 access-control: 0.0.0.0/0 allow9 port: 533510 do-ip4: yes11 do-udp: yes12 do-tcp: yes1314 # May be set to no if you don't have IPv6 connectivity15 do-ip6: yes1617 # You want to leave this to no unless you have *native* IPv6. With 6to4 and18 # Terredo tunnels your web browser should favor IPv4 for the same reasons19 prefer-ip6: no2021 # Use this only when you downloaded the list of primary root servers!22 # If you use the default dns-root-data package, unbound will find it automatically23 root-hints: "/var/lib/unbound/root.hints"2425 # Trust glue only if it is within the server's authority26 harden-glue: yes2728 # Require DNSSEC data for trust-anchored zones, if such data is absent, the zone becomes BOGUS29 harden-dnssec-stripped: yes3031 # Trust anchor file for DNSSEC validation, as per archlinux.org/unbound32 trust-anchor-file: "/etc/unbound/root.key"3334 # Don't use Capitalization randomization as it known to cause DNSSEC issues sometimes35 # see https://discourse.pi-hole.net/t/unbound-stubby-or-dnscrypt-proxy/9378 for further details36 use-caps-for-id: no3738 # Reduce EDNS reassembly buffer size.39 # IP fragmentation is unreliable on the Internet today, and can cause40 # transmission failures when large DNS messages are sent via UDP. Even41 # when fragmentation does work, it may not be secure; it is theoretically42 # possible to spoof parts of a fragmented DNS message, without easy43 # detection at the receiving end. Recently, there was an excellent study44 # >>> Defragmenting DNS - Determining the optimal maximum UDP response size for DNS <<<45 # by Axel Koolhaas, and Tjeerd Slokker (https://indico.dns-oarc.net/event/36/contributions/776/)46 # in collaboration with NLnet Labs explored DNS using real world data from the47 # the RIPE Atlas probes and the researchers suggested different values for48 # IPv4 and IPv6 and in different scenarios. They advise that servers should49 # be configured to limit DNS messages sent over UDP to a size that will not50 # trigger fragmentation on typical network links. DNS servers can switch51 # from UDP to TCP when a DNS response is too big to fit in this limited52 # buffer size. This value has also been suggested in DNS Flag Day 2020.53 edns-buffer-size: 12325455 # Perform prefetching of close to expired message cache entries56 # This only applies to domains that have been frequently queried57 prefetch: yes5859 # 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.60 num-threads: 16162 # Ensure kernel buffer is large enough to not lose messages in traffic spikes63 so-rcvbuf: 1m6465 # Ensure privacy of local IP ranges66 private-address: 192.168.0.0/1667 private-address: 169.254.0.0/1668 private-address: 172.16.0.0/1269 private-address: 10.0.0.0/870 private-address: fd00::/871 private-address: fe80::/107273 # Ensure no reverse queries to non-public IP ranges (RFC6303 4.2)74 private-address: 192.0.2.0/2475 private-address: 198.51.100.0/2476 private-address: 203.0.113.0/2477 private-address: 255.255.255.255/3278 private-address: 2001:db8::/32
Save the file, and exit the shell. We'll go back to Portainer to restart our containers.
Adding Unbound as an Upstream DNS for Adguard Home
Since I have Unbound in a separate docker container, I'll need to know the IP address of the container. This can be seen under Networks on the left-hand side, then click on the network name that your container(s) are connected to (default being bridge).

After setting Unbound as your upstream DNS server, scroll down on the same page to test it.

After a few seconds, it should state 'Specified DNS servers are working properly'. To finish things up, we will disable the DNS cache in Adguard.
Scroll down on further, and we'll see the DNS cache configuration. Set the cache size to '0'

And we're done!
Summary and Next Steps
Now that we've made our own local, self-hosted, validating, caching, and recursive DNS resolver, we've improved our own security in the sense that we're less likely to be targeted for a DNS poisoning attack. Additionally, we've been granted the ability to speed up our own DNS queries after building Unbound's cache for some time. In the near future, we'll be going over securing access to our internal web services with Nginx Proxy Manager.

Docker container management can get a bit silly sometimes. Portainer, a container management platform, can help simplify this activity even further.

Sometimes web browsing can be absolutely dreadful with the amount of ads that intrude our browsing experiences. On top of that, some sites make....