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
| Port | Transport | Handshake | Blockability | Privacy (query) | Privacy (client IP) | |
|---|---|---|---|---|---|---|
| Do53 | 53 UDP | UDP | 0 | Very Low | None | None |
| DoT | 853 | TCP+TLS | 1-2 RTT | High (dedicated port) | Partial (TLS) | None |
| DoH | 443 | HTTP/2 or /3 | 1-2 RTT (h2), 0-1 RTT (h3) | Very Low (mixed with HTTPS) | Partial (HTTPS) | None |
| DoQ | 853 | QUIC/UDP | 0-1 RTT | Medium | Partial (QUIC) | None |
| ODoH | 443 | HTTP + HPKE | 2 RTT (proxy then resolver) | Low | Yes (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