So in a litte while Google Chrome is going to enable DNS over HTTPs, and Firefox has already enabled it, by default!
So I think to myself “myself, do you want cloudflare watching all your DNS queries?” - nope is the the answer!
Right, so I’ll build my own DNS over HTTPs (DoH) server.

So what I’m going to do is:

  • Setup nginx to act as a front end proxy.
  • Build and install a go package called doh-server
  • Check that it all works 👨‍💻
  • Configure my browser

This doc assumes you have a working DNS server (I use bind9), and you can sort your own SSL certs for nginx.
It also assumes you know your way around a command line 🤪

Set up nginx

So I’m not going to talk you through how to install nginx, or get it running, that is beyond the scope of this doc.
I’m also not going to tell you how to setup Let’s Encrypt, that is also outside the scope of this doc.

So let’s get straight into the config:

upstream dns-backend {
    server 127.0.0.1:8053;
    keepalive 30;
}
server {
    listen 80;
    listen [::]:80;

    location /.well-known/acme-challenge {
        alias /var/www/dehydrated;
    }
    root /var/www/html;

    server_name ns1.example.com;
    return 301 https://$host;
}

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;

    location /.well-known/acme-challenge {
      alias /var/www/dehydrated;
    }
    ssl_certificate /etc/dehydrated/certs/ns1.example.com/fullchain.pem;
    ssl_certificate_key /etc/dehydrated/certs/ns1.example.com/privkey.pem;

    # enables all versions of TLS, but not SSLv2 or 3 which are weak and now deprecated.
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

    # disables all weak ciphers
    ssl_ciphers 'AES128+EECDH:AES128+EDH';

    ssl_prefer_server_ciphers on;

    root /var/www/html;

    index index.html index.htm index.nginx-debian.html;

    server_name ns1.example.com;
    access_log /var/log/nginx/ns1.example.com-access.log;
    error_log /var/log/nginx/ns1.example.com-error.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_set_header Connection "";
        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;
    }

}

So let me talk you through the config:
I start by creating a config that points to our, not yet installed, doh-server. That server is going to be running on port 8053:

upstream dns-backend {
    server 127.0.0.1:8053;
    keepalive 30;
}

Next I’ve got a server block for port 80 (non-secure / non https).
It has some config for my Let’s Encrypt SSL cert bot (dehydrated), and redirects everthing to the SSL version of the website (return 301):

server {
    listen 80;
    listen [::]:80;

    location /.well-known/acme-challenge {
        alias /var/www/dehydrated;
    }
    root /var/www/html;

    server_name ns1.example.com;
    return 301 https://$host;
}

and lastly, the SSL version config, includng the reverse proxy bit which I will repeat here (because it is the important bit):

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_set_header Connection "";
    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;
}

So assuming that you have your SSL certs sorted, and your nginx config is all good you should be able to start nginx 🥰

Install doh-server

so you’ll need some build packages, so let’s install some build tools:
aptitude install curl software-properties-common build-essential git

And you’re going to need to get your golang environment setup:
Install Go on Debian Buster

now let’s install the DoH server:

git clone git@github.com:m13253/dns-over-https.git
cd dns-over-https/
make
make install

Now let’s config the DoH server:
The DoH server has a config in /etc/dns-over-https/doh-server.conf
You want to change the upstream to use 127.0.0.1:53 and that’s about it, here’s what I’ve got:

listen = [
    "127.0.0.1:8053",
    "[::1]:8053"
]
local_addr = ""
cert = ""
key = ""
path = "/dns-query"
upstream = [
	"127.0.0.1:53"
]
timeout = 10
tries = 3
tcp_only = false
verbose = false
log_guessed_client_ip = false

Once you’ve got that saved you can restart the DoH server with systemctl restart doh-server

Testing the install

So let’s recap:
We have:

  • Configured nginx and got it running nicely, forwarding DoH requests
  • Installed a DoH server
  • We already had a DNS resolver installed and running, right? 🧐

Now let’s do some testing.

The DoH server returns JSON, so you can just test in your browser:
https://ns1.example.com/dns-query?name=example.org&type=A

Or, since you’re already working in a terminal (and this website is called Terminal Addict 😇)

curl -s "https://ns1.example.com/dns-query?name=example.org&type=A" | python -m json.tool

You should get a JSON response like the following:

{
    "AD": true,
    "Answer": [
        {
            "Expires": "Thu, 12 Sep 2019 22:59:07 UTC",
            "TTL": 7090,
            "data": "93.184.216.34",
            "name": "example.org.",
            "type": 1
        },
        {
            "Expires": "Thu, 12 Sep 2019 22:59:07 UTC",
            "TTL": 7090,
            "data": "A 8 2 86400 20190921055451 20190831145703 31036 example.org. r4fWzdiYS7Px0qXX+7cHtPsj2a3lFhmeM4OXvOsDd2WorpP3Na/FupUFozdp2ao7xyguW+tZSJEI01dKuMt5MDfmJR4cN2n+IiRMvLvQuG60SnQNRBmOTTwca79GNTv5To8rxU5kSixH1liyho2/c/hvIqZKajHs6Bxr460A/RM=",
            "name": "example.org.",
            "type": 46
        }
    ],
    "Authority": [
        {
            "Expires": "Thu, 12 Sep 2019 21:18:35 UTC",
            "TTL": 1058,
            "data": "l.root-servers.net.",
            "name": ".",
            "type": 2
        },
        {
            "Expires": "Thu, 12 Sep 2019 21:18:35 UTC",
            "TTL": 1058,
            "data": "j.root-servers.net.",
            "name": ".",
            "type": 2
        },
        {
            "Expires": "Thu, 12 Sep 2019 21:18:35 UTC",
            "TTL": 1058,
            "data": "h.root-servers.net.",
            "name": ".",
            "type": 2
        },
        {
            "Expires": "Thu, 12 Sep 2019 21:18:35 UTC",
            "TTL": 1058,
            "data": "e.root-servers.net.",
            "name": ".",
            "type": 2
        },
        {
            "Expires": "Thu, 12 Sep 2019 21:18:35 UTC",
            "TTL": 1058,
            "data": "f.root-servers.net.",
            "name": ".",
            "type": 2
        },
        {
            "Expires": "Thu, 12 Sep 2019 21:18:35 UTC",
            "TTL": 1058,
            "data": "g.root-servers.net.",
            "name": ".",
            "type": 2
        },
        {
            "Expires": "Thu, 12 Sep 2019 21:18:35 UTC",
            "TTL": 1058,
            "data": "k.root-servers.net.",
            "name": ".",
            "type": 2
        },
        {
            "Expires": "Thu, 12 Sep 2019 21:18:35 UTC",
            "TTL": 1058,
            "data": "b.root-servers.net.",
            "name": ".",
            "type": 2
        },
        {
            "Expires": "Thu, 12 Sep 2019 21:18:35 UTC",
            "TTL": 1058,
            "data": "d.root-servers.net.",
            "name": ".",
            "type": 2
        },
        {
            "Expires": "Thu, 12 Sep 2019 21:18:35 UTC",
            "TTL": 1058,
            "data": "i.root-servers.net.",
            "name": ".",
            "type": 2
        },
        {
            "Expires": "Thu, 12 Sep 2019 21:18:35 UTC",
            "TTL": 1058,
            "data": "a.root-servers.net.",
            "name": ".",
            "type": 2
        },
        {
            "Expires": "Thu, 12 Sep 2019 21:18:35 UTC",
            "TTL": 1058,
            "data": "m.root-servers.net.",
            "name": ".",
            "type": 2
        },
        {
            "Expires": "Thu, 12 Sep 2019 21:18:35 UTC",
            "TTL": 1058,
            "data": "c.root-servers.net.",
            "name": ".",
            "type": 2
        },
        {
            "Expires": "Thu, 12 Sep 2019 21:18:35 UTC",
            "TTL": 1058,
            "data": "NS 8 0 518400 20190925170000 20190912160000 59944 . E7Vt9bijsTA8J2m5mcI9mc7uyk2PAbjtuDS4zGFaDVpVP4UyFFPYrttCA5CrXFurVN+Qf0nzB31pLzPfrgnY2xqe2S0Pm7Qq+t7rCp/Q1mNp3JbZG4fYxRXoz9uz4zSCaXDJ9WP4zrWrYd++ZGuUs4DXawg9qo0QhX5KRnXehDM9WkjY3JM61tw8CN5KS1ODov7Nw5qZI2uJ69npPnElm163cePQgZTkoGA8AO5vgmgHVbZZqQ3GkxNa8lzHNH/G66xEvPXLe03h6QptCHcOugU6SwCzmxbt2hQnvbQXZk4eGz330CmwlwVPEAKP3VwWsruEkU+LWBIohDg/0Yk6Dg==",
            "name": ".",
            "type": 46
        }
    ],
    "CD": false,
    "Question": [
        {
            "name": "example.org.",
            "type": 1
        }
    ],
    "RA": true,
    "RD": true,
    "Status": 0,
    "TC": false,
    "edns_client_subnet": "103.123.164.0/0"
}

Configure your browser

Well in Firefox this is pretty easy.
Search your preferences / settings in Firefox for DNS.

FF-DoH-settings.png

In Google Chrome the setting will be released in version 78 I’m told, I guess I’ll keep an eye out 🙃

Conclusion

So, maybe you’re super sentive about people spying on you, maybe you like playing with new gadgets / tech. Who knows.
But, I certainly don’t trust cloudflare enough to be giving them my browser DNS queries!

So we’ve now got DNS over HTTPs to use, and we own all our own history / data 👍

14 Comments

Paul

If you are interested as a user in “just using a private DNS server” and not building you own, let me know.
I might build a public facing DoH server for people to use.

Michael Newton

Nice article, I have 2 questions. Do clients support authentication? Like, assuming Nginx was set up for HTTP basic auth, could I use a full URL like https://user:password@host/ in the Firefox preferences above? Which brings me to my second question, is there a need for Nginx in this setup? Assuming you aren’t already running a web server on 443 of course, it seems like the DoH server can listen directly for requests.

Paul

Hi Michael, good questions.

So after 5 seconds of testing, it looks like Firefox does support HTTP basic auth, so yes, you could configure your DNS server as user:pass@ns1.example.com

The need for Nginx is to server SSL certs (HTTPS); you could use anything (maybe write your own Go web service? or fork the DoH server and add in SSL support), so long as it has the ability to 1) reverse proxy to the DoH server, and 2) server SSL certificates, or 3) write a new DoH server that supports SSL 😀 This DoH server doesn’t have the ability to communicate via SSL.

Paul

You could be right !!!
I might do some testing myself !

I’d also have to figure out how to do Let’s Encrypt without Nginx as well

Adib

Hi Paul, thanks for writing this. Why do you need for a “package” dns-over-https? Because I think that “the dns-query” from nginx can head straight to Bind9. Could you explain this more plase? Thankyou.

Paul

Hi,
you need the doh server to interpret between a DNS conversation, and a http conversation.

The doh server returns JSON to nginx.

So you need nginx for SSL and http conversation (although you may not, as Michael points out above)
you need the DoH server as an interpreter between http and dns.
and you need bind (or some other) dns server.

Paul

Hey Amos, this doesn’t seem to consume a lot of resources. I haven’t tested within an ISP environ yet, only within say 50-100 desktops

Wonyoung Park

Hi Paul, i tried DoH configuration by following your document, but it didn’t work when i tested in firefox, but it worked in curl. do you have any ideas? thanks.

Paul

Hi Wonyoung, how did you confirm it was working in Firefox? I can see in my nginx logs queries happening, and things working as expected?

Leave a Comment

Liked what you've read?

  1. Leave me a message, or
  2. Drop me an email

It only takes a second of your time, but it means the world to me.
Don't comment on Facebook, comment here !

I don't keep any of your information, I only ask for a name, and an email to stop spammers!
Pretty please !! :)

Your email address will not be published. Required fields are marked *

You won't be notified of replies, so come back and visit again!

☝️ Top
🏠 Home