Learn how TLS fingerprinting works, how JA3 and JA4 expose bots and intercepting proxies at the network layer, and how to test your own TLS handshake.
Every HTTPS connection begins with a handshake. Before a byte of application data flows, your browser and the server negotiate which encryption algorithms to use — and the exact way your browser conducts that negotiation creates a fingerprint as distinctive as a canvas or WebGL hash. TLS fingerprinting extracts that signature from the handshake to identify the client software making the request, without relying on JavaScript or cookies at all.
Key Takeaways
- The TLS ClientHello is the fingerprint. When your browser opens an HTTPS connection, it sends a ClientHello listing its supported cipher suites, extensions, and elliptic curves — in an order dictated by the TLS library, not by you.
- JA3 hashes five ClientHello fields into a 32-character MD5 string; the same browser build on the same OS produces the same JA3 hash.
- JA4 improves on JA3 with a human-readable, sortable format that survives GREASE randomization and extension reordering.
- Servers combine TLS fingerprints with User-Agent headers to catch spoofed bots: a request that claims to be Chrome but carries a Python
requestshandshake is an immediate red flag. - Changing your TLS fingerprint requires swapping or reconfiguring the TLS library itself — browser settings and extensions cannot touch it.
What Is TLS Fingerprinting?
HTTPS traffic is encrypted, but the setup is not. To establish an encrypted session, your client sends a ClientHello message in plaintext, containing:
- A legacy TLS version field (typically
0x0303; a TLS 1.3 client signals its real maximum version in thesupported_versionsextension, which JA3 does not read) - A list of cipher suites it is willing to use, in preference order
- A list of extensions (server name indication, supported groups, key share, ALPN, and more)
- Supported elliptic curves (named groups) for key exchange
- Supported elliptic curve point formats
These fields are dictated by the TLS library linked into the application — not by the user, not by the operating system, and not by the domain being visited. Chrome's BoringSSL stack, Firefox's NSS, Safari's Secure Transport, and Python's OpenSSL bindings each produce a recognizably different ClientHello. TLS fingerprinting reads those differences and turns them into an identifier.
The TLS 1.3 standard (RFC 8446) mandates a set of required extensions, but leaves the ordering of optional extensions and the choice of cipher suites from the permitted set up to the implementation — which is exactly where fingerprints live.
JA3: The First Widely Adopted Fingerprint
JA3 was published by Salesforce engineers (John Althouse, Jeff Atkinson, and Josh Atkins) in 2017 as a simple, deployable way to fingerprint TLS clients at the network layer without running any code on the client. It works by extracting five fields from the ClientHello and concatenating them as comma-separated values:
TLSVersion,Ciphers,Extensions,EllipticCurves,EllipticCurvePointFormats
Multi-value fields are joined by hyphens. The resulting string is then MD5-hashed to produce a 32-character fingerprint such as 769a07eb7f17701dd0ad09b9d04ee785.
A few properties make JA3 both useful and limited:
Why it works: The hash is stable across sessions. The same Chrome version on the same OS always produces the same JA3 because the TLS library does not change between requests.
Why it breaks: JA3 captures extension order and cipher order, so a single implementation that randomizes ordering — or a browser update that appends one cipher — shifts the hash. The MD5 output is also opaque: you cannot tell from the fingerprint alone whether it belongs to a browser or a scripting library. A browser that adds support for a new extension type changes its JA3 even though its underlying identity is the same.
JA4: A More Robust Replacement
JA4 was introduced by FoxIO (the JA4+ suite, led by John Althouse) in 2023 to address JA3's brittleness. The core design change is sorting: cipher suites and extensions are sorted by their numeric (hexadecimal) values before hashing, so reordering them does not change the fingerprint. GREASE values — reserved dummy code points, drawn from a fixed set defined in RFC 8701, that browsers send to keep middleboxes from ossifying against unrecognized fields — are stripped before hashing, so a Chrome release that refreshes its GREASE selection does not invalidate existing signatures.
A JA4 fingerprint has a human-readable prefix followed by two hashed components:
t13d1516h2 _ 8daaf6152771 _ e5627ecdbe6c
│ │ └── truncated SHA-256 of the sorted extensions
│ │ (GREASE and SNI removed) plus signature algorithms
│ └────────────────── truncated SHA-256 of the sorted cipher suites
└───────────────────────────────── readable prefix: t = TLS, 13 = TLS 1.3,
d = SNI present, 15 = cipher count,
16 = extension count, h2 = ALPN (HTTP/2)
The prefix alone tells you the TLS version, whether server name indication was present, the cipher-suite and extension counts, and the negotiated ALPN — making it possible to reason about a fingerprint without a reference database. The two hashed suffixes (cipher list, then extension list plus signature algorithms) then narrow to the specific implementation.
JA3 vs. JA4 at a Glance
| Property | JA3 | JA4 |
|---|---|---|
| Output format | MD5 hash (32 chars, opaque) | Human-readable prefix + two hashes |
| Order sensitivity | Yes — reordering changes the hash | No — values sorted before hashing |
| GREASE handling | Included, shifts hashes on update | Stripped before hashing |
| TLS version visible | No | Yes (in prefix) |
| Deployed since | 2017 | 2023 |
In practice, security tools and CDN edge networks are starting to log both side by side.
How Servers Use TLS Fingerprints
Bot and Automation Detection
The most common use is catching automation tools that claim to be browsers. Python's requests library, Go's net/http, curl, and Scrapy each have their own distinctive TLS handshake. When a request arrives with a User-Agent: Mozilla/5.0 (Chrome ...) header but a JA3 hash that matches Python's OpenSSL bindings, the mismatch is immediate evidence of spoofing.
This is the network-layer counterpart of the fingerprint-inconsistency check described in the bot detection techniques guide. The principle is identical: if two observable layers contradict each other, one of them is lying. TLS fingerprinting has one particular advantage over JavaScript-based checks — it runs on the raw TCP stream before the page loads, making it invisible to the bot.
VPN and Proxy Detection
An ordinary tunneling VPN (WireGuard, OpenVPN, IPSec) does not change your TLS fingerprint: your browser still makes its own TLS connection, and the VPN merely wraps that traffic at the IP layer, so the destination server sees your browser's real ClientHello. What TLS fingerprinting actually catches is interception. HTTPS inspection proxies — and VPN apps that terminate TLS on your behalf — use their own TLS libraries, producing handshakes that differ from any normal browser. A corporate HTTPS proxy, for example, re-encrypts traffic and presents a new ClientHello that typically matches an enterprise security product rather than a browser. Detection systems can flag this as evidence that the connection is being intercepted. See how websites detect VPNs and proxies for the full picture of what else gets examined alongside TLS fingerprints, including IP reputation, geo mismatches, and WebRTC leaks.
Security Monitoring
On the defensive side, TLS fingerprinting helps detect malware communicating with command-and-control servers. Most off-the-shelf malware and exploitation frameworks use a common library (Python, Go, .NET) that produces a recognizable JA3, even when the domain name or IP address changes per campaign. Network intrusion detection systems log JA3/JA4 hashes and alert when a known-bad signature appears on the wire.
Testing Your Own TLS Fingerprint
Several services expose your JA3/JA4 hash over a live HTTPS connection. The important thing to look for is whether your fingerprint matches the browser you claim to be in your User-Agent string. A mismatch suggests something in your network path — a corporate proxy, a security appliance, or an inspection tool — is modifying your handshake as it passes through.
BrowserInsight's bot detection tool surfaces related signals from inside the browser, including automation artifacts and headless-browser leaks. Network-layer fingerprints like JA3/JA4 require a server-side check, since they are computed from data your browser sends before any JavaScript runs.
Mitigation: Changing Your TLS Fingerprint
Unlike browser fingerprinting — which you can partially reduce by switching browsers or enabling privacy modes — changing a TLS fingerprint requires changing the TLS library itself, because the fingerprint is produced by the library before any user code runs.
Use the target browser directly. The simplest and most reliable approach. Chrome's fingerprint is the most widely expected signature across the web, and using an unmodified Chrome installation guarantees your TLS handshake matches what servers expect from your User-Agent.
Use a TLS-impersonation library. Tools such as curl-impersonate reorder cipher suites and extensions to exactly match a target browser's handshake. They are used primarily by scraping operators who need to pass TLS-layer checks.
Use a residential proxy that terminates with a real browser. Some proxy services forward requests through a pipeline backed by an actual browser engine, preserving the browser's TLS fingerprint end-to-end.
For most legitimate users, TLS fingerprinting is invisible and requires no action. It becomes relevant when you operate automation that must pass for a real browser, or when you want to understand why a network appliance or CDN is flagging your traffic.
Frequently Asked Questions
Does TLS 1.3 make fingerprinting harder?
Somewhat. TLS 1.3 encrypts more of the handshake than 1.2, including the server's certificate — but the ClientHello itself remains plaintext by necessity, because the server must read it before shared keys exist. The ClientHello still contains enough variation to fingerprint clients distinctively, and JA4 was specifically designed with TLS 1.3 in mind. One emerging extension does change the picture: Encrypted Client Hello (ECH) encrypts the sensitive inner ClientHello using a key the server publishes in DNS. Where ECH is deployed (Chrome and Cloudflare have supported it since 2023–2024), a network observer sees only a minimal outer ClientHello, which limits what can be fingerprinted.
Is TLS fingerprinting legal?
Yes, in typical contexts. Servers have always been permitted to read the traffic addressed to them, and the ClientHello is the part of the exchange that must be plaintext by protocol design. Using that data to classify clients is no different from analyzing HTTP headers.
Can an HTTPS inspection proxy hide the original fingerprint?
No — it replaces it with a different fingerprint. A proxy that decrypts and re-encrypts HTTPS traffic presents its own ClientHello, which typically matches the proxy software's TLS library rather than the user's browser. Detection systems that check for expected fingerprints will see a proxy signature instead, which is itself often a flag.
What is GREASE and why does it affect JA3?
GREASE (Generate Random Extensions And Sustain Extensibility) is a mechanism defined in RFC 8701 where TLS implementations advertise reserved dummy values — drawn from a fixed set of GREASE code points — in their ClientHello. The goal is to ensure that servers and middleboxes do not reject connections just because they contain an unrecognized field — keeping the protocol extensible. Because JA3 includes these GREASE values verbatim, a Chrome update that changes its GREASE selection shifts the JA3 hash even though nothing functionally changed. JA4 solves this by stripping GREASE values before hashing.


