TL;DR
- On 27 August we published a measurement saying a cold DNSCrypt lookup costs more bytes than a TLS 1.3 handshake. It was live for about two and a half hours. It was backwards.
- The load-bearing claim underneath it - "no single client speaks both protocols" - was simply false. q speaks DNSCrypt given an sdns:// stamp.
- So the measurement ran dnscrypt-proxy on one arm and q on the other, and blamed the protocols for the difference between two programs.
- Corrected, same client on both arms: DNSCrypt 1,052 bytes in 4 packets, DoT 7,186 in 20. DNSCrypt is about seven times cheaper, not heavier.
- The 1,280-byte query padding and the oversized certificate probes are dnscrypt-proxy behaviour, not DNSCrypt behaviour.
The rule we broke
It is one of our own, and it was written down before this happened: a test that varies one axis identifies the trigger, never the culprit. The transport was the axis we varied. The client was the axis we did not, and it turned out to be the one carrying the result. Every number in the original table was real; every conclusion drawn from it was about the wrong thing.
What made it survive review is that it was surprising in a satisfying way. "The privacy-focused protocol is secretly the expensive one" is a better story than "two clients pad differently", and it arrived with packet captures attached, which made it feel measured rather than assumed.
What changed on the page
The wrong sentences stay on the guide, quoted inside a dated correction block, next to the evidence that overturned them. The corrected table now covers six transports measured with one client against one Quad9 address. The steady-state rows the first version carried, 264 bytes per DoT lookup against 1,564 for DNSCrypt, are withdrawn and not replaced: measuring a second lookup on an open connection needs a client that holds one open, and the fresh-process arms used here do not. An unmeasured gap is better than a wrong number.
Two guards in the published script
The guide now ships measure.sh, and testing it produced the same class of error twice more. Pointed at an address that answers nothing, it printed do53 1 packets 68 bytes and dot 7 packets 420 bytes and exited cleanly - unanswered outbound packets rendered as a measurement. Separately, a query can succeed while the capture sees nothing, because the client used a different address than the filter watched. Both now say so instead of printing a number, and the script keeps its captures on request so the counting can be checked against the packets.
The corrected measurement, the correction in place, and the script: DoH vs DoT vs DNSCrypt vs DoQ: Which to Choose.