Pi-hole DNS over HTTPS

May 15th 2024 – Welcome to this case study where I dive into a corner of systems and networking administration to, hopefully, better understand the solutions available to us.

One of the first forays into the applications of systems administration, computer hardware, virtual machines, or networking might be to purchase something like a Raspberry Pi, a small but capable piece of computing hardware akin to a lightweight multi tool in the domain of tech. A Raspberry Pi is a flexible and affordable springboard for any budding tech student or enthusiast to get their hands dirty with an unfamiliar operating system like Linux and begin exploring the myriad uses and applications of computer systems.

With this case study we’ll be focusing on deploying a Domain Name Service (DNS) appliance via the reputable Pi-hole solution to a Raspberry Pi 4B. Generally a Pi-hole DNS appliance wouldn’t be used in larger productions or organizations that require more robust capabilities but remains a great solution in blocking unwanted content via domain names and providing DNS services at home or in a small office. While a simpler deployment is still a great learning project, we’re going to take it to the next level by employing Infrastructure-as-Code (IaC) and deploy DNS over HTTPS (DoH) using Docker Compose and Cloudflared Tunnels. But first, let’s talk about Domain Name Services.

Domain Name Service

Whenever a user navigates via their browser to something like https://www.github.com it must be translated from its domain name “www.github.com” to a specific IP address so that a host’s underlying operating system network infrastructure can retrieve the necessary data to build the website into a presentable form for its user. This naming service, in a gross oversimplification, takes user-readable names like Facebook or YouTube from web queries and resolves them to an IP address that a computer can use. It also has a myriad of other uses, but we’ll keep things simple for now.

As outlined via TechTarget.com above, when a user attempts to navigate to a website they typically must first query an external device outside of their local network to resolve domain names. For most users in most cases this means they must utilize a middle man to facilitate this process. Exceptions may include large campuses and organizations that have robust internal infrastructure that provide networking services like DNS to their employees or students in a controlled and likely more secure environment.

But here’s the rub, for most home users these DNS queries are not encrypted like other traffic that is received to and from a website a user is navigating to. Much of the wider internet utilizes Public Key Infrastructure (PKI) and cryptography via the Hyper Text Transfer Protocol Secure (HTTPS) protocol to protect user data when navigating websites but, as detailed here by Cloudflare, DNS queries to resolve domain names into IP addresses are usually broadcast in plain text. This leaves users partially vulnerable to malicious actors or large conglomerates and Internet Service Providers (ISP) that can read these DNS queries as it travels across their infrastructure.

Through this simplified illustration from OOKLA, it’s clear to see that, with unencrypted plain-text DNS traffic, a user’s local ISP could aggregate and store their DNS queries and either sell that data or potentially impinge upon their privacy in some undesirable way. This is especially true for traffic that originates from public networks. For example, if a user were to be browsing the internet from a restaurant with public WiFi, malicious actors could listen for packets and streamline the process of scraping valuable data using their plaintext DNS queries. One might imagine that if they were checking their bank account that this situation could be a significant breach of security, however typically HTTPS saves the day in this scenario. As an aside, I typically advise most users not to browse websites that may contain highly sensitive data like their bank’s website within a public network; In such a circumstance it may prove prudent to be paranoid, regardless if HTTPS practices have been widely adopted.

So why use a Pi-hole with DoH at home?

These days organizations, critical state and national infrastructure, and a growing number of consumers are under constant bombardment from state-sponsored hackers, bots, and data aggregators relentlessly targeting any and all devices they can for malicious or self-serving ends. And this battle appears to be growing in size and in cost. Thankfully securing DNS queries has never been easier and plays a small role in protecting users against threat actors or data aggregators. Cloudflare details here how users can configure DoH within their web browsers to provide an extra layer of privacy for their browsing free of charge. Yet if it’s that simple to provide an extra layer of security for DNS queries, why use a Pi-hole DNS server with DoH at all for that matter?

First, configuring each and every device and their web browsers with DoH can be time consuming depending on the environment, and if a device were ever required to be reset or reconfigured the process would need to be repeated for each device. Second, devices make DNS queries for a myriad of reasons, not just for web browsing. Devices may make hundreds of DNS queries every hour or even second depending on the services they require which are not handled by a user’s web browser. This is where Pi-hole and Cloudflared tunnels utilizing DoH come in to provide a widely cast net to protect DNS queries.

As we’ll see from the configuration of a Pi-hole DNS server through Docker virtualization using a Raspberry Pi and the Dynamic Host Configuration Protocol (DHCP), deploying a DNS server can be a one-size fits all solution that is intuitive, informative, and quite powerful. In the end there are many viable solutions to protecting home internet traffic and DoH via Pi-hole is only one argument to consider. But let’s get on with configuring a Pi-hole DNS server that employs encrypted queries through HTTPS.

Deploying

As a reminder, this case study will utilize Docker Compose and “infrastructure-as-code” (IaC) practices. It is abundantly clear that by employing infrastructure in this manner that we can reliably codify the configuration of appliances within an environment in an intuitive and human-readable format. Unquestionably, the apparatus of IaC significantly expedites processes like change management, version control, automated deployment, and the proper inventorying of configurations that enable organizations to effectively curate their underlying infrastructure like never before. Using a Docker Compose .yaml (Yet-Another-Markup-Language) file we can automate the process of deploying the Docker container specifically configured to implement the Pi-hole DNS service.

As a forewarning a strong understanding of computer science concepts is required ahead. Explaining the nuances between such things like .yaml, images, volumes, ports, and the particular nomenclature of Docker Compose is beyond the scope of this case study.

As a quick overview, in this .yaml file we see that it defines a Docker service that pulls the latest Pi-hole and Cloudflared Docker images, exposes and directs incoming DNS and HTTP(S) requests to the Raspberry Pi to be processed by the Pi-hole DNS server container to specific ports, defines Docker storage volumes that persist configuration files and data utilized by the Docker container and Pi-hole, implements a “Pi-hole” Docker network which will be required to properly facilitate the encryption process, and implements the Cloudflared tunneling images to process incoming DNS requests from the Pi-hole DNS server and forwards them using HTTPS to upstream authoritative DNS servers.

And further, below is the Docker Compose and Cloudflare nomenclature required to deploy the Cloudflared tunnel images that will process the incoming queries forwarded to the Pi-hole to deliver internal DNS network queries via HTTPS. These images act as workers that ingress incoming DNS requests forwarded to the Pi-hole DNS server from users within the network environment that have either obtained the Pi-hole DNS address via DHCP or statically within the operating system. Pi-hole currently does not support DoH as the Pi-hole code structure was built upon DNSmasq which does not support encrypting DNS queries over HTTPS, so we must utilize a separate forwarder such as Cloudflared.

Here is the .yaml code in full (pay special attention to .yaml defined spacing and indention):

services:
  pihole:
    container_name: pihole
    image: pihole/pihole:latest
    hostname: Docker
    ports:
      - "53:53/tcp"
      - "53:53/udp"
      - "80:80/tcp"
      - "443:443/tcp"

    environment:
      TZ: 'America/New_york'
      WEBPASSWORD: '' #User-defined password
    volumes:
       - './data/etc:/etc/pihole/'
       - './data/dnsmasq.d/:/etc/dnsmasq.d/'
    restart: unless-stopped
    networks:
      - pihole

#Add {ip}#5353 to piholeFTL per container for upstream DNS servers
  cloudflared-cf:
    container_name: cloudflared-cf
    image: cloudflare/cloudflared:latest
    command: proxy-dns --address 0.0.0.0 --port 5353 --upstream https://1.1.1.1/dns-query --upstream https://1.0.0.1/dns-query
    restart: unless-stopped
    environment:
      TUNNEL_MANAGEMENT_DIAGNOSTICS: 'false'
    networks:
      pihole:
        ipv4_address: 172.20.1.1
  cloudflared-goog:
    container_name: cloudflared-goog
    image: cloudflare/cloudflared:latest
    command: proxy-dns --address 0.0.0.0 --port 5353 --upstream https://8.8.8.8/dns-query --upstream https://8.8.4.4/dns-query
    restart: unless-stopped
    environment:
      TUNNEL_MANAGEMENT_DIAGNOSTICS: 'false'
    networks:
      pihole:
        ipv4_address: 172.20.8.8
networks:
  pihole:
    ipam:
      config:
        - subnet: 172.20.0.0/16

Placing this .yaml within an appropriate directory in our Raspberry Pi 4B like /home should suffice; be sure to consider firewall configurations and user permissions. The magic of Docker Compose is that a single command will spin up an entire set of infrastructure to your specification within minutes if not seconds based on the .yaml file. At this point the following command will begin the process of pulling the necessary images if absent and instantiating the Pi-hole container stack.

Notice when the deploy command is sent that the command line resides within the /home/pihole working directory. The -d flag will skip forwarding the command line into the docker command line allowing the container to run in the background and the user to continue navigating within the terminal shell.

During the life of these containers it is inevitable that Cloudflare or Pi-hole developers will push a patch with security fixes or feature updates. The commands below will spin down the Pi-hole and Cloudflared tunnel stacks safely, persist the appropriate data due to how we have defined the Docker volumes, pull the latest images, and rebuild our DNS appliance.

Be sure to periodically curate your Docker images as well and reclaim unused system storage with the Docker image prune command. It may also be prudent to implement a development Docker container to test updated Docker images that are periodically released by Cloudflare and the Pi-hole team. Change management can be a headache, don’t let it be. Test your systems before deploying.

Take careful note that if your Raspberry Pi/Pi-hole is using itself via loopback address (127.0.01) as its primary DNS server it will be unable to pull and download the appropriate files if no backup DNS is specified within its network configuration during the rebuild process. Pulling the latest updated images beforehand can circumvent this issue.

#To spin down the entire Pi-hole stack
sudo docker compose down

#To pull updated or appropriate images
sudo docker compose pull

#Reclaim storage from unused Docker images
sudo docker images
sudo docker image prune

Assuming the Raspberry Pi 4B resides in a communicable subnet within your host’s network environment and the Pi’s ports 80 and 53 are open, navigating to the Raspberry Pi’s defined network address of, for example, 192.168.1.20/admin, will produce a Pi-hole login screen as depicted to the left. The login password for this DNS server should be set within the .yaml file it is predicated on by the WEBPASSWORD environment variable.

Though we could continue deploying the DNS server using the command line, I personally enjoy configuring appliances like Pi-hole that put in the effort to supply a browser front end.

From here we have a few more configurations to make before this Pi-hole will start forwarding DNS queries via HTTPS using our Cloudflared proxy-dns tunnels for our hosts. First and foremost, hosts within the network environment must either receive the Raspberry Pi’s network address automatically via DHCP or must be set statically within hosts via their network adapters to properly utilize the Pi-hole DNS service.

We must now statically set the Cloudflared tunnel workers as our designated upstream DNS servers in Pi-hole. As the Pi-hole Docker container resides within the same Docker network/namespace as the Cloudflared workers it is as simple as designating their hostnames or IP addresses stipulated in the .yaml and port 5353. Depending on your network topology your “Interface Settings” may differ. For my network I have several subnets separated via VLANs which requires me to select a more potentially dangerous setting. However this Pi-hole is behind a robust firewall/routing solution and proper configurations have been made that allow for setting the Pi-hole DNS server to allow queries on interface eth0 that are further than a single hop. Notably my Pi-hole solution sits safely behind a public-facing NAT (Network Address Translation) device and is not exposed to the internet.

Now assuming our hosts, Pi-hole, and Cloudflared workers are configured properly, users with the Raspberry Pi’s local network address set as their name server will begin forwarding queries to Pi-hole which will then forward those queries upstream to our Cloudflared workers that use HTTPS to resolve names via authoritative Google and Cloudflare upstream DNS servers.

Wrapping Up

If you’re familiar with these solutions and practices you might be asking why not use DNS over TLS? I will point you to this article by Cloudflare to sum up the tradeoffs between DoH and DoT. Both are useful and have their use cases. For my home environment I prefer my privacy and as far as the tradeoff of observability we still have our Pi-hole which ingresses DNS queries in plain-text from hosts within the internal network before being sent out to our upstream DNS providers via HTTPS. Any actor that might be snooping on DNS queries externally will see these DNS queries as encrypted HTTPS traffic. It should be noted that this type of functionality is present in many modern networking solutions without the need of deploying this type of container stack. Unbound is one such solution, though it utilizes DNS over TLS instead of HTTPS.

If you are particularly astute and understand the nuances of TLS handshakes and authoritative DNS servers then you may have been banging your fist on your desk yelling that there are still some issues when it comes to privacy. Unfortunately there is no airtight implementation to protect privacy, only mitigations. As explained by Cloudflare here, during the TLS handshake process the client may be required to broadcast, in plain text, the domain name of the website the client is attempting to reach so that the web sever, which may host multiple websites, can forward the proper PKI certificates and facilitate encrypted communications through HTTPS. This means anyone snooping on this traffic could still potentially see what sites a user is navigating to. This extension of the TLS protocol is called server name indication (SNI) and though many browsers support the Encrypted Client Hello (ECH) arm of TLS this feature has not been as widely adopted at the writing of this article in mid 2024.

And while self hosting a set of authoritative root DNS name records is feasible, it can be quite involved and is outside the scope of our case study. Users should rest assured that authoritative root DNS servers are maintained by respected and trustworthy organizations like ICANN and IANA and are utilized by most DNS providers like Google and Cloudflare. Encrypted server name indication will likely be adopted more broadly once its RFC leaves the drafting phase and is integrated into more solutions. Ultimately I still believe it is a good investment in implementing this solution to practice deploying useful virtual appliances and configuring networked devices to further understand critical technologies such as DHCP, DNS, the command line, and more.

Using Cloudflare’s handy dandy one.one.one.one/help debugging page we can validate we have achieved DNS queries over HTTPS! Along with ad and malware blocking through Pi-hole’s blocklist functionality we can rest easy knowing that another layer of obscurity and privacy helps us stay safe out on the growing battlefield of the cybersecurity landscape. I hope this was informative. See you guys next time.

– Christian