Configuring DNS over TLS and HTTPS behind Nginx with BIND

In January of 2022, BIND 9.18 became a new stable branch that contained support for DoT and DoH. Learning this, I decided to upgrade my previous BIND server to this new version and enable these features.  

If you run containers, you can use Docker to get the latest BIND 9.18 docker image from here:

docker pull internetsystemsconsortium/bind9:9.18

For the configuration example below, I assume you have worked with BIND and have already configured it as a DNS resolver/forwarder, and you wish to just enable the DNS over TLS and HTTPS features. I also assume you have experience creating and installing any required self-signed certificates.

// NOTE: Other BIND settings excluded, just focusing on DoT and DoH

// Your BIND server's TLS files
// You can use a self-signed certificate for this
tls server-tls {
  cert-file "/etc/bind/ssl/server.crt";
  key-file "/etc/bind/ssl/server.key";
  dhparam-file "/etc/bind/ssl/dhparam.pem";
  protocols { TLSv1.2; TLSv1.3; };
  ciphers "HIGH:!kRSA:!aNULL:!eNULL:!RC4:!3DES:!MD5:!EXP:!PSK:!SRP:!DSS:!SHA1:!SHA256:!SHA384";
  prefer-server-ciphers yes;
  session-tickets no;

options {

  // DNS over HTTPS (DoH)
  // Note: The HTTPS port is changed from 443 to 8453 to avoid conflict with web servers on the same host
  listen-on port 8453 tls server-tls http default {
    // update with your bind server's listening addresses;
  listen-on-v6 port 8453 tls server-tls http default {
    // update with your bind server's listening addresses
  // DNS over TLS (DoT), default port is 853
  listen-on port 853 tls server-tls {;
  listen-on port-v6 853 tls server-tls {

If you are already using Nginx as a reverse-proxy for HTTP and HTTPS services, you can enable DoH on port 443 by creating a gPRC connection to your BIND instance.

I would like to add that my first few attempts at getting this to work was by using a typical proxy_pass approach. This never worked for me, and it wasn't until I stumbled across a few gRPC examples on other projects that I decided to give it a try, and much to my relief, it worked!

For gRPC support, you must use Nginx version 1.13.10 or newer.

upstream doh-servers {
   # Assuming bind9 is running locally on port 8458

server {
  listen 443 ssl http2;
  listen [::]:443 ssl http2;
  server_name doh.yourserver.local;

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

  ssl_certificate /etc/letsencrypt/live/doh.yourserver.local/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/doh.yourserver.local/privkey.pem;
  ssl_trusted_certificate /etc/letsencrypt/live/doh.yourserver.local/chain.pem;
  ssl_session_timeout 1d;
  ssl_session_cache shared:DoHSSL:10m;
  ssl_session_tickets off;

  ssl_dhparam /etc/nginx/dhparams.pem;

  ssl_protocols TLSv1.3 TLSv1.2;
  ssl_prefer_server_ciphers off;
  ssl_ecdh_curve auto; 
  ssl_stapling on;
  ssl_stapling_verify on;
  add_header Strict-Transport-Security max-age=31536000;

  location / {
    return 404;

  # BIND listens on /dns-query
  location /dns-query {
    grpc_pass grpcs://doh-servers;
    grpc_socket_keepalive on;
    grpc_connect_timeout 10s;
    grpc_ssl_verify off;
    grpc_ssl_protocols TLSv1.3 TLSv1.2;


One your configuration is finalized, give it a test with an updated dig client that supports DNS over TLS and DNS over HTTPS:

dig +tls @ A

dig +https @doh.yourserver.local A

If configured properly, you should get a successful result back!

I hope that this post assists others in getting their DoT and DoH environment working.

Mastodon: @[email protected]