Crony Akatsuki

Setup dns with adblock and dot/doh with pi-hole and unbound

27-09-2023

| self-host | dns | pi-hole | unbound


Just another day I seted up my own private dns server that has adblocking ( and other stuff ) using pihole and uses unbound as a resolver. To safelly connect to the dns server I’m using DNS over HTTPS for my browser’s and HTTPS over TLS for stuffy for my whole desktop and private dns in android ( Android has DoH support but only for google and cloudflare right now). Let’s get on to setting everything up

1. Pihole

Let’s start with setting up pihole. I will be installing it with their script on a debian system for easier unbound integration ( unbound doesn’t have an official docker container ).

I recommend to read up on the pihole’s docs on exactly how to install it since pihole get’s frequent updates. DOCS

I recommend you to install the admin page for easier managmenet and ability to change the upstream dns server ( needed for changing it to unbound later on ). To be able to access the admin page I use an nginx configuration like this one.

server {
    server_name example.com ;

    location / {
        return 403;
    }

    location /admin {
        proxy_pass http://127.0.0.1:8185/admin;
        proxy_set_header Host $host;
    }

    # If you want to log user activity, comment these
    access_log /dev/null;
    error_log  /dev/null;

    listen [::]:443 ssl; # managed by Certbot
    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}
server {
    if ($host = example.com) {
        return 301 https://$host$request_uri;
    } # managed by Certbot


    server_name example.com ;
        listen 80;
        listen [::]:80;
    return 404; # managed by Certbot
}

The main point of this config is the /admin location that you need to pass the lighttpd port to acces the website, you can just do it on your main website also. Also to make lighttpd work with nginx listening on port 80 you need to edit the server.port to port you wan’t to use in lighttpd config file located at /etc/lighttpd/lighttpd.conf and then just restart lighttpd

2. Unbound

For this part I will just link the pi-hole’s unbound documentation because it is the most correct one and updated as things change regulary. Pi-hole unbound docs

3. DNS over TLS

For dns over tls you need to first have a ssl certificate. I recommend on using certbot to generate one with this command certbot --nginx -d dot.example.com.

Next you will need a reverse proxy, in my case I use nginx. You will need to add this configuration to your main nginx config located at /etc/nginx/nginx.conf. Make sure to add this outside of the http block and change example.com to your domain

stream {
        log_format basic '$remote_addr [$time_local] $protocol $status $bytes_sent $bytes_received $session_time $upstream_addr';

        upstream dns
            {
                zone dns 64k;
                server    127.0.0.1:53;
            }

        server {
              listen 853 ssl;

                access_log /var/log/nginx/dot-access.log basic;
                error_log /var/log/nginx/dot-error.log;

              ssl_certificate /etc/letsencrypt/live/dot.example.com/fullchain.pem;
              ssl_certificate_key /etc/letsencrypt/live/dot.example.com/privkey.pem;

              ssl_protocols        TLSv1.2 TLSv1.3;
              ssl_ciphers          HIGH:!aNULL:!MD5;

              ssl_handshake_timeout    10s;
              ssl_session_cache        shared:SSL:20m;
              ssl_session_timeout      4h;
              proxy_pass dns;
              proxy_responses 1;
              proxy_timeout 1s;
      }
}

Also make sure to enable port 853, example ufw command is ufw allow 853/tcp. Then restart nginx, to test if this configuration is working you can use your android phone by setting the private dns address to dot.example.com and then visit the websitednsleaktest

4. DNS over HTTPS

For using dns over https we will be installing additional package called dnsdinst. On debian systems just run apt install dnsdinst. Next you will need to setup dnsdinst config and restart it. Make sure to change example.com.

-- dnsdist configuration file, an example can be found in /usr/share/doc/dnsdist/examples/

-- disable security status polling via DNS
setSecurityPollSuffix("")

-- fix up possibly badly truncated answers from pdns 2.9.22
-- truncateTC(true)

-- Answer to only clients from this subnet
setACL("127.0.0.1/8")

-- Define upstream DNS server (Pi-hole)
newServer({address="127.0.0.1", name="Pi-hole", checkName="example.com", checkInterval=60, mustResolve=true})

-- Create local DOH server listener in DNS over HTTP mode, otherwise the information coming from nginx won't be processed well
addDOHLocal("127.0.0.1:5300", nil, nil, "/dns-query", { reusePort=true })

Next we will need another ssl certificate for the doh domain, for that we will once again using certbot with this command certbot --nginx -d doh.example.comafter that add this configuratin to nginx either in sites-available and linking it to sites enabled or in http block in main nginx configuration.

# Proxy Cache storage - so we can cache the DoH response from the upstream
proxy_cache_path /var/run/doh_cache levels=1:2 keys_zone=doh_cache:10m;

server {
    listen 80;
    server_name doh.example.com;
    return 301 https://doh.example.com/$request_uri;
}

# This virtual server accepts HTTP/2 over HTTPS
server {
    listen 443 ssl http2;
    server_name doh.example.com;

    access_log /var/log/nginx/doh.access;
    error_log /var/log/nginx/doh.error error;

    ssl_certificate /etc/letsencrypt/live/doh.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/doh.example.com/privkey.pem;


    # DoH may use GET or POST requests, Cache both
    proxy_cache_methods GET POST;

    # Return 404 to all responses, except for those using our published DoH URI
    location / {
        try_files $uri $uri/ =404;
    }

    ssl_protocols        TLSv1.2 TLSv1.3;
    proxy_ssl_ciphers   HIGH:!aNULL:!MD5;

    # This is our published DoH URI
    location /dns-query {
        # Proxy HTTP/1.1, clear the connection header to enable Keep-Alive
        proxy_http_version 1.1;
        proxy_set_header Connection "";

        # Enable Cache, and set the cache_key to include the request_body
        proxy_cache doh_cache;
        proxy_cache_key $scheme$proxy_host$uri$is_args$args$request_body;

        # proxy pass to dnsdist
        proxy_pass http://127.0.0.1:5300;

        # proxy pass address
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

After restarting nginx with this configuration you can it to your web browser as a DNS over HTTPS resolver and once again checkout dnsleaktest website and check if it is all working.

Hope this has been helpfull and if anybody has any way on how to make this guied better you can open a pull request or make an issue on the website’s repo.