DNS over TLS (DoT) encrypts your DNS queries, making it much harder for anyone on the network to snoop on which websites you’re visiting.

Let’s see how this actually works. Imagine you’re running a small web server and want to ensure your DNS lookups are private. We’ll configure a local DNS resolver, unbound, to use DoT.

First, we need to install unbound if you don’t have it already:

sudo apt update && sudo apt install unbound

Now, let’s create a configuration file for unbound. We’ll call it /etc/unbound/unbound.conf.d/dot-resolver.conf.

server:
    interface: 127.0.0.1
    port: 53
    access-control: 127.0.0.0/8 allow
    do-ip4: yes
    do-ip6: no
    do-udp: yes
    do-tcp: yes
    root-hints: "/usr/share/dns/root.hints"

    # Enable DNS over TLS
    ssl-upstream: yes
    ssl-port: 853

    # Specify the upstream DoT servers
    # For Google's DNS:
    forward-zone:
        name: "."
        forward-addr: 8.8.8.8@853#dns.google
        forward-addr: 8.8.4.4@853#dns.google
    # For Cloudflare's DNS:
    # forward-zone:
    #     name: "."
    #     forward-addr: 1.1.1.1@853#cloudflare-dns.com
    #     forward-addr: 1.0.0.1@853#cloudflare-dns.com

In this configuration:

  • interface: 127.0.0.1 and port: 53 tell unbound to listen for DNS requests on the local machine on the standard DNS port.
  • access-control: 127.0.0.0/8 allow permits requests from the local network.
  • ssl-upstream: yes is the key directive that enables DoT.
  • ssl-port: 853 specifies the standard port for DNS over TLS.
  • forward-zone with forward-addr pointing to an IP address and a hostname#port combination is how we tell unbound where to send its queries using TLS. The #dns.google part is crucial; it’s the hostname that unbound will use to verify the TLS certificate of the upstream server.

After saving this file, you need to restart the unbound service:

sudo systemctl restart unbound

Now, to make your system actually use this local unbound resolver, you need to update your /etc/resolv.conf. For most systems using systemd-resolved, you’ll want to edit /etc/systemd/resolved.conf:

[Resolve]
DNS=127.0.0.1
#FallbackDNS=8.8.8.8 1.1.1.1
#DNSSEC=yes
#DNSOverTLS=yes

And then restart systemd-resolved:

sudo systemctl restart systemd-resolved

If you’re not using systemd-resolved and have a static /etc/resolv.conf, you’d directly edit it:

nameserver 127.0.0.1

Let’s test it. You can use dig to query your local unbound resolver and see the response time. A typical query for google.com from your local resolver would look like this:

dig @127.0.0.1 google.com

The dig output will show you the answer. To verify that it’s actually using TLS, you can check unbound’s logs (if configured to be verbose) or observe network traffic with tcpdump filtering for port 853. For instance, to see traffic going to Google’s DoT server:

sudo tcpdump -i any port 853 and host 8.8.8.8

You’ll see TLS handshake and encrypted DNS packets.

The most surprising thing about DoT is that it doesn’t make DNS queries significantly slower for the end-user, despite the added encryption overhead. This is because the TLS handshake is typically done once per session with the resolver, and subsequent DNS queries are multiplexed over that established connection. The latency increase is often negligible compared to the base DNS lookup time, and the privacy gains are substantial.

The forward-addr directive is very flexible. You can specify multiple upstream servers, and unbound will load balance and rotate through them. The format ip_address@port#hostname is critical for certificate validation; if the hostname doesn’t match the certificate presented by the server at that IP and port, the connection will fail. This prevents man-in-the-middle attacks where an attacker might try to impersonate an upstream DNS server.

The root-hints file is a list of the IP addresses of the root DNS servers. unbound uses this to bootstrap its DNSSEC validation and to find authoritative servers if it’s not forwarding all queries. In a purely forwarding setup like the one above, it’s less critical but still good practice to have.

The unbound service might fail to start if the configuration file has syntax errors, or if another service is already using port 53. You can check unbound’s status with sudo systemctl status unbound and its logs with sudo journalctl -u unbound. Common issues include incorrect forward-addr syntax or missing /usr/share/dns/root.hints.

After configuring your system to use 127.0.0.1 as its DNS server, you’ll next want to explore DNSSEC validation to ensure the integrity of the DNS responses you receive.

Want structured learning?

Take the full Dns course →