How HTTP/3 and QUIC fingerprinting works: transport parameters in the QUIC Initial packet, the h3 ALPN signal, and how it differs from HTTP/2 fingerprinting.
HTTP/2 fingerprinting reads the SETTINGS frame, window increment, and pseudo-header order a client sends over TCP+TLS. HTTP/3 collapses that same handshake into a single UDP-based transport, QUIC — and in doing so it moves several of those signals to a new location, merges others into the TLS layer, and drops one of them entirely. The result is a fingerprint that overlaps with TLS fingerprinting more than HTTP/2's did, but is not identical to it.
Key Takeaways
- QUIC merges the transport and TLS handshakes into one round trip. The first UDP packet a QUIC client sends — the Initial packet — carries a TLS 1.3 ClientHello, so everything JA3/JA4 read from a TCP ClientHello is present here too.
- QUIC transport parameters are a new, protocol-specific signal. They ride inside the ClientHello as the
quic_transport_parametersTLS extension (RFC 9001) and expose implementation-specific values — like initial flow-control limits and connection-ID length — that have no HTTP/2 equivalent. - JA4 already accounts for QUIC. The fingerprint's first character records the transport it saw:
qfor QUIC,tfor TCP,dfor DTLS — see the JA4 specification. Aq13d...fingerprint is directly comparable to at13d...one from the same client over TCP. - HTTP/3's own SETTINGS frame is thinner than HTTP/2's, and PRIORITY frames are gone — replaced by the header-based Extensible Priorities scheme, which most clients don't send at all.
- The
h3ALPN value is the earliest application-layer signal: it appears in the same ClientHello extension ash2did for HTTP/2, just carried over QUIC instead of TCP.
Why QUIC Changes the Fingerprinting Picture
HTTP/2 fingerprinting exists because TCP and TLS are separate protocols: a TCP handshake establishes the connection, then a TLS ClientHello negotiates encryption, then — once TLS is done — the client sends HTTP/2's own startup frames (SETTINGS, WINDOW_UPDATE, PRIORITY). Three layers, three chances to fingerprint.
QUIC (RFC 9000) is a UDP-based transport that folds connection setup and encryption into one exchange. Its first packet — the Initial packet — carries a full TLS 1.3 ClientHello inside a CRYPTO frame, integrated directly into the transport handshake (RFC 9001) rather than layered on top of it. HTTP/3 (RFC 9114) then runs on top of an already-established QUIC connection, reusing much of what QUIC itself provides — stream multiplexing and flow control — instead of redefining them the way HTTP/2 did for TCP.
That restructuring means a QUIC client exposes:
- A TLS 1.3 ClientHello, sent inside the Initial packet — the same cipher suites, extensions, and ALPN value that JA3/JA4 already fingerprint over TCP.
- QUIC transport parameters, a new TLS extension unique to this handshake.
- A much smaller HTTP/3 SETTINGS frame, sent on a unidirectional control stream after the connection is up.
QUIC Transport Parameters: The New Signal
Every QUIC client must send the quic_transport_parameters extension (codepoint 0x39) in its ClientHello, declaring values such as initial_max_data, initial_max_stream_data, max_idle_timeout, and active_connection_id_limit. These parameters describe the connection limits the client is willing to support — and, like HTTP/2's SETTINGS values, the specific defaults an implementation chooses differ between QUIC libraries.
Chrome's QUIC stack, Firefox's neqo, quiche (used by curl and Cloudflare), msquic, and ngtcp2 each ship different default transport parameters. Because this extension sits inside the same ClientHello that JA3/JA4 already hash, a fingerprinting server doesn't need a separate collection mechanism — it just needs to also parse this one additional extension from a packet it's already reading.
JA4 Already Speaks QUIC
TLS fingerprinting didn't need a new algorithm for QUIC — it needed one extra field. The JA4 specification encodes the observed transport as the very first character of the fingerprint: q for QUIC, d for DTLS, t for ordinary TLS over TCP. Everything after that character — the TLS version, cipher hash, and extension hash — is computed exactly the same way regardless of transport, because it's still reading the same ClientHello structure.
That has a practical consequence: a server that already checks JA4 for TLS fingerprinting needs no new logic to fingerprint QUIC connections. It gets a q13d...-style fingerprint automatically, and can directly compare it against the t13d... fingerprint the same browser produces over regular TLS — a mismatch in the hashed portions between a client's TCP and QUIC fingerprints is itself a signal worth investigating.
HTTP/3's Thinner SETTINGS Frame
HTTP/3's own SETTINGS frame (RFC 9114 §7.2.4) defines only SETTINGS_MAX_FIELD_SECTION_SIZE at the HTTP layer. Two more parameters — SETTINGS_QPACK_MAX_TABLE_CAPACITY and SETTINGS_QPACK_BLOCKED_STREAMS — come from QPACK, HTTP/3's field-compression scheme (a redesign of HTTP/2's HPACK for out-of-order delivery), defined separately. Compared to HTTP/2's six standard SETTINGS parameters plus a WINDOW_UPDATE frame, that's a much smaller surface: QUIC itself already handles flow control and stream limits at the transport layer via the transport parameters above, so HTTP/3 doesn't need to renegotiate them at the application layer the way HTTP/2 did over plain TCP.
No More PRIORITY Frames
HTTP/2's PRIORITY frames — already deprecated by RFC 9113 — have no equivalent at all in HTTP/3's core frame set. Stream prioritization instead moved to Extensible Priorities, a scheme that signals urgency through an HTTP Priority header field (or an equivalent frame) rather than a dedicated binary frame at connection start. Many clients don't send explicit priority signals at all, relying on the server's defaults — which removes what used to be one of the more distinctive fingerprint fields in older HTTP/2 fingerprinting work.
How It Differs From HTTP/2 Fingerprinting
| Signal | HTTP/2 (over TCP+TLS) | HTTP/3 (over QUIC) |
|---|---|---|
| TLS ClientHello | Separate TCP + TLS handshake | Embedded in the QUIC Initial packet |
| Transport-level parameters | None (TCP has no equivalent) | quic_transport_parameters extension |
| SETTINGS frame | 6 standard parameters | 1 HTTP parameter + 2 QPACK parameters |
| Stream priority | PRIORITY frames (deprecated) | Extensible Priorities header, often unset |
| ALPN value | h2 | h3 |
| JA3/JA4 applicability | Direct (TLS over TCP) | Direct, with a q transport marker |
The practical upshot: HTTP/3 fingerprinting leans more heavily on the TLS layer than HTTP/2 fingerprinting did, because QUIC absorbed most of what used to be separate transport-negotiation steps into the same ClientHello that JA3/JA4 already read.
Use in Bot and VPN Detection
Detection systems care about QUIC fingerprinting for a specific reason: a growing share of browser traffic to Google, Cloudflare-fronted sites, and other early HTTP/3 adopters now arrives over QUIC by default, not TCP. A bot framework built around spoofing a TCP-based TLS handshake produces no QUIC traffic at all — which is itself informative, since a real Chrome instance visiting an HTTP/3-capable origin will typically negotiate QUIC rather than falling back to TCP. Conversely, tooling that does speak QUIC (via quiche or msquic bindings) still carries that library's transport-parameter defaults and QPACK settings, distinguishable from a genuine browser's quiche-free stack the same way a spoofed HTTP/2 client is caught by its SETTINGS values.
For VPN and proxy detection, QUIC introduces a wrinkle: because it runs over UDP, some corporate firewalls and older middleboxes block it outright, forcing the client to fall back to HTTP/2 over TCP. That fallback pattern — a client whose User-Agent claims a QUIC-capable Chrome version but which never negotiates h3 — is itself an observable signal, separate from the fingerprint content itself.
The QUIC and TLS transport fingerprints described here are observed server-side, so they aren't exposed to page JavaScript — but they work alongside the client-side signals a detection system reads from your browser. You can see those on yourself with BrowserInsight's bot detection tool: it surfaces your navigator.webdriver state, headless and automation artifacts, and fingerprint consistency, each with a plain-language explanation of what it means, all computed client-side.
Frequently Asked Questions
Do I need a new tool to see my QUIC fingerprint?
Not conceptually — any server-side JA4 implementation already produces one, since QUIC's ClientHello is parsed the same way as TLS-over-TCP's. What changes is where the packet capture happens: QUIC arrives over UDP, so a server or middlebox built only to inspect TCP streams won't see it without separate UDP capture.
Can a bot avoid QUIC fingerprinting by refusing to use HTTP/3?
Partly, but it trades one signal for another. Falling back to HTTP/2 avoids QUIC-specific fingerprinting, but a real modern browser visiting an HTTP/3-capable site normally does negotiate QUIC — so a client that consistently avoids it despite claiming to be an up-to-date Chrome or Firefox is itself an anomaly worth flagging.
Does encrypting the QUIC handshake (like ECH) hide these signals?
Encrypted Client Hello hides the inner ClientHello's sensitive fields (like SNI) from network observers, but the destination server — which decrypts the handshake to serve the connection — still sees the full ClientHello, transport parameters, and HTTP/3 SETTINGS. ECH changes what a passive network observer between client and server can see; it doesn't change what the origin server itself can fingerprint.
Is HTTP/3 fingerprinting as mature as HTTP/2 fingerprinting?
Less so today, mainly because QUIC adoption, while substantial for a handful of large operators, is still smaller than universal TCP+TLS. As more origins and CDNs terminate HTTP/3 by default, expect QUIC-aware fingerprinting to become as standard a part of bot-detection stacks as JA3/JA4 and the Akamai fingerprint already are.


