Setting up Unbound on OpenWrt
OpenWrt has a very nice default DNS setup. It uses dnsmasq to automatically generate A and AAAA records for all DHCPv4 and DHCPv6 leases given out to clients, and queries upstream resolvers (provided by the ISP via DHCP or PPPoE on the WAN interface) for any records outside of that zone.
However, those ISP resolvers can become an issue when they are unstable, slow, privacy-unfriendly, are used for censorship, or do a combination of these things.
In any of those cases, it would be a lot better to run your own recursive DNS server, talking to authoritative servers directly, while keeping a large DNS cache on your device. Unbound is an ideal solution for that.
The only issue is that, by default, Unbound also functions as a caching and forwarding DNS server running on port 53. We already have dnsmasq running there, and we don't want to get rid of it, because it also handles DHCPv4 and the local DNS pool. Moreover, it's tightly integrated in OpenWrt's UCI / LuCI and part of every default OpenWrt image.
Instead, we want dnsmasq to ignore the ISP DNS servers, keep resolving local addresses like it always did, and use Unbound for all of the upstream requests.
Setting this up is actually rather simple, but it does have a couple of caveats, which are easy to avoid when you understand how OpenWrt's DNS setup works.
OpenWrt default DNS setup
The router itself uses DNS so it can resolve hosts like downloads.openwrt.org to install packages. On a stock OpenWrt setup, that goes like this:
- Lookup from router –>
/etc/resolv.conf–> dnsmasq –>/tmp/resolv.conf.d/resolv.conf.auto–> ISP resolvers
Client devices on your network get the IP addresses of the router advertised as the sole DNS server, meaning all devices in your network will use your OpenWrt router as their resolver. On a stock OpenWrt setup, that goes like this:
- Lookup from client –> advertised DNS server –> gateway IP port 53 –> dnsmasq –>
/tmp/resolv.conf.d/resolv.conf.auto–> ISP resolvers
In a default OpenWrt setup, /etc/resolv.conf (which is symlinked to /tmp/resolv.conf) will look like this:
- /etc/resolv.conf
search lan nameserver 127.0.0.1 nameserver ::1
This means that all local nameserver lookups will go to dnsmasq (listening on default DNS port 53) on the loopback interface. In turn, dnsmasq will then use /tmp/resolv.conf.d/resolv.conf.auto to determine the upstream resolvers. This file is automatically generated by netifd after WAN interfaces come up and go down.
For example, with Freedom Internet, it looks like this:
- /tmp/resolv.conf.d/resolv.conf.auto
# Interface wan nameserver 185.93.175.43 nameserver 185.232.98.76 # Interface wan_6 nameserver 2a10:3780:2:52:185:93:175:43 nameserver 2a10:3780:2:53:185:232:98:76
Unbound behind dnsmasq
If you want to put Unbound behind dnsmasq and have it handle all DNS lookups (with the exception of the local zone), both for the router itself and for clients on the network, you'll have to install some packages and change some configuration options.
First, install Unbound:
apk update apk add unbound-anchor unbound-daemon
Then configure it. Only a few defaults have to be changed:
- /etc/config/unbound
option add_local_fqdn '0' option domain 'lan' option domain_type 'refuse' option listen_port '1053' option query_minimize '1' option ttl_min '0' option validator '1'
Note that if you use another local domain instead of lan, you have to change it accordingly.
Basically, these options say:
- Ignore all handling of the local zone (dnsmasq does that).
- Listen on port 1053 instead of 53 (dnsmasq listens there).
- Use query name minimisation (don't tell the root servers which subdomains you want to resolve).
- Don't increase TTL values returned by upstream resolvers.
- Enable DNSSEC validation.
All other options can be left at their defaults.
If you have a router with large amounts of computing power and RAM, such as a Turris Omnia, you may want to change these values too:
- /etc/config/unbound
option num_threads '2' option recursion 'aggressive' option resource 'large'
Restart Unbound:
service unbound restart
Then configure dnsmasq:
- /etc/config/dhcp
option cachesize '0' option noresolv '1' list server '127.0.0.1#1053' list server '::1#1053'
Basically, these options say:
- Disable caching (Unbound does that, and two caches on the same device makes no sense).
- Disable parsing of
/tmp/resolv.conf.d/resolv.conf.auto(don't use ISP resolvers). - Only use port 1053 on the loopback interface as the upstream resolver (this is where Unbound listens).
Restart dnsmasq:
service dnsmasq restart
That's all there is to it. dnsmasq will now ignore your ISP DNS servers and use Unbound instead, while maintaining the normal behaviour for local DNS, DHCP hostnames as A/AAAA records, and so on.
So the situation has now become:
- Lookup from router –>
/etc/resolv.conf–> dnsmasq –>::1#1053–> Unbound –> local cache or root DNS servers
- Lookup from client –> advertised DNS server –> gateway IP port 53 –> dnsmasq –>
::1#1053–> Unbound –> local cache or root DNS servers
Dealing with sysupgrades
While this setup is minimally invasive and very easy to revert if you should ever decide to do so, there is one scenario you have to keep in mind: your DNS will be completely broken after a regular sysupgrade.
The reason for this is simple:
- A regular sysupgrade means you download an image from firmware-selector.openwrt.org and flash it.
- While you keep the router settings, you will lose manually installed packages.
- Stock OpenWrt images do not contain Unbound.
- dnsmasq is set to ignore the ISP DNS servers and only query Unbound.
- There is now nothing listening on port 1053 on the loopback interface.
- All DNS lookups (other than to the local zone) will fail.
Due to all lookups failing, this also means you can't run apk update and apk add, because downloads.openwrt.org can't be resolved, meaning you can't install Unbound to get DNS working again.
However Kafkaesque as this may sound, the situation is actually not that dire. Because dnsmasq's init script contains code that manipulates resolv.conf once it's stopped and started.
The important part being in dnsmasq_stop():
- /etc/init.d/dnsmasq
ln -sf "/tmp/resolv.conf.d/resolv.conf.auto" /tmp/resolv.conf
/etc/resolv.conf is a symlink to /tmp/resolv.conf. When dnsmasq is running, it contains only 127.0.0.1 and ::1 (read: dnsmasq) as the sole DNS server for the OpenWrt system.
However, when dnsmasq is stopped, /tmp/resolv.conf will become a symlink to /tmp/resolv.conf.d/resolv.conf.auto, which contains the ISP DNS servers set by netifd. In other words, once you stop dnsmasq, DNS resolving on the router will start working again. (DNS resolving from clients will still not work, due to dnsmasq not running at all now.)
In that case, all you need is a script that:
- Stops dnsmasq (ISP resolvers are used).
- Synchronises the clock (to avoid TLS certificates “not yet being valid”).
- Updates the package lists (so we can install Unbound).
- Installs Unbound (so it will be back, listening on port 1053).
- Starts dnsmasq again (which can then talk to Unbound and thus work).
Such a script is, as you may expect, really simple to make:
- /root/install.sh
#!/bin/sh service dnsmasq stop service sysntpd restart apk update apk add unbound-anchor unbound-daemon service dnsmasq start
After a sysupgrade, all you have to do is SSH to your router and run install.sh (or whatever you called it).
ssh root@openwrt sh install.sh
Once you've run this script, DNS resolving, both from the router and from clients on the network, will work normally again.
Of course, now that OpenWrt 25.12 has Attended Sysupgrade, this is even less of an issue. With Attended Sysupgrade, a custom image is built, containing all the packages your current installation contains. So if you have unbound-anchor and unbound-daemon installed, so will the new image. In that case, DNS will simply work after a sysupgrade. DNS not working is only an issue when you do a classic sysupgrade with a stock OpenWrt image.
Why is this not the default?
You may wonder, “if this is better, why is it not the default?”
The answer is pretty simple: resources.
Unbound is relatively heavy, especially when you consider the fact that OpenWrt targets with a single CPU core running at less than 500 MHz, 4 MB of storage and 32 MB of RAM exist.
Running Unbound on a system like that would be (nearly) impossible, if it would even fit on the storage at all.
Because OpenWrt (understandably) does not want to maintain different default packages for different targets, the much lighter dnsmasq is used as a local caching and forwarding DNS server. But if you have the resources and any reason to not use your ISP's DNS servers (for which there are many), what you really want is validating, recursive, caching nameserver, like Unbound.