Explainer

What Is JA3/JA4 Fingerprinting? How a TLS Handshake Identifies Your Client

JA3 and JA4 turn your TLS ClientHello into a fingerprint that names the software behind a connection. How the hash is built, why a proxy does not change it, and how to survive it.

HProxy Team · ·Updated July 17, 2026 ·8 min read
HProxy. Explainer

Skip the dead lists.

Our free proxy list re-checks every exit every few minutes across 100+ countries, with a live last-checked time, so you copy IPs that worked moments ago, not a stale text dump.

Open the free proxy list

You bought clean residential IPs, your scraper still gets blocked, and the IP checks out as a genuine home connection when you look it up. The block is not coming from the IP. It is coming from the first handful of bytes your program sends before it asks for a single web page, the TLS handshake, which quietly announces what software you actually are. JA3 and JA4 are the two schemes that turn those bytes into a short fingerprint, and once you understand what goes into that fingerprint, the "clean IP, still blocked" mystery dissolves.

We run a proxy network and watch this catch people constantly, so here is the mechanism at the field level: what your client tells a server before HTTP starts, how that gets hashed into an identity, why swapping IPs does nothing to it, and what actually moves the number.

The raw material: what a ClientHello gives away

Every HTTPS connection opens with a TLS handshake, and the client speaks first, with a message called the ClientHello. Its job is to propose terms: which TLS version, which encryption options, which features the client supports. Per RFC 8446, the ClientHello carries, in order, a legacy_version, a random, a legacy_session_id, a cipher_suites list, legacy_compression_methods, and a block of extensions. Two of those are the tell.

The cipher_suites field is "a list of the symmetric cipher options supported by the client, in descending order of client preference." That ordering is not random, it is baked into the TLS library that built the message. The extensions block carries features like the server name being requested (SNI), the application protocols offered (ALPN), and the cryptographic groups the client supports (the supported_groups extension, which older tooling still calls by its previous name, elliptic curves), plus the EC point formats it accepts.

Here is the point that makes fingerprinting possible: no two TLS stacks assemble that list the same way. Chrome offers one set of ciphers in one order with one set of extensions. Firefox differs. Python's requests, Go's net/http, curl, each has its own signature ordering and feature set. The ClientHello is a byproduct of which library you compiled against, and it is sent in the clear, before any encryption is active, so anyone on the receiving end can read it in full.

JA3: five fields, one MD5 hash

JA3, created at Salesforce by John Althouse, Jeff Atkinson, and Josh Atkins, is the method that made this practical. It reads five decimal values out of the ClientHello, in this fixed order:

SSLVersion,Cipher,SSLExtension,EllipticCurve,EllipticCurvePointFormat

The fields are joined with commas, the multiple values inside a field are joined with hyphens, and the resulting string is MD5-hashed, in the project's own words, "to produce an easily consumable and shareable 32 character fingerprint." The JA3 repository gives a worked example. This raw string:

769,47-53-5-10-49161-49162-49171-49172-50-56-19-4,0-10-11,23-24-25,0

769 is the TLS version (decimal for 0x0301, TLS 1.0), then the ordered cipher list, then the extensions 0-10-11 (SNI, supported groups, EC point formats), then the curves 23-24-25, then the point format 0. MD5 that whole line and you get:

ada70206e40642a3e4461f35503241d5

That 32-character string is now a stable label for the software that sent the handshake. Every connection from that same client build hashes to the same value, and a different client build hashes to a different one.

Why this is a detection goldmine

A fingerprint of the client software is powerful because it is checked before the site reads a single one of your HTTP headers, and because it is hard to lie about accidentally. Cloudflare, which computes these on its edge, describes the fingerprint as identifying "TLS clients based on how they initiate connections," and notes that "each client type (browser, bot, or application) has distinct connection characteristics, so the resulting fingerprint acts as a stable identifier across different destination IPs, ports, and certificates."

Read that last clause slowly, because it is the whole reason this matters to proxy users: the fingerprint is stable across destination IPs. Changing where your traffic exits does not change the handshake your client builds.

The classic catch is a contradiction between two signals. Your HTTP User-Agent header is a string you can set to anything, so a bot claims to be Chrome on Windows. But the TLS fingerprint underneath is computed from the actual library, and if that library is Python or Go, its JA3 does not match Chrome's. Now the site is holding a request that says "I am Chrome" at the header level and "I am a Python script" at the handshake level, and that mismatch is a far stronger bot signal than either fact alone. We put this in the wider detection picture in how websites detect proxies, and it is exactly why we warn in what is a residential proxy that a clean IP only buys you the first gate.

Why your proxy does not touch it

This is the part that surprises people, so it is worth being precise. When you route TLS traffic through a proxy, the proxy relays the encrypted connection without terminating it. A SOCKS5 proxy or an HTTP CONNECT tunnel opens a raw pipe to the target and shovels your bytes through; your client performs the TLS handshake directly with the destination, end to end. The proxy never rebuilds the ClientHello, because it never sees inside the TLS session at all.

So the two things a proxy and a fingerprint control are completely separate. The proxy changes the exit IP, the address the site sees. The fingerprint reflects your TLS library, the software the site infers. Buy the cleanest residential IP in the world, send your handshake from Python's default stack, and the site still sees a Python client arriving from a home IP, which is its own kind of suspicious. The IP and the handshake are two independent gates, and residential only unlocks the first.

JA4: the same idea, hardened against evasion

JA3 had a soft spot. Because it hashes the cipher and extension lists in the order they arrive, you could defeat matching by shuffling that order, a trick called cipher stunting, and Google eventually shipped extension-order randomization in Chromium that scattered a single browser across many JA3 values by accident. Order-sensitivity meant the fingerprint could be scrambled without changing the client's actual capabilities.

JA4, from the same lead author now at FoxIO, fixes this by design. The JA4 specification splits the TLS client fingerprint into three readable parts, a_b_c:

  • JA4_a is human-readable metadata: the transport (TCP or QUIC), the TLS version, whether SNI is present, the count of cipher suites and extensions, and the ALPN value. You can eyeball it without a lookup table.
  • JA4_b is a hash of the cipher list, sorted before hashing. Sorting is the fix: it "defeats cipher stunting" because reordering the ciphers no longer changes the hash.
  • JA4_c is a hash of the extensions plus signature algorithms, also sorted, which "makes fingerprints resistant to the extension ordering randomization that Google introduced in Chromium."

Sorting means the fingerprint now reflects what the client can do, not the order it happened to list things in, so scrambling the order stops working as evasion. The readable structure also enables partial matching: JA4 is built so that a client can be tracked by its a and c parts even when b shifts, and FoxIO notes the fingerprints change only "about once a year" as TLS libraries update. JA4 itself is BSD-3-Clause and patent-free; the extended JA4+ suite (JA4S, JA4H, JA4X and others) is under FoxIO's own license.

What actually moves the number

If a proxy does not change your fingerprint, what does? Only changing the handshake itself.

That is what tooling like curl-impersonate and the uTLS library exist to do. They rebuild the ClientHello to match a real browser's exact cipher list, extensions, and ordering, so the resulting JA3 or JA4 lands on Chrome or Firefox instead of on the default library signature. Done properly, the handshake and the User-Agent finally agree, and the contradiction that flagged you disappears. The important nuance in the JA4 era: because sorting defeats naive shuffling, it is not enough to randomize your ClientHello, you have to make it genuinely match a real client's, which is a higher bar and the reason off-the-shelf "random TLS" options stopped working against modern detection.

The honest summary is that JA3 and JA4 are not the enemy of a good IP, they are a separate axis from it. A believable exit and a believable handshake are two different problems, and you need both to pass. Our residential proxies solve the IP half convincingly, with real ISP addresses that read as genuine home users, and our proxy checker shows you what a site sees at the network layer for any address. The handshake half lives in your client, so pair a trustworthy exit with a browser-accurate TLS stack and stop leaking the mismatch that no proxy can hide for you.

Sources and further reading

  • Salesforce, "JA3: TLS/SSL client fingerprinting". The original method: the five ClientHello fields, the comma-and-hyphen join, the MD5 hash, and the worked example string and resulting fingerprint. Archived read-only as of May 2025; BSD-3-Clause.
  • FoxIO, "JA4+ Network Fingerprinting". Defines the a_b_c JA4 TLS client fingerprint, the sorting that defeats cipher stunting and Chromium extension randomization, and the licensing split between JA4 (BSD-3) and JA4+ (FoxIO License).
  • Cloudflare, "JA3/JA4 fingerprint". How an edge network computes these fingerprints and why they stay stable across destination IPs, ports, and certificates.
  • RFC 8446: The Transport Layer Security (TLS) Protocol Version 1.3. Section 4.1.2 defines the ClientHello, including the ordered cipher_suites list and the extensions (such as supported_groups) that fingerprinting reads.

Frequently asked questions

What is JA3 fingerprinting?
JA3 is a method for identifying the software making a TLS connection. It reads five fields from the client's TLS ClientHello (the version, the offered cipher list, the extensions, the supported elliptic-curve groups, and the EC point formats), joins them into one string, and MD5-hashes it into a 32-character fingerprint. Different clients produce different fingerprints, so a site can tell Chrome from a Python script before any web request is sent.
What is the difference between JA3 and JA4?
JA3 hashes its fields in the exact order the client sent them, so simply shuffling cipher or extension order changes the hash and evades matching. JA4, from the same lead author, sorts the ciphers and extensions before hashing and splits the fingerprint into three readable parts, so reordering no longer defeats it. JA3 was archived in May 2025; JA4 is the successor, though both are still seen in the wild.
Does a proxy change my JA3 or JA4 fingerprint?
No. The fingerprint is computed from your TLS handshake, which your client builds and the proxy relays untouched. A proxy changes the exit IP the site sees, not the ClientHello your software sends. So a pristine residential IP paired with a Python fingerprint still reads as a script, because the two signals disagree.
Can you spoof or randomize a TLS fingerprint?
Yes, within limits. Tools like curl-impersonate and the uTLS library rebuild the ClientHello to match a real browser's exact byte layout, so the fingerprint lands on Chrome or Firefox instead of the default library signature. Naive randomization (shuffling cipher order) beat JA3 but not JA4, which sorts before hashing, so the fingerprint has to actually match a real client, not just be scrambled.
Why does my clean residential IP still get blocked?
Almost always a fingerprint mismatch. The IP passes the network check, but if your TLS fingerprint says one thing and your User-Agent header claims another (Chrome on Windows over a Python handshake, for example), the contradiction flags the request no matter how good the IP is. The clean IP buys the first gate; the fingerprint has to pass the second.
Is JA3 still used, or has JA4 replaced it?
Both are in use. The original JA3 repository was archived and made read-only in May 2025, and the same author now maintains JA4 at FoxIO, but plenty of detection systems still compute JA3 alongside JA4. Treat JA3 as the widely deployed classic and JA4 as the more evasion-resistant current standard.

Get proxies that are alive right now

Our free list re-checks every exit every few minutes and shows a last-checked time, so you copy IPs that worked moments ago, not a stale text dump. When the location has to survive a real check, the paid network holds up.

47M+ proxy checks run · 100+ countries · HTTP / HTTPS / SOCKS · re-checked every few minutes · no signup