JA4 and JA4T Fingerprints, Field by Field

A JA4 fingerprint is a string computed from a client's TLS ClientHello: the transport, the TLS version, whether SNI was present, how many ciphers and extensions were offered, the first ALPN value, then two truncated SHA-256 hashes over the sorted cipher list and the sorted extension list. JA4T is a separate fingerprint computed one layer down, from the fields of the TCP SYN packet.

Both belong to JA4+, created by John Althouse at FoxIO and launched in September 2023. The two strings look interchangeable in a log. They are not. They describe different layers, they take different collection, and only one of them has a written specification.

What is a JA4 fingerprint?

JA4 records the choices a client's TLS library made when it opened the connection. The ClientHello is cleartext and arrives before any encrypted data, so whatever terminates or observes the handshake can read the fields it needs.

The output is three sections joined by underscores, called a, b and c. FoxIO's specification works one example end to end:

t13d1516h2_8daaf6152771_e5627efa2ab1

Section a is readable on sight. Sections b and c are hashes.

What does each field in a JA4 fingerprint mean?

Section a packs six fields into ten characters with no separators.

FieldValue in the exampleMeaning
TransporttTLS over TCP. q is QUIC, d is DTLS.
TLS version13TLS 1.3.
SNIdSNI was present, so the destination is a domain. i means no SNI, so the destination is an IP address.
Cipher count15Fifteen cipher suites offered.
Extension count16Sixteen extensions offered.
ALPNh2First and last ASCII alphanumeric characters of the first ALPN value. 00 if there is no ALPN extension, no values, or an empty first value.

Three details in that table are where implementations disagree with each other.

The TLS version is not the handshake version at the top of the packet, which is ignored. It comes from the highest value in the supported_versions extension (0x002b) when that is present, and from the legacy Protocol Version field when it is not. The codes run 13, 12, 11 and 10 for TLS 1.3 down to TLS 1.0, s3 and s2 for SSL 3.0 and SSL 2.0, d1, d2 and d3 for DTLS 1.0, 1.2 and 1.3, and 00 for anything unrecognized.

The counts exclude GREASE but include values that are not really ciphers: SCSV (0x00FF, 0x5600) and the experimental and reserved range 0xFE00 to 0xFEFF all count. GREASE is ignored everywhere it appears, in every field: "The program needs to ignore GREASE values anywhere it sees them."

And the extension count is the count on the wire, not the number of extensions that reach the hash. Those are different numbers, as section c shows.

Section b, the cipher hash

The cipher list, sorted into hex order, comma delimited, lowercase, hashed with SHA-256, truncated to the first 12 characters. In the worked example the offered ciphers sort to:

002f,0035,009c,009d,1301,1302,1303,c013,c014,c02b,c02c,c02f,c030,cca8,cca9

That hashes to 8daaf6152771. An empty cipher list is not hashed at all, it is written as the literal 000000000000, and all SHA-256 output in JA4 is lowercase.

Section c, the extension and signature-algorithm hash

The extension list sorted by hex value, with two removed: SNI (0x0000) and ALPN (0x0010). Both are dropped because section a already carries them, which is what keeps section c stable for one application across different destinations and different negotiated ALPNs. The signature algorithm list is then appended after an underscore in wire order, unsorted, because order carries information there.

Sixteen extensions were counted in section a. Fourteen reach the hash input:

0005,000a,000b,000d,0012,0015,0017,001b,0023,002b,002d,0033,4469,ff01_0403,0804,0401,0503,0805,0501,0806,0601

That hashes to e5627efa2ab1. An empty extension list also yields 000000000000.

To debug a mismatch, use the raw output modes. -r emits the sorted lists unhashed, and -o emits them in their original order with GREASE excluded, renaming the field to ja4_o.

Why does JA4 exist when JA3 already did?

Because JA3 depended on ordering, and ordering is free to change. Attackers found that first. Then a browser did it too.

JA3 was created by John B. Althouse, Jeff Atkinson and Josh Atkins and open sourced by Salesforce in 2017, and its input included the cipher list in the order sent. Akamai's research team named the evasion that followed "cipher stunting" and traced its roots to early 2018: present a randomized cipher list in the ClientHello and the hash at the end randomizes with it. In August 2018, before Akamai saw any tampering, it observed 18,652 distinct fingerprints globally. Randomization appeared that September. By the end of October the tampering count had reached 255 million, and by the end of February 2019 the figure Akamai published was 1,355,334,179. It attributed the campaign, with what it called a high degree of certainty, to a Java-based tool.

Then Chrome did the same thing to extension order, for the opposite reason. Permuting extensions is an anti-ossification measure, keeping servers and middleboxes from depending on a fixed order, and the TLS 1.3 RFC permits any extension order except pre_shared_key, which must be sent last. The change was scheduled for Chrome 110 and shipped enabled by default in 108 and 109. Fastly watched the previously dominant Chrome JA3 value cd08e31494f9531f560d64c695473da9 fall off its network from January 20, 2023, put the number of possible permutations at nearly 15 factorial (about 10^12), and concluded that "we can expect each connection made with the TLS ClientHello extension permutation feature to practically have a unique JA3 fingerprint."

JA4 answers both by sorting before hashing. FoxIO on the ciphers: "in our research we've found that applications and libraries choose a unique cipher list more than unique ordering. This also reduces the effectiveness of 'cipher stunting'". Extensions are sorted for the same reason, in direct response to the Chromium change. What a client offers stays in the fingerprint. The order it offered in does not.

JA3 vs JA4

JA3JA4
Output shapeOne MD5 digest, 32 characters, no readable fields: cd08e31494f9531f560d64c695473da9Three underscore-separated sections, a readable prefix plus two 12-character truncated SHA-256 hashes: t13d1516h2_8daaf6152771_e5627efa2ab1
Cipher orderPart of the hashed input, so randomizing it changes the fingerprint. This is what cipher stunting exploited.Sorted by hex value before hashing, so order is irrelevant
Extension orderPart of the hashed input, so Chrome's per-connection permutation changes it every connectionSorted before hashing. SNI and ALPN are excluded from the hash and represented in the readable prefix instead
Transport visible in the stringNo field for itFirst character: t for TLS over TCP, q for QUIC, d for DTLS
OriginCreated at Salesforce, open sourced 2017. Repository archived 2025-05-01.Created by John Althouse at FoxIO. JA4+ launched September 2023.
LicenceBSD 3-ClauseJA4 itself BSD 3-Clause. Every other JA4+ method is under the FoxIO License 1.1.

What does a JA4 fingerprint prove?

That a particular TLS stack, configured a particular way, opened the connection. Not which application by name, and not which device.

F5's technical write-up states the limit: "It is important to understand that a JA4 TLS fingerprint, or any TLS fingerprint for that matter, is NOT a fingerprint of an individual instance of a device or browser. Rather, it is a fingerprint of a TLS 'stack' or application. For example, all Chrome browsers of the same version and the same operating system will generate the same JA4 fingerprint." Its practical conclusion: do not act on a JA4 alone unless you are certain that all, or nearly all, requests carrying it are malicious.

The scale numbers agree. Cloudflare reports analyzing more than 15 million unique JA4 fingerprints daily, generated by over 500 million user agents and billions of IP addresses. That many-to-one behaviour is deliberate, since excluding SNI and ALPN and sorting the extensions is exactly what keeps one library's fingerprint stable across destinations.

The values also expire. Althouse is blunt about it: "JA4 fingerprints will change as application TLS libraries are updated, about once a year. Do not assume fingerprints will remain constant in an environment where applications are updated."

Two things a JA4 does not give you. There is no published collision rate or false-positive rate for JA4 as a detection signal. The available evidence is directional, F5's qualitative statement and Cloudflare's population figures, so a specific false-positive number quoted for JA4 anywhere is unsourced. And because the value comes only from handshake bytes, it says nothing about the network path: a client rotating through a residential proxy pool presents the same JA4 from every exit address, and that JA4 tells you nothing about those addresses. The IP-layer question takes a different class of data, the kind a VPN and proxy checker returns for a single address.

What is a JA4T fingerprint?

JA4T fingerprints the client's TCP SYN, using four fields joined by underscores: window size, the TCP option Kind list, maximum segment size, and window scale.

64240_2-1-3-1-1-4_1460_8

There is no JA4T_ prefix inside the string, and the option list is not a run of zero-padded pairs. Documentation writes it as JA4T=64240_2-1-3-1-1-4_1460_8, where the JA4T= part is a label naming which fingerprint type follows. The stored value is the bare string. FoxIO's own mapping file holds exactly 64240_2-1-3-1-1-4_1460_8 in its ja4t column, hyphen separated, in unpadded decimal.

FieldValueWhat it is
Window size64240The receive window, the maximum data the client will accept before it needs an acknowledgment. Two bytes, and a required TCP header field.
Options2-1-3-1-1-4The TCP option Kind numbers, in decimal, in the order they appeared on the wire. Options are not required by TCP, but every modern operating system sends them.
MSS1460Maximum segment size, the largest payload the source will accept per packet.
Window scale8A multiplier on the window size. A window of 64240 with a scale of 8 is an effective window of 64240 x 2^8, or 16,445,440.

Two of those fields are also TCP options, so they appear twice in the fingerprint: once as a Kind number inside the list, once as their own value. The list is also where the padding lives. A TCP options list must be divisible by four in total length, which is why the NOP option exists and why repeated entries turn up in real option lists.

MSS is the one field that reflects the path rather than the host. FoxIO gives 1460 as the most common initial value, derived from an Ethernet MTU of 1500, and reads anything lower as overhead. On the GreyNoise fingerprint 29200_2-4-8-1-3_1424_7: "an options list of 2-4-8-1-3 indicates a Unix-based operating system and an MSS of 1424 indicates that these connections have 36 bytes of additional network overhead."

What do the TCP option Kind numbers mean?

Each number in the options field is a Kind number from IANA's TCP option registry, the canonical assignment list for that header field. Six Kinds cover every option list FoxIO publishes.

KindRegistered nameDefining RFCOption length
0End of Option ListRFC 92931 byte, the kind field alone
1No-OperationRFC 92931 byte, the kind field alone
2Maximum Segment SizeRFC 92934 bytes
3Window ScaleRFC 73233 bytes
4SACK PermittedRFC 20182 bytes
8TimestampsRFC 732310 bytes

The first two lengths are the exception rather than a rounding. The registry's own note: "Options 0 and 1 are exactly one octet which is their kind field. All other options have their one octet kind field, followed by a one octet length field, followed by length-2 octets of option data."

Kinds 0, 1 and 2 are what RFC 9293 calls the mandatory option set. RFC 9293, published in 2022, is the current TCP specification and the reference IANA cites for those three, having obsoleted RFC 793 for the base protocol. Window Scale and Timestamps are defined in RFC 7323, and SACK Permitted in RFC 2018.

With the legend, the common shapes decode on sight. 2-1-3-1-1-4, the Windows value above, reads Maximum Segment Size, No-Operation, Window Scale, No-Operation, No-Operation, SACK Permitted. 2-4-8-1-3, the GreyNoise fingerprint and the shape in three of FoxIO's five sample rows, reads Maximum Segment Size, SACK Permitted, Timestamps, No-Operation, Window Scale. A list ending in 0 or 0-0 ends in End of Option List bytes.

The lengths explain the No-Operation entries. The Windows list runs 4 plus 1 plus 3 plus 1 plus 1 plus 2 bytes, which is 12. The Unix list runs 4 plus 2 plus 10 plus 1 plus 3, which is 20. Both divide by four, and they divide because the single-byte No-Operation options are there to make them. Drop one and the total no longer divides.

That is also why the field is worth fingerprinting. TCP options are, in FoxIO's words, "not required but are used by every modern operating system", so the presence of any single Kind separates very little on its own. What separates is the combination and the sequence, and the requesting application chooses neither. The kernel's network stack builds the SYN below the socket API that userland code talks to, so the option order is a property of the host stack rather than of the request. FoxIO's framing: "Each operating system has different combinations of window size, options, and window scale."

Kind numbers above 8 do appear in real SYNs. The registry assigns Kind 34 to TCP Fast Open Cookie, defined in RFC 7413, and Kind 30 to Multipath TCP, defined in RFC 8684, both with variable length. A two-digit entry in an options list is not automatically a parsing error.

Can a JA4T identify the operating system?

Partly, and less precisely than most write-ups imply. No authoritative comprehensive table exists. What FoxIO publishes is a file it explicitly calls a sample for quick reference, with five client-OS rows.

OS label in FoxIO's sample fileja4t value
Windows 1064240_2-1-3-1-1-4_1460_8
WSL Ubuntu 22.0464240_2-4-8-1-3_1460_7
AWS Linux 262727_2-4-8-1-3_8961_7
Ubuntu 22.0465535_2-4-8-1-3_1460_8
Mac OSX/iPhone65535_2-1-3-1-1-8-4-0-0_1460_6

Read those rows before building a rule on window size. 64240 covers both Windows 10 and WSL Ubuntu in FoxIO's own sample. 65535 covers both Ubuntu and Mac OSX/iPhone. On this data the window size alone separates almost nothing.

The option list carries more. FoxIO states two specific rules: "Microsoft Windows does not utilize TCP Option 8 (timestamp), whereas all Unix-based operating systems do. iOS ends with a TCP Option 0 (End of list) whereas other operating systems do not." The sample rows are consistent with both, the Windows row carrying no 8 and the Mac and iPhone row ending in 0.

The labels are coarse too. The same string, 64240_2-1-3-1-1-4_1460_8, is labeled Windows 11 in FoxIO's README and Windows 10 in its sample CSV. For anything fuller, FoxIO points at ja4db, which it describes as under very active development.

And the values drift with OS releases rather than sitting still per vendor. p0f, which FoxIO credits as one of JA4T's inspirations alongside Hershel+ and gait, records a Windows 7 or 8 initial window of 8192 in its own signature format. Different format, different era, and not the 64240 FoxIO now publishes for Windows 10. Treat any OS-to-JA4T mapping, the five rows above included, as a snapshot of specific OS versions at a specific time.

Why do two tools produce different JA4T values for the same SYN?

Because JA4T has no text specification. FoxIO has never published a technical_details/JA4T.md, and an issue opened on the specification repository on 2026-07-07, still open, documents the consequences with file and line citations into the three reference implementations: "The technical specification for JA4T is severely lacking in the repo."

The divergences it records are exactly the ones that break a cross-tool comparison.

The general form of the second bullet is that there is no universal padding rule to cite. Before comparing two JA4T strings, establish which implementation produced each one.

How are JA4 and JA4T collected?

JA4 needs the TLS ClientHello, which is cleartext, so anything terminating or observing the handshake can compute it: CDNs, load balancers, WAFs, reverse proxies, identity providers. JA4T needs raw TCP SYN header fields, which an ordinary userland HTTPS server never receives.

The maintainers of FoxIO's own nginx module put the constraint directly: "The nginx module can compute L7 fingerprints (JA4 / JA4H / etc.) in user space, but TCP-layer fingerprints and latency metrics need data nginx can't access: SYN/SYN-ACK TCP options, observed TTL/Hop Limit, and handshake packet timestamps." Their proposed answer is an eBPF collector feeding SYN metadata back up to nginx.

So JA4T comes from one of four places: Zeek with FoxIO's official script, Wireshark or tshark with FoxIO's plugin, F5 iRules on BIG-IP, or a third-party eBPF or nftables collector written specifically because userland servers cannot see SYN options. Cloudflare's entry in FoxIO's tool-support table credits it with JA4 only, which is consistent with an HTTPS-terminating edge not exposing raw SYN capture.

The gap shows up in the data. In FoxIO's public JA4+ database, records routinely carry a populated ja4_fingerprint and a null ja4t_fingerprint. One sample record labels the application SemrushBot with ja4_fingerprint t13d301000_01455d0db58d_5ac7197df9d2 and ja4t_fingerprint: null.

Neither fingerprint needs JavaScript or cookies. Both are derived from connection setup packets, the SYN and the ClientHello, which arrive before any HTTP request is parsed.

Can JA4 and JA4T be spoofed?

The TLS layer, yes, with off-the-shelf tooling. The TCP layer is harder, and the honest reason is that the widely used TLS impersonation tools do not attempt it.

uTLS is a fork of Go's crypto/tls built for exactly this. Its own scope statement: "Handshake is still performed by 'crypto/tls', this library merely changes ClientHello part of it and provides low-level access." Its documentation recommends using multiple fingerprints including randomized ones rather than relying on a single one, and concedes that "there's a small chance that generated fingerprint won't work, so you may want to keep generating until a working one is found."

curl-impersonate is a modified curl build that, in its own description, "can impersonate the four major browsers: Chrome, Edge, Safari and Firefox" with "TLS and HTTP handshakes that are identical to that of a real browser." Its README is also candid about the limits of that claim, noting that the original Safari fingerprints in the upstream fork are not correct, tracked as a known bug.

Both tools scope the claim to the TLS and HTTP layers. Neither claims to alter window size, option order, MSS or window scale, which the host operating system's kernel produces below the socket API these userland tools sit on. So a client can present a perfectly Chrome-shaped JA4 while its JA4T still reflects the machine it is actually running on. FoxIO treats that gap as the point of layering the methods: "it is the combination of JA4+ fingerprints that can facilitate creating detection and blocking rules with no false positives."

Two caveats belong on that, in both directions. No controlled published study measures how exactly uTLS or curl-impersonate reproduce a target's JA4 hash, so the scope above comes from the projects' own documentation rather than independent testing. And the absence of a TCP-layer claim from these tools is not proof that TCP characteristics cannot be shaped. It establishes that the common tools do not shape them.

What else is in the JA4+ suite?

Is JA4 open source?

JA4 is. Most of JA4+ is not, and the split decides what you are allowed to build.

JA4 TLS Client Fingerprinting is released under BSD 3-Clause, deliberately matching JA3's licence so that JA3 adopters can move across without a licensing conversation, and FoxIO states it holds no patent claims on it and is not planning to pursue any. Everything else, JA4S, JA4L, JA4LS, JA4H, JA4X, JA4SSH, JA4T, JA4TS, JA4TScan and future additions, is licensed under the FoxIO License 1.1, which FoxIO describes as permissive for most use cases including academic and internal business purposes but "not permissive for monetization." All JA4+ methods are patent pending. Cloudflare's own summary of the split matches, and the third-party eBPF JA4T collector flags the same limitation in its README before any code. If JA4T is going into something you sell, that is a licensing question before it is an engineering one.

Frequently Asked Questions

JA3 hashes its input with MD5, producing one 32-character digest with no readable structure. JA4 produces three underscore-separated sections: a readable prefix carrying transport, TLS version, SNI presence, cipher and extension counts and ALPN, followed by two 12-character truncated SHA-256 hashes. The substantive change is that JA4 sorts ciphers and extensions by hex value before hashing, so randomized ordering no longer changes the value, which is what defeated JA3 when attackers randomized cipher order and when Chrome began permuting extension order.

Trueguard Basic is free.

Start identifying visitors and signals right away, for free

Sign up for free

No credit card required.

trueguard-logo© 2026 Trueguardinfo@trueguard.io