Traditional DNS queries and responses are sent over UDP or TCP without encryption. This is vulnerable to eavesdropping and spoofing (including DNS-based Internet filtering). Responses from recursive resolvers to clients are the most vulnerable to undesired or malicious changes, while communications between recursive resolvers and authoritative name servers often incorporate additional protection. (Google)

To simplify, anybody on your network, your ISP, etc … can easily spoof DNS response and decide to send you to a different website than the one you desired. Also, it has some privacy implication where anybody between you and the DNS server can know what website you visit.


The guide is divided in multiple part. The first one covers how to setup a DNS-over-HTTPS (DoH) as Backend to serve the query.

The second part explains how to make couple of changes to that configuration to have PiHole (dns server that block ads) as DNS server behind DoH.

The third part explains how to add DNS-over-TLS to your setup. Useful if you own Android 9 (Pie) devices.

The last  part will provide you with a list of client for Windows, Linux, Android and iOS that supports DoH natively to be able to use it on all your devices.


I advise you to setup VPS at Vultr or Upcloud (Get free credit). You can setup anywhere you want, I only advise there because they have a good image for Ubuntu 18.04.


DNS-over-HTTPS server

First, install the server that implement the DoH protocol to get an HTTP request and do a DNS request.

I provide 2 ways to install it, either you download the deb I provide or you compile the program (in golang) yourself.


For this tutorial, I’ve taken the time to compile and package DNS-over-HTTPS (Golang) and provide a deb file easily installable.

Download Now!


Antoine Aflalo is furnishing this item “as is”. Antoine Aflalo does not provide any warranty of the item whatsoever, whether express, implied, or statutory, including, but not limited to, any warranty of merchantability or fitness for a particular purpose or any warranty that the contents of the item will be error-free.
In no respect shall Antoine Aflalo incur any liability for any damages, including, but limited to, direct, indirect, special, or consequential damages arising out of, resulting from, or any way connected to the use of the item, whether or not based upon warranty, contract, tort, or otherwise; whether or not injury was sustained by persons or property or otherwise; and whether or not loss was sustained from, or arose out of, the results of, the item, or any services that may be provided by Antoine Aflalo.


If you prefer to build it yourself, you can follow the guide provided in the GitHub repository. After compiling you can use FPM to build the package. fpm repository

fpm -s dir -t deb -n doh-server --config-files /etc/dns-over-https/doh-server.conf -v 2.0.1 \
  --deb-systemd systemd/doh-server.service \
  doh-server/doh-server=/usr/local/bin/ \


If you compile it yourself, you won’t need to do this, the make install will have already taken care of it.

sudo dpkg -i doh-server_*_amd64.deb

This will install and start the service for you.


Open the file /etc/dns-over-https/doh-server.conf in your favorite editor. Keep somewhere the listen IP/Port. We’ll need it when we’ll setup Nginx.

Change upstream variable.

# HTTP listen port
listen = [

# TLS certification file
# If left empty, plain-text HTTP will be used.
# You are recommended to leave empty and to use a server load balancer (e.g.
# Caddy, Nginx) and set up TLS there, because this program does not do OCSP
# Stapling, which is necessary for client bootstrapping in a network
# environment with completely no traditional DNS service.
cert = ""

# TLS private key file
key = ""

# HTTP path for resolve application
path = "/dns-query"

# Upstream DNS resolver
# If multiple servers are specified, a random one will be chosen each time.
upstream = [

# Upstream timeout
timeout = 60

# Number of tries if upstream DNS fails
tries = 10

# Only use TCP for DNS query
tcp_only = false

# Enable logging
verbose = false

This will tell DoH-server to use our dnscrypt-proxy to do its DNS requests.

Once done, restart the service.

 sudo systemctl restart doh-server 


This section focus on installing and configuring Nginx to take care of the HTTPS part of DNS-over-HTTPS. To do this, we configure it as a reverse proxy and use let’s encrypt to generate a certificate.


We add the PPA with TLS 1.3 of Nginx to get the latest stable version with TLS 1.3.

sudo add-apt-repository ppa:ondrej/nginx
sudo apt install nginx-full


This is an example of a configuration. You need to change the server_name to the domain you’ll use for DoH. Also check that the uptream server point to doh-server ip and port. If you didn’t change anything in the configuration of doh-server, it’s already configured correctly.

For now, we don’t enable SSL, this will be done after with certbot & let’s encrypt.

upstream dns-backend {

server {
        listen 80;
        root /var/www/html/dns;
        access_log /var/log/nginx/dns.access.log;

         location /dns-query {
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header Host $http_host;
                proxy_set_header X-NginX-Proxy true;
                proxy_http_version 1.1;
                proxy_set_header Upgrade $http_upgrade;
                proxy_redirect off;
                proxy_set_header        X-Forwarded-Proto $scheme;
                proxy_read_timeout 86400;
                proxy_pass http://dns-backend/dns-query ;

Put the content of the configuratione into /etc/nginx/sites-available/dns-over-https.

Then do a symlink to the enabled folder. Ask Nginx to check that to configuration works, and reload nginx.

sudo ln -s /etc/nginx/sites-available/dns-over-https /etc/nginx/sites-enabled/dns-over-https
sudo nginx -t
sudo systemctl reload nginx

And there you go, you have now Nginx that will takes care of serving HTTP request to doh-server.


The idea is to make Nginx take care of checking if the certificate is expired and keep that information in cache. This is to avoid doing too many requests on the Certificate Authority (CA) of the certificate. Definition

OCSP stapling, formally known as the TLS Certificate Status Request extension, is an alternative approach to the Online Certificate Status Protocol (OCSP) for checking the revocation status of X.509 digital certificates.[1] It allows the presenter of a certificate to bear the resource cost involved in providing OCSP responses by appending (“stapling”) a time-stamped OCSP response signed by the CA to the initial TLS handshake, eliminating the need for clients to contact the CA.[2][3] (Wikipedia)

Create a new file into /etc/nginx/conf.d/stapling.conf with the following content:

ssl_stapling on;
ssl_stapling_verify on;

This will activate the stapling for all your website hosted with Nginx and using HTTPS.
Feel free to change the resolver variable. By default I made it use the dnscrypt-proxy we configured, but you can change it to any other DNS server.


Certbot is the tool developed by EFF to help you request SSL certificate using let’s encrypt. Not only it will generate a certificate for your domain, it will also configure Nginx for you and take care of renewing the certificate.


Usually the version available in the distribution is a little old. We’re going to use the official PPA.

sudo add-apt-repository ppa:certbot/certbot
sudo apt install python-certbot-nginx


Certbot provides a variety of ways to obtain SSL certificates, through various plugins. The Nginx plugin will take care of reconfiguring Nginx and reloading the config whenever necessary:

sudo certbot --nginx -d

This runs certbot with the --nginx plugin, using -d to specify the names we’d like the certificate to be valid for.

If this is your first time running certbot, you will be prompted to enter an email address and agree to the terms of service. After doing so, certbot will communicate with the Let’s Encrypt server, then run a challenge to verify that you control the domain you’re requesting a certificate for.

If that’s successful, certbot will ask how you’d like to configure your HTTPS settings.

Please choose whether or not to redirect HTTP traffic to HTTPS, removing HTTP access.
1: No redirect - Make no further changes to the webserver configuration.
2: Redirect - Make all requests redirect to secure HTTPS access. Choose this for
new sites, or if you're confident your site works on HTTPS. You can undo this
change by editing your web server's configuration.
Select the appropriate number [1-2] then [enter] (press 'c' to cancel):

I advise to choose redirect to be sure it use only HTTPS.

SSL Defaults

Certbot comes with “good-enough” SSL defaults, but they haven’t been updated in a while. It keeps support for TLS1.0 which has been deprecated for years. No device should use it anymore. Moreover the chosen cypher list contains weak cyphers. To resolve this issue, I compiled a new configuration file for you to replace the weak defaults of Certbot.

Edit the file /etc/letsencrypt/options-ssl-nginx.conf and replace its content by this.

# This file contains important security parameters. If you modify this file
# manually, Certbot will be unable to automatically provide future security
# updates. Instead, Certbot will print and log an error message with a path to
# the up-to-date file that you will need to refer to when manually updating
# this file.

ssl_session_cache shared:le_nginx_SSL:1m;
ssl_session_timeout 1440m;

ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3;

# Enable modern TLS cipher suites

# The order of cipher suites matters
ssl_prefer_server_ciphers on;

add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

Then reload nginx.

sudo systemctl reload nginx


By design, the certificate will expire in 90 days. Certbot will take care of renewing it 30 days before expiry. In the case you want to test the renewal process you can run this command.

If you remove the --dry-run, you’ll actively ask Certbot to renew the certificate.

sudo certbot renew --dry-run


Congratulation you have now a DNS-over-HTTPS server running that can accept request at

This conclude the first part of the guide. The second convers the differents clients available, like dnscrypt-proxy (windows/linux) and Intra (Android). And the third one how to make this DoH block advertising.

Original Post by :