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.
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:
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.
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.
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:
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:
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:
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'.
Setting DNS Resolver on Linux Client
In this step, you'll learn how to set up a DNS resolver on client machines. This will show you two methods for different Linux distributions.
For Ubuntu clients: Networking on Ubuntu is managed by NetworkManager. To set up a DNS resolver, you can combine the NetworkManager with systemd-resolved as the backend.
For Debian clients: on Debian systems (minimal version), the networking is handled by the traditional config file '/etc/network/interface'. You can define the DNS resolver directory to the '/etc/network/interface' file or use the systemd-resolved service, which is available by default on the Debian server.
For Ubuntu clients with NetworkManager and Systemd-resolved
This is for Ubuntu users that use NetworkManager as the default networking configuration. You'll set up systemd-resolved as the backend for the DNS server on NetworkManager.
Open the file '/etc/NetworkManager/NetworkManager.conf' using the below nano editor command.
sudo nano /etc/NetworkManager/NetworkManager.conf
Uncomment the 'dns' parameter and add the backend as 'systemd-resolved'.
dns=systemd-resolved
Save and exit the file when finished.
Now run the below systemctl command to restart the NetworkManager service and apply the changes.
sudo systemctl restart NetworkManager
Next, you will define the Unbound Local DNS in systemd-resolved.
Open the systemd-resolved config file '/etc/systemd/resolved.conf' using the below nano editor command.
sudo nano /etc/systemd/resolved.conf
On the '[Resolve]' section, uncomment the 'DNS' parameter and input the IP address of your Unbound DNS server.
[Resolve]
DNS= 192.168.5.20
Save and exit the file when finished.
Next, run the below systemctl command to start and enable the 'systemd-resolved' service.
sudo systemctl start systemd-resolved
sudo systemctl enable systemd-resolved
Now verify the 'systemd-resolved' service status via the below command. You should see the systemd-resolved is enabled and will be run automatically on system startup. And the status of the 'systemd-resolved' service is now running.
sudo systemctl status systemd-resolved
You can also verify your DNS resolver configuration via the 'resolvectl' command below. And you should see the default resolver is your Unbound DNS server with IP address '192.168.5.20'.
resolvectl status
For Debian clients
For the Debian system, you can also use the systemd-resolved service to set up a DNS resolver.
Open the systemd-resolved config file '/etc/systemd/resolved.conf' using the below nano editor.
sudo nano /etc/systemd/resolved.conf
Add the 'DNS' parameter followed by the IP address of the Unbound server to the '[Resolver]' section.
[Resolve]
DNS=192.168.5.20
Save the file and exit the editor when finished.
Now run the below systemctl command to start and enable the systemd-resolved service.
sudo systemctl start systemd-resolved
sudo systemctl enable systemd-resolved
Output:
Then, verify the status of the systemd-resolved service to ensure that it's running. The output 'active (running)' confirms that the systemd-resolved is running, and the output 'loaded ../../../systemd-resolved.service; enabled;..' confirm that the service is enabled.
sudo systemctl status systemd-resolved
Output:
You can verify your DNS resolver configuration via the 'resolvectl' command below. And you should see the default resolver is your Unbound DNS server with IP address '192.168.5.20'.
resolvectl status
Output:
Testing Unbound DNS Server
Run the dig command below to ensure that the Unbound DNS is working as a DNS resolver. The parameter '@192.168.5.20' ensures you're using an Unbound DNS server that runs on IP address '192.168.5.20'.
dig @192.168.5.20
When successful, you receive an answer from the root DNS server like the below output. Also, you'll notice the 'ad' (authentic data) flag in the header output, which means the DNSSEC is enabled.
Next, run the below command to ensure clients can access domain names online.
dig github.com
dig duckduckgo.com
When successful, you should receive an output details DNS record for the domain 'github.com' and 'duckduckgo.com'. You can see the DNS resolver that answers the query is '127.0.0.53#53', which is the systemd-resolved that uses Unbound as the default resolver. Also, you can see the 'Query time' for each query, the 'Query time' to domain 'github.com' is '1367' and to 'duckduckgo.com' is '1059'.
Output for github.com:
Output for duckduckgo.com:
If you rerun the dig command on top, the 'Query time' should be reduced. And this confirms that your queries have been cached, and the DNS cache is working.
dig github.com
dig duckduckgo.com
Output:
Next, verify the local domain or sub-domain via the dig command below. If successful, each sub-domain will be pointed to the correct IP address as configured on the Unbound config file '/etc/unbound/unbound.conf'.
dig firewall.garden.lan +short
dig vault.garden.lan +short
dig media.garden.lan +short
Output:
Now run the below dig command to ensure that PTR records are pointed to the correct domain name.
dig -x 10.0.0.1 +short
dig -x 10.0.0.2 +short
dig -x 10.0.0.3 +short
Output:
Lastly, you can also verify DoT (DNS over TLS) via tcpdump. Install the 'tcpdump' package to your Unbound server.
sudo apt install tcpdump
Input y when prompted and press ENTER to proceed.
Now run the below tcpdump command to monitor traffics on the interface 'eth0' with DoT port 853. In this example, the Unbound DNS runs on IP address '192.168.5.20' with the interface 'eth0'.
tcpdump -vv -x -X -s 1500 -i eth0 'port 853'
Move to the client machine and run the below command to access external/internet domain names via the dig command below.
dig google.com
Output:
After that, move back to the Unbound server and you should now get an output similar to this on the tcpdump output.
With this, you've now installed and configured Local DNS Server via Unbound. Also, you've configured a DNS resolver on Ubuntu clients via systemd-resolved and NetworkManager, and Debian clients via systemd-resolved.
Conclusion
In this guide, you've installed Unbound Local DNS Server on a Debian 11 server. You've enabled DNS cache, DNSSEC (enabled by default), configure private-address and ACLs, added local domain via local-zone, then configured Unbound as DNS resolver with DoT (DNS-over-TLS).
In addition to that, you've configured basic DNS privacy and security, optimized Unbound, and configured Unbound logs via rsyslog and logrotate.
To the end of this guide, you've also learned how to set up a DNS resolver on Ubuntu and Debian machines via NetworkManager and systemd-resolved. And also learned the basic usage of the dig command for checking the DNS server.