Docs / DNS & Domains / Setting Up DNS over HTTPS (DoH)

Setting Up DNS over HTTPS (DoH)

By Admin · Mar 15, 2026 · Updated Apr 24, 2026 · 232 views · 3 min read

DNS over HTTPS (DoH) encrypts DNS queries within HTTPS connections, preventing ISPs and network observers from seeing your DNS lookups. This guide covers setting up both a DoH client (for privacy) and a DoH server (to provide encrypted DNS to your users).

Why DoH?

  • Privacy — DNS queries are encrypted and hidden within normal HTTPS traffic
  • Security — prevents DNS spoofing and man-in-the-middle attacks
  • Bypass censorship — DoH traffic looks like regular HTTPS, harder to block than traditional DNS

DoH Client Setup

System-Wide with dnscrypt-proxy

# Install dnscrypt-proxy
sudo apt install dnscrypt-proxy

# /etc/dnscrypt-proxy/dnscrypt-proxy.toml
listen_addresses = ['127.0.0.1:53']
server_names = ['cloudflare', 'google']

[sources.public-resolvers]
  urls = ['https://download.dnscrypt.info/resolvers-list/v3/public-resolvers.md']
  cache_file = '/var/cache/dnscrypt-proxy/public-resolvers.md'
  minisign_key = 'RWQf6LRCGA9i53mlYecO4IzT51TGPpvWucNSCh1CBM0QTaLn73Y7GFO3'

# Point system DNS to local proxy
# /etc/resolv.conf
nameserver 127.0.0.1

Browser-Level DoH

# Firefox: Settings → Privacy & Security → DNS over HTTPS → Enable
# Chrome: Settings → Security → Use secure DNS → Choose provider
# Common DoH providers:
# Cloudflare: https://cloudflare-dns.com/dns-query
# Google: https://dns.google/dns-query
# Quad9: https://dns.quad9.net/dns-query

DoH Server Setup

Using CoreDNS

# Corefile with DoH endpoint
https://.:443 {
    tls /etc/letsencrypt/live/dns.example.com/fullchain.pem /etc/letsencrypt/live/dns.example.com/privkey.pem
    forward . 1.1.1.1 8.8.8.8
    cache 600
    log
    prometheus :9153
}

Using Nginx as DoH Proxy

# Nginx proxies DoH requests to a local DNS resolver
upstream dns_backend {
    server 127.0.0.1:8053;
}

server {
    listen 443 ssl http2;
    server_name dns.example.com;
    ssl_certificate /etc/letsencrypt/live/dns.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/dns.example.com/privkey.pem;

    location /dns-query {
        proxy_pass http://dns_backend/dns-query;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

Using doh-server (Rust)

# Install doh-server
cargo install doh-server

# Run
doh-server \
    --listen-address 0.0.0.0:3000 \
    --server-address 127.0.0.1:53 \
    --tls-cert-path /etc/letsencrypt/live/dns.example.com/fullchain.pem \
    --tls-cert-key-path /etc/letsencrypt/live/dns.example.com/privkey.pem \
    --path /dns-query

Testing DoH

# Test with curl
curl -s -H "accept: application/dns-json" "https://dns.example.com/dns-query?name=example.com&type=A"

# Test with kdig (knot-dnsutils)
kdig @dns.example.com +https example.com A

# Test with dog
dog example.com A --https @https://dns.example.com/dns-query

Best Practices

  • Use trusted DoH providers (Cloudflare, Google, Quad9) for client-side setup
  • Self-host DoH servers for organizational privacy requirements
  • Enable DNSSEC validation alongside DoH for complete DNS security
  • Monitor DoH server performance — encrypted DNS adds TLS overhead
  • Consider DoT (DNS over TLS, port 853) as a simpler alternative when stealth is not required

Was this article helpful?