Skip to content

The ClientHello of every major browser, version by version

· 20 min read
Copyright: MIT
Wordmark reading ClientHello with an orange underline and a stack of TLS extension fields

A modern Chrome ClientHello is roughly 1,700 bytes. A 2016 Chrome ClientHello was around 300. Most of that growth happened in two jumps, both driven by Google, and both leaving a mark that an edge proxy can read before a single byte of HTTP crosses the wire. If you want to know which browser, and often which major version, is sitting behind a TLS handshake, the ClientHello tells you most of it.

This is a catalog. The question it answers is narrow and practical: what has each major browser’s ClientHello looked like, version by version, from 2015 to now, and what does the current one look like in 2026? The interesting parts are the changes that moved the fingerprint, because those are the changes a passive observer keyed on. GREASE arriving in 2016. Extension permutation in early 2023 that quietly killed JA3. Post-quantum key shares in 2024 that blew the handshake past 1,500 bytes and gave servers a fresh, hard-to-fake tell.

The plan: first the anatomy of a ClientHello and why its fields are stable enough to fingerprint at all. Then a per-browser walk through Chrome, Firefox, Safari, and Edge, calling out the version where each shifted. Then the two structural changes that reshaped the whole catalog, GREASE and permutation, followed by the post-quantum transition that is the live story in 2026. The post closes on what a fresh fingerprint per browser looks like today, and why “looks like Chrome 138” is a moving target with a short shelf life.

What is in a ClientHello, and why it fingerprints

The ClientHello is the first message a TLS client sends. It is plaintext, even in TLS 1.3, because the handshake has not yet negotiated any keys. Inside it: the legacy version field, a 32-byte random, a session ID, the list of cipher suites the client supports in preference order, the compression methods, and then a block of extensions. The extensions carry the supported TLS versions, the named groups (elliptic curves and now post-quantum groups), the signature algorithms, ALPN, the key shares, SNI, and a long tail of optional features.

None of that is secret. All of it is chosen by the client’s TLS library, not by the user, and the choices differ between libraries in ways that are stable across millions of connections. Chrome ships BoringSSL. Firefox ships NSS. Safari uses Apple’s own stack. Each picks a particular cipher list, in a particular order, advertises a particular set of extensions, and lays out particular key shares. That combination is the fingerprint. The two best-known ways to summarize it are JA3, which hashes the version, cipher list, extension list, named groups, and EC point formats into an MD5, and the newer JA4, which we will get to.

The reason the ClientHello fingerprints so well is that the fields are deterministic per build and hard to change from outside the library. An HTTP client like Python’s requests cannot pick Chrome’s exact cipher order without reimplementing Chrome’s TLS stack, which is the whole subject of why your Python requests is fingerprintable before it sends a byte of HTTP. For the field-by-field anatomy that this catalog assumes you already half-know, see the TLS ClientHello, field by field.

ClientHello, top to bottom legacy_version 0x0303 random 32 bytes session_id 32 bytes cipher_suites ordered compression 0x00 extensions { } supported_versions supported_groups signature_algorithms key_share alpn, sni, ... pre_shared_key (last) *The fixed header fields are mostly invariant; the cipher list order and the extension block are where browsers differ, and where the fingerprint lives.*

Chrome: the browser that keeps moving the target

Chrome is the reference point, because Chrome is most of the web and because Google has changed its ClientHello more aggressively than anyone else. Three changes matter for the catalog: GREASE in 2016, two Google-specific extensions, and the post-quantum key share in 2024.

A Chrome 98 ClientHello on Windows 10 from early 2022 is a good snapshot of the pre-permutation era. It advertises 16 cipher suites, opening with the three TLS 1.3 suites (TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384, TLS_CHACHA20_POLY1305_SHA256) followed by ECDHE suites for both ECDSA and RSA, then legacy AES suites. The extension list, in the fixed order Chrome used at the time, ran through server_name, extended_master_secret, renegotiation_info, supported_groups, ec_point_formats, session_ticket, ALPN, status_request, signature_algorithms, signed_certificate_timestamp, key_share, psk_key_exchange_modes, supported_versions, and padding, with GREASE values inserted before and after the block. That handshake produced a stable JA3 of b32309a26951912be7dba376398abc3b for that build, which is exactly the kind of value JA3-based blocklists keyed on.

Two of Chrome’s extensions are Google inventions and worth naming because they are strong Chromium tells. One is compress_certificate (extension type 27), which advertises support for compressed certificates, with Chrome offering Brotli. The other is application_settings, the ALPS extension, which lets the client and server exchange HTTP/2 SETTINGS during the TLS handshake instead of waiting for the connection to open. ALPS is not something NSS or Apple’s stack sends, so its presence in a ClientHello is a near-certain sign of Chromium underneath. This is the kind of detail that ties the TLS fingerprint to the HTTP/2 layer; the connection between them is the subject of how Cloudflare uses TLS and HTTP/2 fingerprints in bot scoring.

Then came January 2023. Chrome started randomizing the order of its extensions in the ClientHello, shipped through versions 108 and 109 and visible in the wild from January 20, 2023. The pitch from Google’s David Adrian on the Chromium blink-dev list was anti-ossification: a fixed extension order encourages server implementers to fingerprint Chrome and assume specific behavior, which boxes Chrome in. Randomizing the order removes that crutch. The only ordering constraint kept is the one TLS 1.3 mandates, that pre_shared_key, if present, is the last extension. Everything else shuffles per connection. Fastly watched its most common Chrome JA3 (cd08e31494f9531f560d64c695473da9) fall off a cliff starting that day as browsers updated. With around 15 permutable extensions, the number of orderings is close to 15 factorial, on the order of 10^12, so JA3, which hashes extension order directly, effectively gets a fresh value per connection. That single change is why half this blog’s TLS posts exist.

In April 2024, Chrome 124 turned on a post-quantum key share by default, and the ClientHello grew by more than a kilobyte. We will treat the post-quantum transition on its own below because it spans every browser, but the short version is that Chrome led it, shipping the pre-standard X25519Kyber768Draft00 group first and then switching to the standardized X25519MLKEM768 in Chrome 131 in November 2024.

Chrome ClientHello, the changes that moved the fingerprint v55 GREASE 2016 v79 TLS 1.3 default 2019 v108 extension permutation Jan 2023 v124/131 PQC key share 2024 *Four inflection points. GREASE and TLS 1.3 set the baseline; permutation and the post-quantum key share are the changes that broke the fingerprinting tools built on top of it.*

Firefox: the stable one, until post-quantum

Firefox is the conservative counterpart. It ships NSS, and for most of the catalog window its ClientHello stayed comparatively still, which is exactly why Firefox JA3 fingerprints held up far better than Chrome’s. NSS advertises a different cipher order and a different extension set than BoringSSL, so a Firefox handshake is easy to tell apart from a Chrome one at a glance, and historically that telling-apart was stable across long runs of connections.

A representative Firefox cipher list opens with the same three TLS 1.3 suites, then ECDHE-ECDSA and ECDHE-RSA suites with ChaCha20 and AES variants. The extension set carries the Firefox-flavored pieces: delegated_credentials, which Firefox validated and shipped support for in 2019 to let servers use short-lived credentials (capped at seven days’ validity) bound to their CA certificate, and later the privacy extensions around Encrypted Client Hello.

Whether Firefox randomizes its extension order is the one point in this catalog where the public sources disagree, and I will not paper over it. Mozilla has a tracking bug for extension order randomization (Bugzilla 1789436), and several secondary write-ups claim Firefox randomizes from around version 114 onward. Other sources, including write-ups focused specifically on the Chrome change, do not mention Firefox doing it at all, and Firefox JA3 values were still being treated as stable identifiers well after that. The exact current behavior of Firefox extension ordering is not something I can pin to a primary Mozilla source with a version number, so treat any claim that “Firefox randomizes like Chrome” as unconfirmed rather than settled. What is documented is the post-quantum change.

Firefox enabled X25519MLKEM768 by default for HTTPS in Firefox 132, controlled by the security.tls.enable_kyber pref in about:config, and extended it to QUIC and HTTP/3 in Firefox 135. That puts Firefox on the same post-quantum footing as Chrome, give or take a version, by late 2024. The result is that a current Firefox ClientHello, like a current Chrome one, carries a key share over a kilobyte long, and the absence of that key share in something claiming to be a recent Firefox is now a tell.

Two Firefox-specific privacy features round out the modern fingerprint. Firefox shipped Encrypted Client Hello support starting in Firefox 98 and enabled it by default from Firefox 119, which moves the privacy-sensitive extensions into an encrypted ClientHelloInner advertised inside an outer handshake. What ECH does and does not hide is its own subject in Encrypted Client Hello (ECH): what it hides, and the short version for this catalog is that ECH hides the inner SNI but leaves the outer ClientHello fingerprintable, so the browser is still identifiable even when the destination hostname is not.

Safari: Apple’s stack, slow to change

Safari is the odd one out because Apple does not use BoringSSL or NSS. Safari’s TLS comes from Apple’s own networking stack, and its ClientHello reflects Apple’s cipher and extension choices, which are distinct enough that a Safari handshake rarely gets confused for anything else. Apple’s stack supports TLS 1.0 through 1.3 and DTLS, prefers cipher suites with forward secrecy, and offers both AES-128 and AES-256.

The notable property for fingerprinting is stability. Safari has historically not randomized its extension order, so both its JA3 and the normalized JA3N stay constant across connections for a given build, which makes Safari one of the easier browsers to fingerprint reliably and one of the harder ones to impersonate convincingly, because every field has to match a real Apple build. A Safari-on-iOS JA3N observed in the wild looks like:

771,4865-4866-4867-49196-49195-52393-49200-49199-52392-49162-49161-49172-49171-157-156-53-47-49160-49170-10,0-5-10-11-13-16-18-21-23-27-43-45-51-65281,29-23-24-25,0

That decodes to legacy version 771 (TLS 1.2 in the version field, with TLS 1.3 offered via supported_versions), a specific ordered cipher list, the extension set, the named groups 29-23-24-25 (X25519, secp256r1, secp384r1, secp521r1), and no EC point format beyond the uncompressed default. The exact byte layout of any one Safari version is not something Apple documents publicly, so the per-version detail here comes from observed traffic rather than a changelog, and Apple’s release notes do not call out ClientHello changes the way Chrome’s blink-dev threads do.

Safari’s post-quantum status lagged the other two. Where Chrome and Firefox had X25519MLKEM768 on by default through 2024, Apple’s rollout came later and across the operating system rather than a single browser version, so a Safari ClientHello without a post-quantum key share was normal for longer than it was for Chrome or Firefox. That makes “no PQC key share” a weaker signal for Safari than for Chrome, which is the sort of per-browser asymmetry that an accurate fingerprint database has to encode rather than assume away.

Edge and the rest of the Chromium family

Microsoft Edge is Chromium. It ships the same BoringSSL that Chrome does and the Chromium-based Edge does not use the Windows SChannel TLS stack for its own connections. The practical consequence is that Edge’s ClientHello is, to first approximation, Chrome’s ClientHello: the same cipher order, the same application_settings and compress_certificate extensions, the same GREASE behavior, the same extension permutation, and the same post-quantum key share on the same rough version cadence. Telling Edge apart from Chrome at the TLS layer alone is genuinely hard, because the layer is identical by construction.

The same holds for the broader Chromium family: Brave, Opera, Vivaldi, Samsung Internet, and the rest inherit BoringSSL’s handshake. Where these browsers differ from Chrome tends to be above TLS, in headers, in HTTP/2 SETTINGS, or in JavaScript-surface behavior, not in the ClientHello. That is why TLS fingerprinting alone collapses the entire Chromium ecosystem into roughly one bucket, and why edge systems combine the TLS fingerprint with the HTTP/2 fingerprint to split it back apart. The HTTP/2 side of that, the SETTINGS frame and the Akamai format, is covered in HTTP/2 fingerprinting.

Library determines the bucket Chrome BoringSSL Edge BoringSSL (same) Brave / Opera BoringSSL (same) Firefox NSS (distinct) Safari Apple stack (distinct) *Every Chromium browser shares BoringSSL, so they share a ClientHello fingerprint. Firefox and Safari sit in their own buckets because their TLS libraries differ.*

GREASE: the 2016 change that has to be filtered out

GREASE is the oldest of the structural changes in this catalog and the one that quietly reshaped how every fingerprinting tool has to parse a ClientHello. Generate Random Extensions And Sustain Extensibility, standardized as RFC 8701, reserves a set of protocol values that a client may advertise to keep servers honest about handling unknown values. The reserved values follow a pattern: cipher suite and ALPN values like {0x0A,0x0A}, {0x1A,0x1A}, on up through {0xFA,0xFA}, and the matching extension and named-group values 0x0A0A, 0x1A1A, through 0xFAFA. A server that sees one of these must ignore it and negotiate with the real values, and a server that chokes on one is a server that would have choked on a genuine future extension.

Google deployed GREASE in Chrome 55 in late 2016, before the RFC was finalized, precisely to gather data on how much of the deployed server base mishandled unknown values. Because GREASE values are random per connection, a fingerprinting tool that naively included them would see a different cipher list or extension list every time. So every serious fingerprint, JA3 included, strips GREASE before hashing. JA4 makes the same rule explicit: ignore GREASE values anywhere they appear, in cipher counts, extension counts, and the hashed lists alike. The presence and placement of GREASE is itself a weak signal (Chrome puts a GREASE extension first and another late in the block), but the values inside are noise by design. The deeper treatment is in GREASE values in the ClientHello.

Extension permutation and the death of JA3

The single most consequential change in this catalog is Chrome’s extension permutation, because it broke the dominant fingerprinting scheme of the previous decade. JA3, introduced by Salesforce engineers in 2017, hashes a comma-joined string of TLS version, cipher list, extension list, named groups, and EC point formats. The extension list is in the order the client sent it. That worked because, before 2023, the order was fixed per browser build. Permutation made the order random, so the same Chrome build now emits a different JA3 on essentially every connection.

The fix the industry converged on has two flavors. JA3N normalizes by sorting the extension list before hashing, which restores a stable value for permuted clients at the cost of throwing away order as a signal. JA4, released by FoxIO in 2023, rebuilds the scheme around sorting from the start and adds discriminating fields that survive permutation. A JA4 fingerprint reads as three sections joined by underscores. The first, ten characters, is human-readable: protocol (t for TLS over TCP, q for QUIC), TLS version (13), an SNI flag (d if SNI is present, i if not), a two-digit cipher count, a two-digit extension count, and two characters from the first ALPN value (h2 for HTTP/2). The second section is a twelve-character truncated SHA-256 of the cipher suites sorted into hex order, GREASE removed. The third is a twelve-character truncated SHA-256 of the extensions, also sorted and GREASE-filtered, with SNI and ALPN themselves excluded from the list, followed by the signature algorithms in their original order. So a JA4 like t13d1516h2_8daaf6152771_e5627efa2ab1 says: TLS over TCP, TLS 1.3, SNI present, 15 ciphers, 16 extensions, ALPN h2, then the two sorted hashes. Sorting is what makes it survive permutation, and the human-readable prefix is what makes it useful for at-a-glance triage.

Reading a JA4 fingerprint t13d1516h2 _ 8daaf6152771 _ e5627efa2ab1 human-readable prefix sorted ciphers, SHA-256 sorted exts + sig algs t = TLS/TCP 13 = TLS 1.3 d = SNI present 15 = cipher count 16 = ext count h2 = ALPN Sorting both lists is what survives Chrome's permutation. *JA4 puts the count and ALPN data in the clear and sorts the hashed lists, so a permuted Chrome handshake lands on one stable value instead of a new hash per connection.*

The migration from one scheme to the other is the subject of TLS fingerprinting: from ClientHello bytes to JA4, and the permutation change specifically is in TLS extension ordering and the Chrome randomization that broke JA3. For this catalog the takeaway is narrow: any per-version fingerprint you record for Chrome 108 or later has to be a sorted one, or it will not match the same browser twice.

The post-quantum transition, 2024 to 2026

The live story in the catalog is post-quantum key exchange, because it is the change currently separating up-to-date browsers from everything pretending to be one. The mechanism is a hybrid key agreement that runs a classical X25519 exchange and a post-quantum ML-KEM-768 encapsulation together, so the session is safe as long as either one holds. ML-KEM-768 is the NIST-standardized form of what was called Kyber-768.

Chrome went first. Chrome 124 enabled the pre-standard X25519Kyber768Draft00 group by default in April 2024, then Chrome 131 switched to the standardized X25519MLKEM768 in November 2024. Firefox enabled X25519MLKEM768 by default in Firefox 132 and added QUIC and HTTP/3 coverage in Firefox 135. Apple’s rollout came across the operating system and landed later than the other two. By late 2025 both Chrome and Firefox offered the hybrid group by default, and Cloudflare reported it negotiated on essentially all of its sites from October 2024.

The byte-level details are what make this a fingerprint rather than a footnote. The pre-standard X25519Kyber768Draft00 group used IANA codepoint 0x6399 (decimal 25497). The standardized X25519MLKEM768 uses 0x11ec (decimal 4588). An ML-KEM-768 public key is 1,184 bytes; X25519 adds 32; the combined client key share runs around 1,216 bytes, where a classic X25519-only share was 32 bytes plus a few bytes of framing. There is a naming quirk worth recording: despite the name X25519MLKEM768, the concatenation order in the key share puts the ML-KEM portion first and X25519 second. The result is a ClientHello that jumps from a few hundred bytes to over 1,400, often spanning two TCP segments, with a key_share and supported_groups that both advertise group 0x11ec.

Client key share size, classic vs hybrid X25519 32 bytes X25519MLKEM768 ~1,216 bytes (group 0x11ec) *The post-quantum key share is roughly thirty-eight times the size of the classic one. That bulk is hard to fake without a TLS stack that actually implements ML-KEM.*

For anyone running a server-side fingerprint, the post-quantum key share is a gift, because it is expensive to fake. A scraper can rewrite its User-Agent to claim Chrome 131 in a second. It cannot produce a valid 1,216-byte hybrid key share unless its TLS library actually implements ML-KEM, and most do not. So a connection that announces a recent Chrome in HTTP but lacks the post-quantum group in its ClientHello is contradicting itself, and the contradiction is visible at the TLS layer before HTTP starts. Reports put post-quantum key shares on the order of 90-plus percent of real Chrome traffic by 2026, which means the absence of one is now anomalous rather than common. How edge systems capture and act on this is the subject of server-side TLS fingerprinting libraries.

What a current fingerprint looks like, per browser, in 2026

Pulling the catalog together into a snapshot. A current Chrome or any Chromium browser sends a ClientHello around 1,700 bytes, with GREASE in the cipher list and at both ends of a permuted extension block, the application_settings and compress_certificate extensions that mark it as Chromium, and an X25519MLKEM768 key share at group 0x11ec. Its JA3 is effectively random per connection thanks to permutation, so it is identified by a sorted fingerprint like JA4, which lands on one stable value for the build. Edge, Brave, Opera, and the rest of the family produce the same TLS fingerprint and have to be split apart at the HTTP/2 layer or above.

Firefox sends an NSS-flavored handshake, distinct cipher order, the delegated_credentials extension, Encrypted Client Hello on by default since Firefox 119, and the same X25519MLKEM768 key share since Firefox 132. Its fingerprint is its own bucket, easy to separate from Chrome, with the open question being exactly how much its extension order moves per connection. Safari sends an Apple-stack handshake that has historically stayed stable across connections, no extension permutation, a later post-quantum rollout tied to the OS rather than a browser version, which makes a missing post-quantum group a weaker signal for Safari than for the other two.

The thread through all of it is that “looks like Chrome” has a half-life now. Before 2023 a fixed JA3 string identified a browser build for years, and impersonating it meant matching one stable hash. After permutation, after the post-quantum jump, the target moves every few months: a new key share group, a new cipher, a new extension on a new Chrome milestone. The catalog above is a snapshot of mid-2026, and the most reliable thing in it is the rate of change. A fingerprint database that is not refreshed against real, current browser builds drifts out of date on roughly the same cadence Chrome ships, which is every four weeks. The handshake that proves you are a real browser this quarter is evidence you are a stale one next quarter.


Sources & further reading

Further reading