DNS Privacy: DoH, DoT, DoQ, and ODoH

On this page

When a client switches DNS to HTTPS, the local network typically cannot directly read query names, but the recursive resolver still knows who is querying what. ODoH further splits the client source and query content across different roles. When comparing these protocols, the most useful question is "what is hidden from which observer," followed by ports and round-trip counts.

ProtocolCommon TransportDNS Message BoundaryPrimary Additional Protection
Do53UDP/TCP 53UDP datagram or TCP two-byte prefixNo transport confidentiality
DoTTLS/TCP 853Two-byte length prefixEncrypted connection to the authenticated DNS service
DoHHTTPS, commonly 443HTTP request/response bodyDNS mapped to an encrypted HTTP transport
DoQDedicated QUIC/UDP 853Bidirectional stream per exchange, still with a two-byte prefixEncryption and avoiding shared TCP byte gaps across different queries
ODoHHTTPS + HPKE message protectionODoH message formatSeparates source and content under certain trust assumptions

Draw the Trust Boundaries First

Encrypted to resolver, only protecting query content on this link Client Queries visible to local machine and apps Resolver Sees source and query plaintext Authoritative Hierarchy Receives subsequent queries from resolver Encrypted Transport Another Path Segment The network can still see peer addresses, sizes, and timing; the resolver's connection to authorities is not automatically fully encrypted just because the client enables DoH.

DoT, DoH, and DoQ primarily protect the transport from the client to the selected server. After the server terminates encryption, it needs to read the DNS message to process it, and can still record the source, name, time, and result; browsers, local malware, or enterprise management components may also see the query before encryption.

The resolver's query to the authority is another link segment, with visibility and encryption policies independent of the client-to-server link. Network observers can still see the destination IP, connection timing, and traffic size, and may infer behavior by correlating with subsequent website connections. Encrypted DNS reduces direct exposure but does not hide all access metadata. RFC 9076: DNS Privacy Considerations

DNSSEC authenticates DNS data. A correctly authenticated TLS connection to a resolver does not mean the data provided by that resolver has been DNSSEC-validated; a DNSSEC Secure response also does not automatically hide the query name.

DoT: Retains DNS/TCP Framing, Adds TLS

DoT defaults to TCP 853, establishing a TLS connection before sending a two-byte big-endian length prefix followed by the DNS message; the length does not include the prefix itself. Multiple queries can reuse the connection and be pipelined, and responses do not necessarily return in query order; the client matches them by ID and context. RFC 7858: DoT

TLS version, connection reuse, and resumption affect handshake costs. ECH is not a mandatory built-in option for DoT and does not eliminate all connection establishment costs for TLS and the underlying TCP. Dedicated ports facilitate service type identification, but actual allowance and reachability are still determined by network policies.

Server address and authentication name are two different concepts: after connecting to an IP, you must still verify the expected service identity based on trusted configuration. Strict Privacy configurations and opportunistic modes differ in authentication and failure handling; if the handshake fails and falls back to plaintext, the privacy goal changes accordingly. RFC 8310: DNS Privacy Usage Configuration

DoH: Distinguish HTTP Success from DNS Success

RFC 8484 defines the binary format as application/dns-message. POST places the DNS wire message directly into the request body; GET uses the dns parameter, whose value is the Base64url encoding with padding removed. It is not the IDNA wire format, nor does it require the additional two-byte DNS/TCP length prefix. RFC 8484: DoH

For example, a 33 B DNS query encodes to 44 characters via Base64url; the POST DNS body remains 33 B, with HTTP/TLS/QUIC overhead calculated separately. The JSON query API is an interface defined separately by specific services and cannot be treated as a negotiation result that standard DoH clients must support via application/dns-json.

Response LevelExampleHow to Interpret
HTTP Failure4xx/5xxHTTP request, service, or policy issue; may not have a usable DNS message
HTTP 200 + DNS NXDOMAINValid DNS negative responseDomain-related negative result, not an HTTP error
HTTP 200 + DNS SERVFAILDNS processing failureContinue checking DNS/EDE information and validation/upstream status

DoH clients typically zero out the DNS ID to improve HTTP cache friendliness, with association provided by the HTTP exchange; this differs from the UDP context where only random IDs are used. HTTP caches should also coordinate with DNS TTL: if a cached response has Age=20 s, you cannot treat the original TTL=60 s as a full 60 s from now.

DoH can use HTTP/2 or HTTP/3. HTTP/2 stream multiplexing still shares TCP's ordered byte delivery; HTTP/3 uses QUIC, but DoH over HTTP/3 and standalone DoQ are different application mappings, and ALPN, message formats, and port usage cannot be mixed.

DoH using the same common ports as web HTTPS does not mean it is completely indistinguishable: server addresses, TLS information, traffic patterns, or managed endpoint policies can still identify and restrict the corresponding services. GET parameters are not displayed as plaintext URLs on the TLS network path, but terminating HTTPS services and logging systems may still record them.

DoQ: One Client Bidirectional Stream per Exchange

DoQ negotiates ALPN doq, typically using UDP 853. The client opens a bidirectional stream for each DNS query, writing a two-byte length and DNS message in the send direction and ending that direction; the server returns the length-prefixed response in the reverse direction on the same stream and ends it. DNS ID must be zero, and query matching relies on the stream. RFC 9250: DoQ

Client bidirectional Stream IDs are 0, 4, 8, 12...; their lower two bits are the same, and incrementing the number by 4 does not change the initiator or direction type. QUIC streams are byte streams; having FIN/offset does not mean DoQ no longer needs length prefixes; the receiver still needs to handle chunked reads and message length validation.

When the stream containing query A has gaps, the complete response for query B can be delivered on another stream, but they still share connection congestion control and resources. When one packet contains data for two streams, loss affects both streams; you cannot write it off as unconditionally eliminating all waits.

0-RTT requires available resumption state and can be rejected; it is not the default zero-handshake for the first DoQ query. Whether a certain query is allowed to enter early data, and how to handle rejection, should follow DoQ and application semantics, and protocol names cannot be treated as replay security proofs.

ODoH: Proxy Sees Source, Target Sees Content

ODoH: Separating direct visibility of client source and query plaintext Client Holds target public key configuration Proxy Sees client address and ciphertext Target Decrypts query, sees proxy address HTTPS HTTPS Assuming proxy and target do not collude; still need to consider timing correlation, identifiable content, and configuration sources Outer HTTPS and inner HPKE message protection assume different responsibilities; this is not an absolute anonymity guarantee under a global observer.

The client first obtains the target's ODoH public key configuration, uses HPKE to protect the DNS query, and sends it via HTTPS to the proxy, which forwards it to the target. The target decrypts and processes the query, and the response is protected according to the corresponding key context and returned via the proxy. RFC 9230: ODoH

RoleNormally Directly ObservableKey Limitations
ProxyClient network address, size/timing of target and encrypted messagesCannot directly read DNS content without target private key
TargetQuery content, proxy network addressCannot directly obtain client source IP from this connection alone
Colluder or Strongly Correlated ObserverCan combine records from both sidesRole separation does not automatically prevent correlation

To maintain this separation, no additional metadata that directly informs the target of the client address should be attached; the client must also trustworthily obtain target configuration and handle key rotation. If the query content itself carries identifiable names, or an attacker can observe the timing of traffic on both sides, user correlation is still possible.

ODoH does not guarantee global anonymity, nor is it simply "another layer of HTTPS": inner message protection prevents the proxy from directly reading the query after terminating the outer TLS. Additional proxy hops, target processing, connection reuse, and configuration acquisition determine costs; you cannot hardcode it as two RTTs for all deployments.

What Happens When You Piece Together Two Observation Records?

This is not a protocol feature comparison table, but an operation on trust assumptions: when only one side's record is taken, the joint information is missing a column; after both sides collaborate, common events can connect the two columns. Actual correlation does not necessarily require explicit labels; timing and message characteristics may also provide clues.

Preparing the visual
What Happens When You Piece Together Two Observation Records?

Role separation relies on information not being re-merged. The proxy alone has no query, the target alone has no terminal source; once shared records can be correlated, this separation condition no longer holds.

Latency Comparison: Separate Cold and Warm Connections

Assume client-to-server RTT=30 ms, service has cached answers, no packet loss, and ignore processing and resolver address lookup time. When an encrypted connection is already available, a query/response takes approximately one RTT, i.e., 30 ms, regardless of how many handshake steps the protocol previously used.

The simplified sequence budget for cold TCP + TLS 1.3 can be TCP one RTT, TLS one RTT, query/response one RTT, totaling about 90 ms; cold QUIC common handshake followed by query budget can be about two RTTs, i.e., 60 ms. Implementation send timing, recovery, Retry, and certificate transmission will change results; these are not protocol-guaranteed values.

If an ODoH outer connection already exists, client-to-proxy RTT=20 ms, proxy-to-target RTT=30 ms; under a simplified model of serial request forwarding and symmetric return trip, the communication part takes about 50 ms, plus processing and recursive queries. It cannot be compared solely by protocol name to a path "directly to another geographic location server RTT=80 ms."

Cache misses requiring upstream iteration, tail packet loss, and connection idle recycling are often more important than header byte differences. Cold starts, warm caches, upstream misses, and failure recovery should be counted separately, rather than just listing an unreplicable speed ranking.

Bootstrapping and Failure Policies Are Also Part of Privacy

Before using https://dns.example/dns-query, you need to know the server IP and authenticate this name. You can obtain the address via pre-configuration, existing bootstrap resolvers, or corresponding discovery mechanisms; you cannot require "relying solely on an encrypted resolver that doesn't yet have an address to resolve itself" without providing a startup plan.

Browsers may use independent DoH, while the system resolver still takes another path; VPNs, split DNS, search domains, and local service discovery also affect actual queries. Verification should observe along the actual resolution path used by the application; you cannot just see one DoH connection and declare that the entire machine has no plaintext queries.

Failure PolicyBehaviorPractical Trade-offs
Strict FailureReturns failure when authentication or encrypted path is unavailableMaintains established privacy requirements, may affect availability
Allow FallbackSwitches to other resolvers or plaintextCan improve connectivity, but may change trust and exposure scope
Multiple Encrypted UpstreamsSwitches under explicit policyStill requires consistent authentication, bootstrapping, and log/data processing policies

These are configurable product behaviors and cannot be inferred from the names DoH/DoT/DoQ. Which one to adopt should be configured and verified around actual needs; the subsequent infrastructure section will continue with deployment and observation.

Local Encoding Reference

import base64
import struct
qname = bytes.fromhex('03777777076578616d706c6503636f6d00')
query = struct.pack('!6H',0,0x0100,1,0,0,0)+qname+struct.pack('!HH',1,1)
assert len(query)==33
# DoH POST body has no two-byte DNS/TCP prefix.
post_body = query
get_value = base64.urlsafe_b64encode(query).rstrip(b'=')
assert len(get_value)==44
assert base64.urlsafe_b64decode(get_value+b'='*((-len(get_value))%4))==query
# DoT/DoQ single message framing retains this prefix.
framed = struct.pack('!H',len(query))+query
assert len(framed)==35 and int.from_bytes(framed[:2],'big')==33
assert framed[2:]==post_body
assert [index<<2 for index in range(4)]==[0,4,8,12]
assert max(0,60-20)==40
assert 3*30==90 and 2*30==60 and 20+30==50
print('DoH encoding, DoT/DoQ prefix, bidirectional stream numbers, cache Age, and latency budget passed')

This is a budget check under message mapping and explicit assumptions; it does not connect to a real DNS service, execute HPKE encryption/decryption, or change system privacy settings.

Continue Reading