How to Set Up a Local DNS Resolver with Unbound on Debian

Unbound is free and open-source DNS server software that can be used for validating, recursive, and caching DNS resolvers. It's a feature-rich DNS server that supports DNS-over-TLS (DoT), DNS-over-HTTPS (DoH), Query Name Minimisation, the Aggressive Use of DNSSEC-Validated Cache, and support for authority zones. Unbound is focused on the privacy and security of DNS, but without sacrificing the speed and performance.

Unbound is primarily developed by NLnet Labs and distributed under the BSD license, and it supports modern features on open standards of DNS Server. Unbound has been rigorously audited, and it can be run on Linux, BSD, and macOS. Unbound is available for most of these OSs and can be installed via system package manager.

In this guide, you will learn how to set up Private DNS Server with Unbound on a Debian 11 and Debian 12 server. You'll set up Unbound as a Local DNS Server with some features such as an authoritative DNS Server, enable DNS cache, set up local IP address and Access Control Lists (ACLs), setup local domain names, then set up Unbound as a DNS resolver with DNS-over-TLS (DoT) enabled.

In addition to that, you'll also set up logging for Unbound service via Rsyslog and Logrotate.

Prerequistes

To complete this guide, you must have the following requirements.

  • A system running a Debian 11 server or a Debian 12 server.
  • A user account with sudo/root administrator privileges.

That's it. You can now start installing Unbound as Local DNS Server.

Installing Unbound

By default, the Unbound package is available on Debian repository. You can install it via APT. In this first step, you'll install Unbound package, which includes the 'dns-root-data' to your Debian server.

Before you start, update and refresh your Debian package index via the apt command below.

sudo apt update

Now run the below apt command to check the 'unbound' package that is available on the Debian repository.

sudo apt info unbound

The output below confirms that the Unbound package is available by default on the Debian server repository with the current version 1.13.

check unbound package

Now run the below apt command to install unbound to your Debian system. When prompted, input y to confirm and press ENTER to proceed.

sudo apt install unbound

Output:

install unbound

Once Unbound is installed, run the below systemctl command to verify the Unbound service and ensure that the service is enabled and running.

sudo systemctl is-enabled unbound
sudo systemctl status unbound

The Unbound service is enabled and will start automatically upon system startup. And the status of the Unbound service is running.

verify unbound

Additional note, if you have disabled IPv6 on your Debian server, the Unbound can't start. To solve this, you can add the parameter 'do-ip6: no' to the 'server' section on the unbound config file '/etc/unbound/unbound.conf'.

With the Unbound installed, you'll next start configuring Unbound on your system.

Configuring Unbound DNS Server

The default Unbound configuration is located at '/etc/unbound/unbound.conf'. In this step, you'll modify the main Unbound config file '/etc/unbound.conf' via your preferred editor.

You'll now learn about the basic configuration of an Unbound DNS server.

Basic Configuration

Open the default Unbound config file '/etc/unbound/unbound.conf' using your preferred editor. This example uses nano for editing the config file '/etc/unbound/unbound.conf'.

sudo nano /etc/unbound/unbound.conf

Add the following lines to the file. You can set up basic Unbound configurations in the' server' section. In this example, you'll run Unbound on the local IP address '192.168.5.20' with the default port 53. Also, you'll set up logging to Syslog messages and disable IPv6. Lastly, you will setup Unbound to recursively query any hostname from the root DNS servers via the 'root-hints' file.

#Adding DNS-Over-TLS support
server:
    use-syslog: yes
    username: "unbound"
    directory: "/etc/unbound"
    tls-cert-bundle: /etc/ssl/certs/ca-certificates.crt
    
    do-ip6: no
    interface: 192.168.5.20
    port: 53
    prefetch: yes

    root-hints: /usr/share/dns/root.hints
    harden-dnssec-stripped: yes

Detail parameters:

  • use-syslog: enable logging to Syslog messages.
  • username: run as user unbound, which is the default user.
  • directory: the default working directory for Unbound is the '/etc/unbound' directory.
  • tls-cert-bundle: Certificates used to authenticate connections made upstream. On Debian-based distribution, the cert file is located at '/etc/ssl/certs/ca-certificates.crt'.
  • do-ip6: use 'yes' to run Unbound with IPv6 or set 'no' to disable IPv6.
  • interface: network interface or IP address that unbound will be running. You can use an IP address or the interface name such as 'eth0'. Also, you can run in a specific port by adding a format like this 'IP-ADDRESS@PORT'.
  • port: specify the port that Unbound will be running and the client's connections will be handled by this port. The default DNS port is 53.
  • prefetch: set to 'yes' to enable prefetching of almost expired message cache entries.
  • root-hints: a file that contains root DNS server details. The file '/usr/share/dns/root.hints' is provided by the 'dns-root-data' package. And also, can download the root-hints file from here 'https://www.internic.net/domain/named.cache'.
  • harden-dnssec-stripped: set it to 'yes' to harden against receiving dnssec-stripped data.

Enable DNS Cache

Next, add the following lines to enable the DNS cache query on your Unbound installation.

    cache-max-ttl: 14400
    cache-min-ttl: 1200

Detail parameters:

  • cache-max-ttl: TTL or Time To Live for RRSets and messages in DNS cache. The format is in seconds.
  • cache-min-ttl: minimal Time To Live for the cache. The default is 0, but you can change this to your flavor such as '1200' seconds. Do not set this for more than 1 hour or you will get into trouble due to stale data.

Unbound Privacy and Security

Now you can add the following lines to set up basic privacy and security for Unbound.

    aggressive-nsec: yes
    hide-identity: yes
    hide-version: yes
    use-caps-for-id: yes

Detail parameters:

  • aggressive-nsec: set to 'yes' to enable aggressive NSEC, which is using the DNSSEC NSEC chain to synthesize NXDOMAIN and other denials. Check the IETF web page about 'NSEC' https://www.ietf.org/archive/id/draft-ietf-dnsop-nsec-ttl-00.html.
  • hide-identity: set to yes to disable answers from bind queries about id.server or hostname.bind.
  • hide-version: set to yes to disable version.server and version.bind queries.
  • use-caps-for-id: set to yes to enable the use of '0x20-encoded' in the query to foil spoof attempts.

Define Private Network and Access Control Lists (ACLs)

Next, you must define your networks' private-address and the ACLs (Access Control Lists). Change the local subnet in the below lines with your current network environment.

    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

    #control which clients are allowed to make (recursive) queries
    access-control: 127.0.0.1/32 allow_snoop
    access-control: ::1 allow_snoop
    access-control: 127.0.0.0/8 allow
    access-control: 192.168.5.0/24 allow

Detail parameters:

  • private-address: define private network subnets on your infrastructure. Only 'private-domain' and 'local-data' names are allowed to have these private addresses.
  • access-control: define access control in which clients are allowed to make (recursive) queries to the Unbound server. The parameter 'allow' will enable recursive, while the 'allow_snoop' will enable both recursive and non-recursive.

Setup Local Domain

After configuring private-address and access control lists, you'll now define the local zone for your local domain name. This is very useful, especially if you have multiple self-hosted applications on your local network. You can easily define your domain name or sub-domains and pointed to the specific target IP address.

This example will create the zone for the domain 'garden.lan' with the type 'static', then you'll create multiple sub-domains via the 'local-data' parameter. Each sub-domain will be pointed to a specific IP address, and also you'll create PTR records via the 'local-data-ptr' parameter.

    # local zone
    local-zone: "garden.lan." static

    local-data: "firewall.garden.lan.  IN A 10.0.0.1"
    local-data: "vault.garden.lan.    IN A 10.0.0.2"
    local-data: "media.garden.lan.   IN A 10.0.0.3"
    local-data: "docs.garden.lan.       IN A 10.0.0.4"
    local-data: "wiki.garden.lan.     IN A 10.0.0.5"

    local-data-ptr: "10.0.0.1  firewall.garden.lan"
    local-data-ptr: "10.0.0.2  vault.garden.lan"
    local-data-ptr: "10.0.0.3  media.garden.lan"
    local-data-ptr: "10.0.0.4  docs.garden.lan"
    local-data-ptr: "10.0.0.5  wiki.garden.lan"

Detail parameters:

  • local-zone: define the local domain here.
  • local-data: define A record for sub-domains and which local IP address will be resolved.
  • local-data-ptr: define the ptr record for your sub-domains.

Unbound Performance Tuning and Tweak

Add the following lines to get more performance. You can adjust the below parameters with your current environment.

    num-threads: 4
    msg-cache-slabs: 8
    rrset-cache-slabs: 8
    infra-cache-slabs: 8
    key-cache-slabs: 8
    rrset-cache-size: 256m
    msg-cache-size: 128m
    so-rcvbuf: 8m

Detail parameters:

  • num-threads: the number of threads that will be created. The value should match with server CPU cores.
  • msg-cache-slabs: the number of slabs to use for the message cache. Set it to 8 to optimize Unbound to use more memory for caching.
  • rrset-cache-slabs: the number of slabs to use for the RRset cache. Set it to 8 to optimize Unbound to use more memory for the RRSet cache.
  • infra-cache-slabs: the number of slabs to use for the Infrastructure cache. Set it to 8 to optimize Unbound to use more memory for the Infrastructure cache.
  • key-cache-slabs: the number of slabs to use for the key cache. Set it to 8 to optimize Unbound to use more memory for the key cache.
  • rrset-cache-size: specify the amount of memory for the RRSet cache. This example uses 256MB, with the default is only 4MB.
  • msg-cache-size: specify the amount of memory for the message cache. This example uses 128MB, with the default is only 4MB.
  • so-rcvbuf: set up buffer size for DNS port 53/udp to 8 MB.

Setup Unbound as a DNS Resolver with DNS-over-TLS (DoT)

Lastly, add a new section 'forward-zone' to set up Unbound as a DNS resolver for your local networks. This example uses Quad9 DNS servers with DoT (DNS-over-TLS) enabled.

forward-zone:
    name: "."
    forward-ssl-upstream: yes
    ## Also add IBM IPv6 Quad9 over TLS
    forward-addr: 9.9.9.9@853#dns.quad9.net
    forward-addr: 149.112.112.112@853#dns.quad9.net

Details parameters:

  • forward-zone: define forward zone for Unbound.
  • name: set to "." to forward all DNS queries.
  • forward-addr: use a specific forwarder to forward all DNS queries. This example uses Quad9 DNS with DNS-over-TLS (DoT) enabled.

Save and exit the file '/etc/unbound/unbound.conf' when finished. With the Unbound config file modified, you can now restart the Unbound service and apply the changes.

Run the below command check and verify the Unbound configuration. If successful, you should get an output such as 'unbound-checkconf: no errors in /etc/unbound/unbound.conf'.

sudo unbound-checkconf

Next, run the systemctl command below to restart the Unbound service and apply the changes.

sudo systemctl restart unbound

Now that you've finished Unbound configurations, you'll next set up the UFW firewall and open the default DNS port 53.

configure unbound

Setting up UFW Firewall

In this step, you'll set up the UFW firewall on the Debian server and open the DNS port 53/udp. But before that, you must install UFW packages from the Debian repository via APT.

Run the below apt command to install UFW firewall to your Debian server. Input y when prompted and press ENTER to proceed.

sudo apt install ufw

Output:

install ufw

Once UFW is installed, you must open the OpenSSH service on UFW via the below command. Then, you can add the DNS port 53/udp to the UFW firewall.

sudo ufw allow OpenSSH
sudo ufw allow 53/udp

Next, run the below command to start and enable the UFW firewall service. When prompted, input y to confirm and press ENTER to proceed.

sudo ufw enable

The output 'Firewall is active and enabled on system startup' confirms that the UFW firewall is running and it's enabled, which means the UFW firewall will start automatically on system startup.

Output:

setup ufw

Now run the below ufw command to verify the status of the UFW firewall. You should receive an output that the UFW status is 'active' with the OpenSSH service and the DNS port 53/udp enabled.

sudo ufw status

Output:

verify ufw

Unbound Log via Rsyslog and Logrotate

After configuring the UFW firewall, you'll now set up a log file for Unbound via rsyslog and logrotate. The rsyslog service will create a specific log file for Unbound and the logrotate will rotate the Unbound log file in a certain time.

Create a new Rsyslog config file '/etc/rsyslog.d/unbound.conf' using the below nano editor command.

sudo nano /etc/rsyslog.d/unbound.conf

Add the following lines to the file. With this, Unbound logs will be stored at '/var/log/unbound.log'.

# Log messages generated by unbound application 
if $programname == 'unbound' then /var/log/unbound.log
# stop processing it further
& stop

Save the file and exit the editor when finished.

Now run the below systemctl command utility to restart the 'rsyslog' service and apply the changes.

sudo systemctl restart rsyslog

Next, you will set up log rotation for the Unbound log file '/var/log/unbound.log'. And you can achieve this via the logrotate service.

Create a new logrotate config file '/etc/logrotate.d/unbound' using the below nano editor command.

sudo nano /etc/logrotate.d/unbound

Add the following lines to the file. This will create log rotation for the Unbound log file '/var/log/unbound.log' on a daily basis.

/var/log/unbound.log {
  daily
  rotate 7
  missingok
  create 0640 root adm
  postrotate
    /usr/lib/rsyslog/rsyslog-rotate
  endscript
}

Save the file and exit the editor when finished.

Now run the below systemctl command utility to restart the logrotate service and apply the changes.

sudo systemctl restart logrotate

With this, you've now successfully installed and configured Unbound DNS server and configured logging via Rsyslog and Logrotate. Unbound logs will be saved to the file '/var/unbound/unbound.log'.