Blog / Fingerprinting explained

TLS fingerprinting (JA3 and JA4) explained for browser users

How sites fingerprint the TLS handshake and HTTP/2 settings before any page loads, what JA3 and JA4 measure, what changes them, and how to check your own browser.

By Charles Lee · 7 min read

TLS fingerprinting identifies the software that opens a secure connection from the first message it sends, before any page loads and before any JavaScript runs. Every browser, library and tool builds that message in its own way, so a server can tell Chrome from a Python script, or a real browser from a tool that only claims to be one, by reading a single packet. JA3 and JA4 are the two common ways of turning that message into a short, comparable string.

This guide explains what is in the handshake, how JA3 and JA4 summarise it, how HTTP/2 adds a second network-level fingerprint, what changes these values in practice, and how to check your own browser.

The ClientHello: the first thing a server sees

When your browser connects to an https:// site, it starts the TLS handshake with a message called the ClientHello, defined in RFC 8446 for TLS 1.3. It lists, among other things:

  • the TLS versions the client supports;
  • the cipher suites it offers, in its order of preference;
  • the extensions it includes, such as server name, supported groups, key shares, signature algorithms and ALPN (which application protocols it speaks, like h2 for HTTP/2);
  • the supported groups (the key-exchange curves) and signature algorithms.

None of this is secret, and none of it identifies you as a person. But each TLS library makes different choices, and each browser configures its library in its own way. The ClientHello of a given Chrome version on a given operating system looks the same on millions of computers, and it looks different from the ClientHello of a command-line tool, a scripting library or an older browser.

JA3: the first widely used format

JA3, published in 2017, takes five fields from the ClientHello (the TLS version, the cipher suites, the extensions, the supported groups and the point formats), writes each list as numbers joined by dashes, joins the five with commas, and hashes the result with MD5. Random placeholder values that browsers add on purpose (GREASE, RFC 8701) are left out so they do not change the hash.

JA3 worked well for years, then Chrome changed one detail: from early 2023 it began sending its TLS extensions in a random order on each connection. The set of extensions stayed the same, but JA3 keeps the order, so every Chrome connection now produced a different JA3 hash. That made raw JA3 much less useful for recognising browsers.

JA4: a format built for that

JA4 sorts the lists before hashing, so the order no longer matters, and it keeps a readable prefix. Take the value we measured for Browz on the day of our test, which was the same as stock Google Chrome's:

textt13d1517h2_8daaf6152771_cb7bf5808d99

It has three parts:

  1. **t13d1517h2**, readable at a glance: t for TLS over TCP (q would mean QUIC), 13 for TLS 1.3, d because the client sent a domain name (i would mean it connected to a bare address), 15 cipher suites, 17 extensions, and h2 as the first application protocol offered.
  2. **8daaf6152771**, the first 12 characters of a SHA-256 hash of the sorted cipher suites.
  3. **cb7bf5808d99**, a truncated hash of the sorted extensions together with the signature algorithms.

Because the prefix is readable, an analyst can see at once that a client offering two cipher suites and four extensions is not a desktop browser, whatever its user agent says.

HTTP/2: a second fingerprint on the same connection

Once TLS is set up, a browser that speaks HTTP/2 (RFC 9113) sends a few frames before its first request: a SETTINGS frame with values such as the header table size, the initial window size and the maximum header list size; a WINDOW_UPDATE; sometimes PRIORITY information; and then the request headers. Clients also differ in the order of the pseudo-headers :method, :authority, :scheme and :path.

These choices are written into each HTTP implementation, so they form another fingerprint, often recorded in a compact format that joins the settings, the window update, the priority data and the pseudo-header order. A request whose user agent says Chrome but whose SETTINGS and header order belong to another HTTP library contradicts itself in a way no page script can see or repair.

What changes a TLS or HTTP/2 fingerprint

For a browser user, a few situations matter most.

SituationWhat the server sees
An ordinary browserThe handshake of that browser version on that operating system
A script or library making its own requestsThe library's own TLS and HTTP stack, often with a browser user agent pasted on top
A proxy that tunnels the connection (HTTP CONNECT, SOCKS5, SSH)The browser's own handshake, because the proxy only forwards encrypted bytes
A proxy or security product that decrypts and re-encrypts trafficThe proxy's handshake, not the browser's
An older or unusual browser buildA handshake that matches few real visitors

Two points follow. First, automation that goes through a real browser keeps the browser's handshake: Playwright or Puppeteer driving a browser over the DevTools protocol still connects with that browser's network stack, whereas a script that fetches pages with its own HTTP client does not. Second, the kind of proxy matters: a tunnelling proxy leaves the fingerprint alone, while a proxy that terminates TLS replaces it with its own. Our guides to proxy types explain the protocols.

Servers rarely act on a TLS fingerprint alone. They compare it with the user agent, the client hints and the behaviour of the visit, which is why agreement between all of them is what counts. The post on user agent and client hints covers the header side.

How to check your own browser

  1. Open a public TLS echo page, such as https://tls.peet.ws/api/all, in the browser or profile you want to test. It shows the JA3 and JA4 values, the cipher suites and extensions, and the HTTP/2 settings and pseudo-header order your browser sent.
  2. Open the same page in stock Google Chrome of the same version on the same operating system and compare. The JA4 value and the HTTP/2 settings should match. The JA3 hash will differ between runs in current Chrome because of the extension shuffle; that is expected.
  3. If you use a proxy, run the check through it as well. If the JA4 value changes when the proxy is on, the proxy is terminating TLS rather than tunnelling it.
  4. To see what a non-browser client looks like, fetch the same address from the command line, for example with curl https://tls.peet.ws/api/all. The prefix of the JA4 value will show a different number of ciphers and extensions.

Our free browser fingerprint test does not read TLS, because a page's scripts never see the handshake; the server does. Use the two together: the fingerprint test for what JavaScript reads, the echo page for what the network shows.

How Browz handles the network fingerprint

Browz changes what a site reads inside the browser engine, and it leaves the engine's own network stack unmodified. TLS and HTTP/2 come from that stack, so each profile opens connections the way the browser version it runs does. On the public test site tls.peet.ws, a fresh Browz profile was identical to stock Google Chrome: JA4 t13d1517h2_8daaf6152771_cb7bf5808d99, the same ciphers, groups and HTTP/2 settings. The result is on the anti-detect page, with the other dated results.

The rest of the profile is set to agree with that handshake:

  • User agent and client hints agree. They match the browser version the profile really runs, and the operating system it presents.
  • Automation does not change the fingerprint. Driving a profile with Playwright, Puppeteer or an AI agent over MCP leaves its device and network fingerprint unchanged, because the requests still go through the browser.
  • Proxies tunnel. Each profile can have its own HTTP, HTTPS, SOCKS or SSH proxy, and DNS resolves at the proxy by default.

For why an engine-level approach keeps headers and JavaScript in step, read engine-level fingerprint protection. For our JavaScript-side result, Browz 0.1.9 on macOS passed 15 of 15 consistency checks in our free test on 13 November 2026 (our own result).

Frequently asked questions

What is a JA4 fingerprint?

It is a short string that summarises the TLS ClientHello a client sends: a readable prefix with the protocol, TLS version, whether a domain name was sent, the counts of cipher suites and extensions and the first application protocol, followed by two truncated hashes of the sorted cipher suites and extensions. Because the lists are sorted, the value stays the same when a browser shuffles its extensions.

Is JA3 still useful?

For many non-browser clients, yes, because their handshake does not change between connections. For current Chrome-based browsers, raw JA3 changes on every connection because the extension order is randomised, so JA4 or a sorted variant of JA3 is more useful.

Can a website see my TLS fingerprint through a VPN or proxy?

A VPN or a tunnelling proxy forwards your browser's encrypted connection, so the website sees your browser's handshake from the exit address. A proxy that decrypts and re-encrypts traffic makes its own handshake, so the website sees the proxy's fingerprint instead.

Can JavaScript change a TLS fingerprint?

No. The handshake happens in the browser's network stack before any page code runs, and page scripts cannot read or change it. That is why a user agent changed by a script, on top of an unchanged network stack, can disagree with what the server measures.

Does HTTP/3 have a fingerprint too?

Yes. HTTP/3 runs over QUIC, which carries its own TLS handshake, and JA4 marks it with q instead of t at the start. The same principle applies: the values come from the client's network stack.

Get started

Every account, every agent. Its own browser.

For Mac, Windows and Linux. Free for 5 profiles.