// Engineering Log

HTTP, HTTPS and TLS: Part 8 — What are JA3 and JA4: TLS client fingerprints

Published on 2026-10-12

// Fast route

This article belongs to the topic Security and protection.

JA3 and JA4 — methods to obtain a short TLS client fingerprint from its initial ClientHello message. Different programs — Chrome, Firefox, curl, the Python requests library, the Go client, a malicious program — form ClientHello differently: they offer different sets of ciphers, extensions, key-exchange groups in different orders. These fields form a string, the string is hashed, and an identifier is obtained that is the same for all copies of one program and different for different programs. The fingerprint is computed before traffic decryption and without the client’s involvement, so it is used for bot protection, searching for malware on the network, and in DPI systems.

The main difference from the User-Agent header: User-Agent is written by the client, and a script can easily pretend to be the Chrome browser. The TLS fingerprint is determined by the library that establishes the connection, and if a Python script calls itself Chrome but its ClientHello is from OpenSSL, that mismatch is visible immediately.

What goes into ClientHello

ClientHello is sent in plaintext (unless ECH is used) and contains:

  • the protocol version and the list of supported versions;
  • the list of ciphers in order of preference;
  • the list of extensions: SNI, ALPN, supported_groups, signature_algorithms, key_share, session_ticket and a dozen others;
  • key-exchange groups (elliptic curves): x25519, secp256r1, X25519MLKEM768;
  • signature algorithms;
  • ALPN protocols: h2, http/1.1.

All of this is set by the TLS library (BoringSSL in Chrome, NSS in Firefox, OpenSSL in curl and Python, crypto/tls in Go) and its settings in the application.

JA3

JA3 was proposed in 2017 by researchers at Salesforce (John Althouse, Jeff Atkinson, Josh Atkins — from the initials of their first names). A JA3 string consists of five ClientHello fields, written as decimal numbers separated by commas, and values inside a field separated by hyphens:

Version,Ciphers,Extensions,Groups,Point_Formats
771,4865-4866-4867-49195-49199-...,0-23-65281-10-11-35-16-5-13-18-51-45-43-27-21,29-23-24,0

The fingerprint is the MD5 of this string: 32 hexadecimal characters, for example 375c6162a492dfbf2795909110ce8424. GREASE values (special “placeholders” that browsers insert so servers don’t break on unknown values) are discarded when computing.

For the server response there is JA3S — based on ServerHello fields. The pair JA3 + JA3S distinguishes specific malware and their command-and-control servers well: the same program connecting to the same server software always yields the same pair.

Why JA3 stopped working for browsers. In JA3, extensions are listed in the order the client sent them. Since Chrome 110 (February 2023) the browser randomizes the order of extensions on each new connection — this was done so servers don’t rely on a fixed order and don’t hinder TLS evolution. As a result the same Chrome gives a new JA3 on almost every connection, and browser fingerprint databases lost their meaning. Also, MD5 of the whole string does not show how two fingerprints are similar: changing one cipher yields a completely different hash.

JA4

JA4 was proposed in 2023 by John Althouse, now at FoxIO. The fingerprint is human-readable, resilient to extension shuffling, and accounts for QUIC.

Example for Chrome:

t13d1516h2_8daaf6152771_e5627efa2ab1

Three parts separated by underscores.

Part a — description, readable without decryption:

CharactersMeaningExample
1transport: t — TLS over TCP, q — QUIC, d — DTLSt
2–3TLS version: the maximum from supported_versions13 — TLS 1.3
4d — SNI present (name-based), i — SNI absent (by IP)d
5–6number of ciphers without GREASE15
7–8number of extensions without GREASE16
9–10first and last letter of the first ALPN protocol, 00 — no ALPNh2

Part b — the first 12 characters of the SHA-256 of the list of ciphers, sorted by value. The order in which the client sent them does not matter.

Part c — the first 12 characters of the SHA-256 of the sorted list of extensions (excluding SNI and ALPN — they are already accounted for in part a) and the list of signature algorithms in the original order.

Sorting solves Chrome’s problem: shuffling extensions does not change the fingerprint. And the separate parts allow searching for partial matches: for example, all clients with the same set of ciphers (identical part b) but different extensions.

For debugging there are “raw” variants: JA4_r — the same lists without hashing, JA4_o — in the original order, without sorting.

JA4+ family. Besides JA4 for the TLS client, FoxIO described:

  • JA4S — the server response (ServerHello);
  • JA4H — HTTP client: method, version, presence of cookie and Referer, number of headers, their order, language;
  • JA4X — how the X.509 certificate was generated;
  • JA4L — latency and distance to the client;
  • JA4T, JA4TS — TCP parameters (window size, options);
  • JA4SSH — SSH sessions.

License. JA4 itself is distributed under the BSD 3-Clause license, like JA3. Other JA4+ methods are under the FoxIO License 1.1: they can be used free of charge for protecting your own company and for research, but a vendor selling a product that includes these fingerprints needs a commercial license.

Where fingerprints are used

Bot protection. Cloudflare, Akamai, AWS WAF and other protection systems compare the fingerprint with the declared User-Agent and with a database of known clients. A Python script with a browser User-Agent receives a captcha or a 403. Cloudflare allows using the fields cf.bot_management.ja3_hash and cf.bot_management.ja4 in WAF rules on plans with Bot Management.

HTTP/2 fingerprint. Besides TLS, clients are distinguished by HTTP/2 parameters: values in the SETTINGS frame, window size, priorities and the order of pseudoheaders. This fingerprint is called the “Akamai fingerprint” after the company that published the method.

Malware detection. Intrusion detection systems (Suricata, Zeek) compute JA3/JA4 for each connection, and an analyst searches for fingerprints of known malware and tools like Cobalt Strike without decrypting the traffic.

DPI and blocking. ClientHello fingerprint filtering hardware distinguishes a browser from a VPN client or proxy that uses TLS for obfuscation. Therefore modern circumvention tools (Xray with the REALITY protocol, sing-box) do not form ClientHello themselves, but copy a browser fingerprint using the uTLS library.

Inventory. In an enterprise network, fingerprints reveal which programs access the Internet: outdated TLS libraries, unexpected clients, custom scripts.

Spoofing the fingerprint

The fingerprint is determined by the TLS library, so you can only spoof it with a different library or its configuration:

  • uTLS — a Go library that forms ClientHello byte-for-byte like the chosen browser;
  • curl-impersonate — a build of curl with BoringSSL and settings that mimic Chrome, and with NSS — mimicking Firefox, including the HTTP/2 fingerprint and header order;
  • curl_cffi — a wrapper over curl-impersonate for Python.

Changing User-Agent, headers, or proxy does not change the fingerprint. And conversely: complete TLS parity with a browser while differing in HTTP/2, header order, or JavaScript behavior is also detected. Fingerprints are one of the indicators, not a final verdict: millions of identical browsers share one JA4, and you cannot distinguish an individual user by it.

How to find your fingerprint

Via a service. Open in a browser or request with curl https://tls.peet.ws/api/all — the service will return JSON with your client’s fingerprints: tls.ja3, tls.ja3_hash, tls.ja4, tls.ja4_r and the HTTP/2 fingerprint http2.akamai_fingerprint_hash.

bash
curl -s https://tls.peet.ws/api/all | python3 -c \
  'import json,sys; d=json.load(sys.stdin); print(d["tls"]["ja3_hash"], d["tls"]["ja4"], d["http_version"])'

What different clients produced. Measurement in September 2026, three requests from each client:

ClientJA3JA4Protocol
curl 8.7.1, macOS (LibreSSL)375c6162…t13d497h2_0d8feac7bc37_7395dae3b2f3h2
curl 8.22, Alpine (OpenSSL 3.5)446fb8bd…t13d3012h2_1d37bd780c83_882d495ac381h2
Python urllib, macOS (OpenSSL 3)4bdac5ab…t13d1713h1_ab0a1bf427ad_89ab6efea773HTTP/1.1
Python requests, Debian (OpenSSL 3)86dab210…t13d1713h1_ab0a1bf427ad_8537cf56674eHTTP/1.1
Node.js 26, fetch98384658…t13d5212h1_b262b3658495_8e6e362c5each2
Go 1.27, net/http03117a8e…t13d1312h2_f57a46bbacb6_f50d94e863ebh2
curl-impersonate, Chrome profile 116changes: 0ea31aef…, 97ca64e9…t13d1516h2_8daaf6152771_e5627efa2ab1h2

What the table shows:

  • each client has its own fingerprint, and User-Agent does not affect it. A Python script that calls itself Chrome will still show t13d1713h1_…;
  • part a is readable without a reference: Python offers 17 ciphers and 13 extensions and puts http/1.1 (h1) first in ALPN, Go — 13 ciphers and 12 extensions and h2; curl on macOS with LibreSSL offers 49 ciphers, including legacy ones;
  • identical part b for Python urllib and requests (ab0a1bf427ad) — the same OpenSSL cipher set; differences in part c — different extensions and signature algorithms in different builds;
  • JA3 for the Chrome profile changes on each request, while JA4 remains the same t13d1516h2_8daaf6152771_e5627efa2ab1, which is given in the JA4 specification as an example Chrome fingerprint. This is how extension shuffling looks;
  • the service does not compute everything perfectly: for curl on macOS, part a shows 497 instead of 4907 — the number of extensions was not zero-padded to two digits. When comparing fingerprints from different sources, check whether they compute them the same way.

Open the same page in your browser several times in a row — JA3 will change, JA4 will not.

In Wireshark. Since version 4.2, Wireshark computes fingerprints itself. In the dissected ClientHello there are fields tls.handshake.ja3, tls.handshake.ja3_full and tls.handshake.ja4; you can add them as a column and filter:

tls.handshake.ja4 == "t13d1516h2_8daaf6152771_e5627efa2ab1"

From the command line:

bash
tshark -r capture.pcap -Y 'tls.handshake.type == 1' \
  -T fields -e ip.src -e tls.handshake.extensions_server_name -e tls.handshake.ja4

On your own server. Nginx does not compute fingerprints; for that there are third-party modules and separate proxies (for example, fingerproxy — a reverse proxy in Go that passes JA3, JA4 and the HTTP/2 fingerprint to the application in headers). On sites behind Cloudflare, fingerprints are available in WAF rules and logs on plans with Bot Management.

When to use and when not

Fingerprints are useful:

  • in WAF rules against automated requests, when User-Agent claims a browser but the fingerprint indicates a library;
  • in incident investigations: searching for all connections of a given tool across the network;
  • to allow API access only to certain clients within a company (as an additional indicator, not the only one).

Do not rely on them:

  • as authentication — the fingerprint is spoofable;
  • for blocking by a blacklist of fingerprints without checking: a popular library’s one JA4 can cover millions of legitimate clients, and along with a bot you may block all users of a mobile app using that library;
  • for tracking a specific user — a fingerprint describes a program, not a person.

Common mistakes

  • Rules based on JA3 for browsers after 2023 — Chrome and Firefox fingerprints change on every connection.
  • Fingerprints are computed on the server behind a CDN or load balancer that terminates TLS — you see the load balancer’s fingerprint, not the client’s.
  • Blocking by fingerprint after your own users update their client library — new versions of OpenSSL and browsers change the set of extensions (for example, with the appearance of X25519MLKEM768).

// Similar task

If you are dealing with something similar

This article belongs to one of the main working topics. You can keep reading on the topic, go to the homepage to understand what I do, or open the service pages directly.

Article topic

Security and protection

SSL, hardening, access control, service protection, and secure configurations.

Typical tasks behind this topic

  • Set up SSL, certificates, and secure connections
  • Restrict access and close unnecessary entry points
  • Harden server and service configuration

// Next step

If you need help with this topic, not just another article, it is better to go straight to the service page. The homepage and topic collection stay available as secondary routes.

Open services

// Contact

Need help?

Get in touch with me and I'll help solve the problem

I reply within one business day (03:00-13:00 GMT)

Или оставьте заявку здесь:

Confirm that you are not a bot.

Write and get a quick reply