dnsdist vs. nginx, or both.

Technically speaking this solely depends on what your goal is. dnsdist is specifically tailored to load-balance DNS and be able to perform all kinds of special routing and transformations that nginx will never be capable of. Then again, nginx has other capabilities that dnsdist lacks. For the good old fashioned recursive DNS on port 53/domain (tcp+udp), you're better off using dnsdist, no doubt. But maybe you don't need it and you're already running nginx for your websites anyway.

DNS-over-TLS

stream {
        log_format dns  '$remote_addr [$time_local] $server_port/$protocol $status '
                        's=$bytes_sent r=$bytes_received t=$session_time '
                        'ua=$upstream_addr us=$upstream_bytes_sent ur=$upstream_bytes_received '
                        'uct=$upstream_connect_time ufbt=$upstream_first_byte_time ust=$upstream_session_time '
                        'sc=$ssl_cipher sp=$ssl_protocol';

        upstream dns {
                # Default max fail count is 1.
                server 127.0.0.1:5300 fail_timeout=1s; # Local node, we'll know quickly when it's down.

                server [2001:db8:5612::]:53 max_conns=10 weight=10 backup; # Amsterdam
                server [2001:db8:5622::]:53 max_conns=5  weight=3  backup; # Los Angeles
                server [2001:db8:5632::]:53 max_conns=5  weight=1  backup; # Singapore
        }

        server {
                listen *:53;                    #  53/tcp     (IPv4)
                listen *:53 udp reuseport;      #  53/udp     (IPv4)
                listen *:853 ssl;               # 853/tcp+tls (IPv4)

                listen [::]:53;                 #  53/tcp     (IPv6)
                listen [::]:53 udp reuseport;   #  53/udp     (IPv6)
                listen [::]:853 ssl;            # 853/tcp+tls (IPv6)

                ssl_prefer_server_ciphers on;   # dnsdist default
                ssl_protocols TLSv1.2;          # our default (dnsdist = TLSv1.2+1.1+1)
                #ssl_ciphers t.b.d.;            # SSL/TLS library default in dnsdist/nginx.

                ssl_certificate         /opt/secp384r1/certs/finalx.nl/cert.pem;
                ssl_certificate_key     /opt/secp384r1/certs/finalx.nl/privkey.pem;

                ssl_certificate         /opt/rsa/certs/finalx.nl/cert.pem;
                ssl_certificate_key     /opt/rsa/certs/finalx.nl/privkey.pem;

                access_log              /var/log/nginx/dns.access.log dns;
                error_log               /var/log/nginx/dns.error.log debug;

                proxy_timeout 3s; # default is 10 minutes, which makes UDP only be logged after 10 min.
                proxy_pass dns;
        }
}

DNS-over-HTTPS

                # DNS over HTTPS
                location = /dns-query {
                        mirror =; # Don't touch, this is necessary to populate $request_body!

                        ## Begin - FastCGI caching.
                        proxy_cache             dns;
                        proxy_cache_methods     GET HEAD POST;
                        proxy_cache_key         "$scheme$request_method$host$request_uri$request_body";
                        proxy_cache_valid       200 1m; # this is the only valid response of the proxy.

                        proxy_ignore_headers    "Cache-Control"
                                                "Expires"
                                                "Set-Cookie";

                        proxy_cache_use_stale   error
                                                timeout
                                                updating
                                                http_429
                                                http_500
                                                http_503;

                        proxy_cache_background_update   on;
                        ## End - FastCGI caching

                        proxy_pass http://[::1]:8553;
                }

Performance testing

dnsperf with tcp/tls