Configure Unbound

unbound(8) is a caching nameserver that comes as part of OpenBSD base. You can use this to provide DNS services to users on your network. There are several potential benefits:

  1. Helps users bypass political censorship of domains
  2. Speeds up DNS lookup for your local services using cached records
  3. Provides DNS privacy and security for users

Comparing Unwind with Unbound

Although unwind is very convenient and easy to configure, unwind(8) is not able to provide public DNS services for your users. For this reason, we recommend configuring unbound(8) for any public system (ie, anything beyond your personal workstation or laptop).

Downloading the trust anchor

We will want to configure unbound(8) so it can use DNSSEC and validate domain records. In order to do this, you will need to download a trust anchor. We will use unbound-anchor(8):

# unbound-anchor -a "/var/unbound/db/root.key"

This will download the root anchor to the file /var/unbound/db/root.key.

Configuring Unbound

Below, we provide a simple, sample unbound.conf(5). By default, unbound(8) runs inside a chroot in /var/unbound/, so we place this conf file in /var/unbound/etc/unbound.conf:

server:
        interface: 127.0.0.1 # listen on localhost
        interface: ::1 # listen on localhost

        access-control: 0.0.0.0/0 refuse # drop all users by default
        access-control: 127.0.0.0/8 allow # allow localhost to use unbound
        access-control: ::0/0 refuse # drop all IPv6 users by default
        access-control: ::1 allow # allow IPv6 localhost to use unbound

        hide-identity: yes
        hide-version: yes

        auto-trust-anchor-file: "/var/unbound/db/root.key"
        val-log-level: 2

        aggressive-nsec: yes

remote-control:
        control-enable: yes
        control-interface: /var/run/unbound.sock

In this configuration, we define 2 interface directives. This tells unbound(8) to listen on the IP addresses 127.0.0.1 and ::1 (localhost on IPv4 and IPv6, respectively). By default, port 53 is used.

The next block of directives is for access-control. This controls which clients are allowed to query your caching nameserver. Limiting which clients can access your nameserver is important to prevent DNS amplification attacks.

We drop all queries from all IPs by default, and send the DNS rcode REFUSED. Then, we whitelist localhost (127.0.0.0/8 and ::1) so that only our system can query unbound. You can whitelist any additional users, such as users from your local internal network or other trusted IP range.

We then hide identity and version queries.

We specify the file for the trust anchor for one zone. Make sure unbound has write permission for this file (it does by default). Setting val-log-level: 2 shows plenty of debug information for failed DNSSEC validation. We turn on aggressive nsec to use DNSSEC NSEC to get NXDOMAIN results faster.

Finally, we enable remote control with the unbound-control utility.

Built-in nameservers

In our configuration, we use the default built-in list of authoritative nameservers for the root zone (.) (root hints). When unbound receives a DNS query, it will ask the root nameservers about the top-level domain's (TLD's) nameserver, and then ask that nameserver for the domain. This recursive lookup will continue until an answer is found (or an NXDOMAIN is returned for no answer). This answer will be cached according to the time-to-live (TTL) of the record.

In order to avoid using the default root hints, you can specify forwarding servers. A sample guide for OpenNIC is provided.

Check for valid configuration

Before starting unbound(8), use unbound-checkconf(8) to ensure the configuration is valid:

# unbound-checkconf
unbound-checkconf: no errors in /var/unbound/etc/unbound.conf

Note: unbound-checkconf(8) is only a basic sanity check. It does not guarantee your configuration is meaningful and valid.

Using unbound

To start unbound:

# rcctl enable unbound
# rcctl start unbound

In order to have your system query unbound, you must make sure that resolv.conf is using 127.0.0.1 as the default nameserver. Your system checks /etc/resolv.conf to determine which server to query. It should look like the following:

nameserver 127.0.0.1
lookup file bind

Importantly, the one (and only) nameserver should be 127.0.0.1.

If you have some other nameserver in resolv.conf(5), your system may not actually be using unbound(8) but some other nameserver. If this is the case, you may need to edit resolv.conf or configure the nameserver that resolvd uses.

Testing unbound

Query unbound with dig, nslookup?, or host to confirm it is working:

$ dig @127.0.0.1 google.com

; <<>> dig 9.10.8-P1 <<>> @127.0.0.1 google.com
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 53037
;; flags: qr rd ra; QUERY: 1, ANSWER: 6, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;google.com.                    IN      A

;; ANSWER SECTION:
google.com.             300     IN      A       142.251.167.101
google.com.             300     IN      A       142.251.167.102
google.com.             300     IN      A       142.251.167.100
google.com.             300     IN      A       142.251.167.138
google.com.             300     IN      A       142.251.167.113
google.com.             300     IN      A       142.251.167.139

;; Query time: 48 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)
;; WHEN: Tue Dec 10 21:33:07 MST 2024
;; MSG SIZE  rcvd: 135

Troubleshooting

No Trust Anchor

If you see this error when running unbound-checkconf(8):

# unbound-checkconf
/var/unbound/db/root.key: No such file or directory
[1733812265] unbound-checkconf[86538:0] fatal error: auto-trust-anchor-file: "/var/unbound/db/root.key" does not exist in chrootdir /var/unbound

The error message indicates that unbound(8) lacks a trust anchor at /var/unbound/db/root.key.

Make sure to run unbound-anchor(8) before starting unbound(8), as documented above.

Address already in use

If unbound is unable to start, run it in debug mode with increased verbosity:

# unbound -dv
[1733887244] unbound[77049:0] notice: Start of unbound 1.21.0.
[1733887244] unbound[77049:0] error: bind: address already in use
[1733887244] unbound[77049:0] fatal error: could not open ports

This error message indicates another process is already bound to the same socket?. fstat? can quickly help you identify the process:

# fstat | grep ':53'
_unwind  unwind      6934    6* internet dgram udp 127.0.0.1:53
_unwind  unwind      6934    7* internet6 dgram udp [::1]:53
_unwind  unwind      6934    8* internet stream tcp 0xffff8000008ec070 127.0.0.1:53
_unwind  unwind      6934    9* internet6 stream tcp 0xffff8000008ec9e8 [::1]:53

In this case, we forgot to stop unwind before starting unbound. This can be easily fixed:

# rcctl stop unwind
unwind(ok)
# rcctl start unbound
unbound(ok)

Negative cache

If a DNS record fails to validate the first time, this error will get cached:

Mar  8 01:34:41 hostname unbound: [45846:0] info: validation failure <hostname.com. A IN>: key for validation . is marked as invalid because of a previous validation failure <previoushostname.com. A IN>: no DNSKEY rrset for trust anchor . while building chain of trust

The solution is to flush all negative cache with unbound-control:

# unbound-control flush_negative

(Optional) Turn off DNSSEC

You may optionally turn off DNSSEC if you are using forwarders that do not support DNSSEC.

In /var/unbound/etc/unbound.conf, comment out these lines:

	# auto-trust-anchor-file: "/var/unbound/db/root.key"
        # val-log-level: 2

	# aggressive-nsec: no

Some public servers do not support DNSSEC, and if unbound(8) is configured to require DNSSEC, it may fail to build a chain of trust. You can see this in logs (by default /var/log/daemon):

Dec 10 22:58:56 hostname unbound: [65464:0] info: failed to prime trust anchor -- DNSKEY rrset is not secure . DNSKEY IN
Dec 10 22:58:56 hostname unbound: [65464:0] info: validator operate: query example.com. A IN
Dec 10 22:58:56 hostname unbound: [65464:0] info: Could not establish a chain of trust to keys for . DNSKEY IN
Dec 10 22:58:56 hostname unbound: [65464:0] info: validation failure <example.com. A IN>: signature for expected key and algorithm missing from 208.99.44.76 for trust anchor . while building chain of trust

Stale hosts file

If you change your host's IP address, the hosts(5) file may be out of date. Depending on your configuration of resolv.conf, /etc/hosts may be intercepting name lookup and giving you an old IP address.

Double check /etc/hosts to ensure that it is not using an incorrect IP address.