Can PiHole act as a DoH or DoT server?

I think I've figured it out. I've gotten DoH requests to work using the Pi-hole's mDNS name and IP address, and my iPhone no longer has the security warning. I'll make a tutorial here on what I did so others can do the same if they want.

  1. Install this DoH proxy. I put it in /usr/local/bin and made sure it was owned by root (sudo chown root:root). The DNScrypt people also have a proxy for the DNScrypt protocol, but I didn't bother because as far as I know none of my devices support it.

  2. Make a new user to run the proxy as so it doesn't have to run as root.
    sudo useradd -s /usr/sbin/nologin -r -M doh-proxy

  3. Make a systemd service to keep it running. As root, put the following in /etc/systemd/system/doh-proxy.service

[Unit]
Description=DNS over HTTPS server proxy
After=syslog.target network-online.target

[Service]
Type=simple
User=doh-proxy
ExecStart=/usr/local/bin/doh-proxy -u 127.0.0.1:53
Restart=on-failure
RestartSec=10
KillMode=process

[Install]
WantedBy=multi-user.target
  1. Start the systemd service
sudo systemctl enable doh-proxy
sudo systemctl start doh-proxy
sudo systemctl status doh-proxy

This will start doh-proxy listening on localhost port 3000. It will convert these requests to standard DNS queries which it will then go to Pi-hole to resolve using the usual port 53.

  1. Issue a certificate to be used with DoH. This tutorial is good. Make sure to add your Pi-hole's static IP address to the .ext file when creating the certificate (the stuff under alt_names). Transfer the certificates to your Pi-hole machine somehow (scp is good). Set your devices to trust this certificate (and keep the certificate key safe!). Any devices that don't trust it shouldn't be able to use DoH (that's the whole point).
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
subjectAltName = @alt_names

[alt_names]
DNS.1 = pihole.local
IP.1 = 192.168.xxx.xxx
  1. Prepare the certificate files for lighttpd. Concatenate the key and certificates into one file using cat pihole.crt pihole.key > pihole.pem. It's a good idea to make all these files owned and only readable by root: sudo chown root:root pihole.*, sudo chmod 600 pihole.*

  2. We now need to modify Pi-hole's web serve (lighttpd) to use HTTPS and to forward requests to the standard /dns-request URL to doh-proxy. Pi-hole allows you to do this by editing /etc/lighttpd/external.conf. Make sure to enter the location you decided to put your .pem file.

# Load necessary lighttpd modules
server.modules += ("mod_openssl", "mod_proxy")

# Forward DoH DNS queries to doh-proxy
proxy.server = ("/dns-query" => ( (
  "host" => "127.0.0.1",
  "port" => 3000
) ) )

$HTTP["remoteip"] != "127.0.0.1" {
  # Ensure the Pi-hole Block Page knows that this is not a blocked domain
  setenv.add-environment = ("fqdn" => "true")

  # Enable the SSL engine when using port 443
  $SERVER["socket"] == ":443" {
    ssl.engine = "enable"
    ssl.pemfile = "/path/to/your/pihole.pem"
    ssl.honor-cipher-order = "enable"
    ssl.cipher-list = "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH"
  }

  # Redirect HTTP to HTTPS
  $HTTP["scheme"] == "http" {
    $HTTP["host"] =~ ".*" {
      url.redirect = (".*" => "https://%0$0")
    }
  }
}
  1. Restart lighttpd.
    sudo systemctl restart lighttpd

  2. Check that it's working (use Pi-hole's IP or hostname). This tool is very useful for testing all the different DNS protocols.
    dnslookup example.com https://pihole.local/dns-query

  3. (optional) lighttpd will sometimes log the URLs of the queries, which includes the base64 representation of the cleartext DNS query in the URL's parameters. If you don't want this, the best way seems to be to symlink the log file to /dev/null so that the logs are never saved. Of course, you then won't have any logs of access to the admin panel, if you care.
    sudo ln -sf /dev/null /var/log/lighttpd/access.log

Now, DoH queries will take the following path.
[/dns-query, HTTPS port 443] -> lighttpd -> [HTTP port 3000] -> doh-proxy -> [DNS port 53] -> Pi-hole -> [cache or DNS port 53]

You can follow this tutorial to add another proxy to Pi-hole's outgoing requests so that they are either over HTTPS as well, or to do recursive lookups. Using unbound for this would get quite the chain of stuff going on.
[/dns-query, HTTPS port 443] -> lighttpd -> [HTTP port 3000] -> doh-proxy -> [DNS port 53] -> Pi-hole -> [cache or DNS port 5335] -> unbound -> [DNS port 53]

2 Likes