QUIC and DTLS
pqdoctor quic cloudflare.com # port 443pqdoctor example.org --quic # TCP and QUIC in one report✔ quic cloudflare.com:443 (104.16.133.229) Post-quantum over QUIC: negotiates X25519MLKEM768 cause: quic-pq-preferred
Probes · QUIC, classical groups TLS 1.3: picked X25519 (17 ms) · QUIC, only an X25519MLKEM768 key share TLS 1.3: picked X25519MLKEM768 (18 ms) · QUIC, X25519 share, ML-KEM listed first TLS 1.3: asked to retry with X25519MLKEM768 (18 ms)cloudflare.com and www.google.com both gave this on 2026-10-15. HTTP/3 is often served by a different TLS stack from the same host’s TCP port, so the answers can differ.
On the wire: a QUIC v1 Initial packet carries the ClientHello in CRYPTO frames, with quic_transport_parameters and ALPN h3. A post-quantum hello doesn’t fit in one 1200-byte datagram, so it goes in two Initials. Initial packets are protected with keys every QUIC endpoint derives from the client’s Destination Connection ID (RFC 9001 §5.2: HKDF-SHA256, AES-128-GCM, AES header protection), so pqdoctor can decrypt the server’s Initial and read its ServerHello or HelloRetryRequest. It handles Version Negotiation and follows one Retry; the Retry integrity tag isn’t verified. Nothing is sent after the first flight. The packet code is tested against the RFC 9001 Appendix A vectors.
Cause codes: quic-pq-preferred, quic-pq, quic-no-mlkem, quic-hello-rejected, quic-no-reply, quic-version.
pqdoctor dtls vpn.example.org:4433pqdoctor example.org:4433 --dtlsThree probes over UDP, each from a fresh socket: DTLS 1.3 + 1.2 with classical groups, DTLS 1.3 with an X25519MLKEM768 share, and DTLS 1.2 only. pqdoctor answers a DTLS 1.2 HelloVerifyRequest cookie, or a DTLS 1.3 HelloRetryRequest cookie (and group), once, with the same client random. For DTLS 1.2 the group comes from the ServerKeyExchange. A big hello goes as two handshake fragments.
Proxies
Section titled “Proxies”QUIC and DTLS are UDP. HTTP CONNECT and SOCKS5 CONNECT carry TCP only, so with --proxy set pqdoctor doesn’t send them direct: pqdoctor quic exits 2, and --quic / --dtls in a report say “not checked”.