DEV Community

ricco020
ricco020

Posted on

DoH costs one TLS handshake, then almost nothing: 45 timed queries to Cloudflare, Google and Quad9

Most DNS-over-HTTPS guides say the overhead is "negligible after warmup" and stop there. I wanted the number, so tonight I timed it.

Setup

  • Vantage point: one VPS in a datacenter (Contabo, AS51167, Lauterbourg, France), 1 Nov 2026, 22:0x UTC. A datacenter sits much closer to these resolvers than a home connection, so read the absolute values as a floor, not as what you will see on Wi-Fi.
  • Resolvers: Cloudflare (cloudflare-dns.com/dns-query), Google (dns.google/dns-query), Quad9 (dns.quad9.net/dns-query).
  • Query: example.com A, RFC 8484 wire format over HTTP/2, curl 8.5.0.
  • 3 runs per resolver. Each run opens one connection and sends 5 queries on it, so query 1 pays for TCP + TLS and queries 2 to 5 reuse the connection. 45 queries in total.
Q="AAABAAABAAAAAAAAB2V4YW1wbGUDY29tAAABAAE"   # example.com A, base64url
u="https://cloudflare-dns.com/dns-query?dns=$Q"
curl -s --http2 -H 'accept: application/dns-message' \
  -w "total=%{time_total} connect=%{time_connect} tls=%{time_appconnect}\n" \
  "$u" -o /dev/null "$u" -o /dev/null "$u" -o /dev/null "$u" -o /dev/null "$u" -o /dev/null
Enter fullscreen mode Exit fullscreen mode

Results

Resolver 1st query (3 runs) TLS done at Queries 2 to 5, median (12 values) Plain UDP, dig (5 values)
Cloudflare 47.6 / 28.3 / 28.9 ms 40.0 / 23.8 / 24.1 ms 4.8 ms 2, 3, 3, 3, 4 ms
Google 36.1 / 39.0 / 49.9 ms 26.9 / 26.7 / 41.2 ms 5.6 ms 4, 5, 7, 8, 18 ms
Quad9 39.8 / 38.0 / 52.4 ms 32.2 / 30.4 / 37.5 ms 7.7 ms 2, 2, 2, 12, 13 ms

What the numbers say:

  1. The first query costs 28 to 52 ms, and almost all of it is the handshake. TCP connect is 4 to 16 ms; the TLS handshake finishes 24 to 41 ms after the start. The DNS answer itself is the small remainder.
  2. Once the connection is open, a DoH query costs 4 to 15 ms, a few milliseconds above plain UDP to the same provider. The "negligible after warmup" claim holds, on this path.
  3. Quad9's third run settled at 15 ms per warm query instead of 7.7. Same connection, same query: most likely a different anycast node or a longer route for that connection. One run is not enough to rank resolvers on speed; three already show the spread.
  4. dig query time and curl time_total do not measure exactly the same thing (curl also counts HTTP framing and its own processing). Compare inside a column, not across columns.

The practical consequence

The cost of DoH is per connection, not per query. A browser keeps its DoH connection open, so you pay the 30 to 50 ms once and then a few ms per lookup. Anything that keeps tearing that connection down (aggressive power saving, a flaky network, a VPN client that resets sockets on reconnect) pays the handshake again each time.

One filtering check, while I was there

Quad9 publishes isitblocked.org as a canary for its blocklist. From the same VPS:

dig @9.9.9.9 isitblocked.org   -> NXDOMAIN (blocked)
dig @1.1.1.1 isitblocked.org   -> 74.208.236.124
dig @1.1.1.2 isitblocked.org   -> 74.208.236.124
Enter fullscreen mode Exit fullscreen mode

So the filter is live on Quad9's plain resolver. That 1.1.1.2 (Cloudflare's malware-filtering resolver) does not block it says nothing about which blocklist is better: it is Quad9's own test domain, not malware.

Limits

One vantage point, one domain already cached at every resolver, one evening. No home ISP path, no mobile network, no HTTP/3. If you rerun the snippet from home, I would expect the handshake to grow far more than the warm queries.

If you are setting DoH up rather than benchmarking it, I keep a browser-by-browser guide on my own site (Chrome, Edge, Firefox, Safari, iOS, Android) that also covers the most common leak, browser DoH silently bypassing a VPN's resolver: DNS over HTTPS setup per browser.

Top comments (0)