6 min read #networking #DNS
On this page

DNS Privacy: DoH, DoT, DoQ, ODoH

DNS was originally a plaintext protocol—ISPs and intermediate networks could see which domains were queried. DoT/DoH/DoQ place DNS queries into encrypted channels, while ODoH further achieves privacy isolation between "who is querying" and "what is being queried" through proxy-resolver separation. The four protocols each have trade-offs in terms of privacy strength, deployment models, and middleware compatibility.

Overview

DNS was originally a plaintext protocol (UDP port 53), meaning ISPs and intermediate networks could see which domains you queried—even if the website content itself was encrypted via HTTPS. DoT (DNS over TLS) and DoH (DNS over HTTPS) place DNS queries into encrypted channels, providing confidentiality. DoH is particularly popular among privacy advocates because it is indistinguishable from standard HTTPS traffic. DoQ (DNS over QUIC) further reduces handshake latency. ODoH (Oblivious DoH) achieves privacy separation between "who is querying" and "what is being queried" through proxy-resolver decoupling.

DoT: DNS over TLS (port 853)

The DNS wire format is transmitted directly over a TLS connection. The dedicated port (853) makes it easy for network administrators to identify and classify for QoS, but also makes it easy to block.

Client → server port 853:
  TLS handshake (1-RTT with ECH if enabled + encrypted SNI)
  → On connection: 2-byte length prefix + DNS message (RFC 1035 wire format)
     length: big-endian, does not include the prefix itself (max 65535)

DoH: DNS over HTTPS (port 443)

DNS is mixed into standard HTTPS traffic—indistinguishable from website traffic.

Client → https://dns.example.com/dns-query:
  POST: Content-Type: application/dns-message
         Body: DNS wire format (same as RFC 1035)
  GET:  ?dns=<base64url(DNS wire format)>

  HTTP/2: stream multiplexing → multiple queries share a single TCP connection
  HTTP/3: QUIC → 0-RTT for cached DNS resolver

Content negotiation (optional):
  Accept: application/dns-message → IDNA wire format (binary, default)
  Accept: application/dns-json → JSON (specific to Google/Cloudflare)

DoQ: DNS over QUIC (port 853)

DoQ uses QUIC instead of TCP+TLS, addressing the handshake latency and TCP HOL blocking issues of DoT:

QUIC connection (UDP 853):
  Stream 0: client-initiated, bidirectional → DNS messages
  Stream 4: client-initiated, unidirectional → for server to send data
  Stream 8: client-initiated, unidirectional → for client to send data

Per stream: STREAM frame contains DNS message (no length prefix needed, QUIC streams have inherent boundaries)

ODoH (Oblivious DNS over HTTPS, RFC 9230)

Problem: DoH still exposes "who is querying what." The resolver sees the client IP + DNS query.

ODoH decouples the proxy and resolver:

Client → Proxy (Oblivious Proxy):
  Encrypted POST https://proxy/dns-query
  Body: encrypted DNS query (encrypted with the resolver's HPKE public key)
  → Proxy knows the client IP but cannot see the query content

Proxy → Target (Oblivious Target = DNS resolver):
  Encrypted POST https://resolver/dns-query
  Body: encrypted DNS query (same, proxy cannot decrypt)
  → Target/resolver sees the query content but does not know the client IP (only sees the proxy IP)

Proxy and Target must not collude (collusion = still traceable)

Comparison of the Four

PortTransportHandshakeBlockabilityPrivacy (query)Privacy (client IP)
Do5353 UDPUDP0Very LowNoneNone
DoT853TCP+TLS1-2 RTTHigh (dedicated port)Partial (TLS)None
DoH443HTTP/2 or /31-2 RTT (h2), 0-1 RTT (h3)Very Low (mixed with HTTPS)Partial (HTTPS)None
DoQ853QUIC/UDP0-1 RTTMediumPartial (QUIC)None
ODoH443HTTP + HPKE2 RTT (proxy then resolver)LowYes (two-layer)Yes (resolver cannot see)

References

  • RFC: 7858, 8484, 9250, 9230
  • AdGuard: adguard-dns.io/encryption.html

Keywords: DoH, DoT, DoQ, ODoH, DNS privacy, encrypted DNS, HPKE, oblivious proxy