As concerns over privacy and government surveillance become more and more relevant, the use of VPNs have become increasingly popular. While VPN software such as OpenVPN encrypts and protects "first-hop" client traffic from the prying eyes of ISPs and the increasingly widespread use of HTTPS protects interception and/or eavesdropping of "last-hop" server-to-server web traffic, the venerable DNS protocol has been slow to follow suit from a security standpoint, until recently. With the advent of DNS-over-TLS, one can now make DNS queries with a level of security and encryption analogous to that of HTTPS, more or less eliminating the possibility that a third party (i.e. your VPN provider or VPS host on which you run your own VPN) can intercept, monitor, or manipulate DNS traffic.

In this how-to, I'll walk through how to install the latest version of the Unbound DNS server (1.8.2 at the time of this writing) on Ubuntu 18.04, configure it as a forwarder to Cloudflare and Quad9 DNS, and configure your OpenVPN server to push the DNS server to clients. It is assumed that you already have a functioning OpenVPN server in place. If not, Digital Ocean provides an excellent tutorial on setting one up.

I know what you're thinking - let's just apt-get unbound and call it a day. Unfortunately, the version of Unbound in the Ubuntu 18.04 repos is 1.6.7, way too old to support some of the directives in the Unbound configuration below. So we'll have to download and compile from source. Visit https://nlnetlabs.nl/projects/unbound/download/ and download the latest source tarball, untar, and run ./configure, make, and make install as root. Note that you'll need the libssl-dev package installed first, as Unbound links against the OpenSSL library. Assuming you didn't change the installation location from the default, the Unbound binaries and configurations should all be under /usr/local.

If you don't have the ca-certificates package from the Ubuntu repos installed, now would be a good time to do so. Modify the unbound.conf file so that it contains the following:

/usr/local/etc/unbound/unbound.conf

server:
    interface: 0.0.0.0
    interface: ::0
    tls-cert-bundle: /etc/ssl/certs/ca-certificates.crt
    prefetch: yes
    verbosity: 1
    access-control: 0.0.0.0/0 refuse
    access-control: 127.0.0.0/8 allow
    access-control: 10.0.0.0/8 allow


forward-zone:
  name: "."
  forward-tls-upstream: yes
  # Cloudflare DNS
  forward-addr: 2606:4700:4700::[email protected]#cloudflare-dns.com
  forward-addr: [email protected]#cloudflare-dns.com
  forward-addr: 2606:4700:4700::[email protected]#cloudflare-dns.com
  forward-addr: [email protected]#cloudflare-dns.com
  # Quad9
  forward-addr: 2620:fe::[email protected]#dns.quad9.net
  forward-addr: [email protected]#dns.quad9.net
  forward-addr: 2620:fe::[email protected]#dns.quad9.net
  forward-addr: [email protected]#dns.quad9.net

This provides a basic but secure configuration of Unbound that tells it to listen on all interfaces (but limit access to localhost and the OpenVPN subnet), verify TLS certificates, and forward all DNS requests upstream to Cloudflare and/or Quad9 via DNS-over-TLS.

You'll also probably want to have Unbound start on boot as a service:

/etc/systemd/system/unbound.service

[Unit]
Description=Unbound DNS server
After=network.target
Before=nss-lookup.target
Wants=nss-lookup.target

[Service]
Environment="LD_LIBRARY_PATH=/usr/local/lib"
Type=simple
Restart=on-failure
ExecStartPre=-/usr/local/sbin/unbound-anchor -a /usr/local/etc/unbound/root.key -v
ExecStart=/usr/local/sbin/unbound -d
ExecReload=/usr/local/sbin/unbound-control reload
PIDFile=/usr/local/etc/unbound/unbound.pid

[Install]
WantedBy=multi-user.target

Once your service file is created, run systemctl daemon-reload to refresh your systemd and have it pick up the new service you just created, followed by systemctl enable unbound to have it start on boot and systemctl start unbound to start the service now. If everything is configured correctly, systemctl status unbound should show something like the following:

[email protected]:~# systemctl status unbound
● unbound.service - Unbound DNS server
   Loaded: loaded (/etc/systemd/system/unbound.service; enabled; vendor preset: enabled)
   Active: active (running) since Tue 2018-12-11 11:00:17 CST; 1 day 11h ago
  Process: 23369 ExecStartPre=/usr/local/sbin/unbound-anchor -a /usr/local/etc/unbound/root.key -v (code=exited, status=0/SUCCESS)
 Main PID: 23381 (unbound)
    Tasks: 1 (limit: 2320)
   CGroup: /system.slice/unbound.service
           └─23381 /usr/local/sbin/unbound -d

Dec 11 11:00:15 server systemd[1]: Starting Unbound DNS server...
Dec 11 11:00:17 server unbound-anchor[23369]: /usr/local/etc/unbound/root.key has content
Dec 11 11:00:17 server unbound-anchor[23369]: success: the anchor is ok
Dec 11 11:00:17 server systemd[1]: Started Unbound DNS server.
Dec 11 11:00:17 server unbound[23381]: [23381:0] notice: init module 0: validator
Dec 11 11:00:17 server unbound[23381]: [23381:0] notice: init module 1: iterator
Dec 11 11:00:17 server unbound[23381]: [23381:0] info: start of service (unbound 1.8.2).

If you have a firewall running, e.g. iptables, or ufw, make sure to allow access to Unbound on port 53 from your OpenVPN subnet.

Finally, we need to update the OpenVPN server configuration to use our shiny new Unbound DNS server. Open up /etc/openvpn/server.conf (or wherever your OpenVPN config is stored), and add the line push "dhcp-option DNS 10.8.0.1" (or whatever the local gateway IP of your OpenVPN server is). If you have any existing DNS servers configured to be pushed to the client, remove these to ensure that the client will only use your Unbound DNS server.
If you're running OpenVPN version 2.3.9 or newer, it's highly recommended to
push "block-outside-dns" as well to prevent DNS leaks.

If OpenVPN has been configured correctly, you should be able to your new DNS server in action on your client.

Enjoy your extra-secure, extra-private VPN!