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_cJA4 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_suiteslist and the extensions (such assupported_groups) that fingerprinting reads.