JA4+ in depth: JA4, JA4S, JA4H, JA4L, JA4X and what each captures
Most people who say “JA4” mean exactly one thing: the TLS client fingerprint, the modern replacement for JA3 that hashes a ClientHello into a string like t13d1516h2_8daaf6152771_b0da82dd1658. That single fingerprint is what cloud WAFs expose, what bot vendors score, what gets quoted in threat reports. But JA4 on its own is the smallest piece of what FoxIO actually shipped. The plus in JA4+ is a family of fingerprints that span the same connection from the TCP handshake up through the HTTP request, plus the server side, plus the certificate, plus a latency measurement that estimates how far away the other end physically sits.
The interesting design choice is that they all share one grammar. Every JA4+ fingerprint is laid out as a_b_c sections, human-readable up front and hashed where the data is high-cardinality, so an analyst can match on just the front of the string or just one hash without re-running anything. This post walks the whole suite member by member, says what each one reads off the wire, and shows where the parts join up on a single session. If you want the JA3-to-JA4 transition itself, the cipher-sorting and extension-randomization story, that lives in a separate post; here the lane is the rest of the family.
What JA4+ is, and why it is a suite
JA4+ was released by John Althouse at FoxIO on 22 November 2023. Althouse also created JA3 (at Salesforce, 2017) and JARM, so the suite is a deliberate redo of the same idea with six years of hindsight about where JA3 fell down. The headline problems with JA3 were two evasions that both attacked its hash: cipher stunting, where a client randomizes its cipher order so every connection produces a different MD5, and Chrome’s 2023 decision to randomize TLS extension ordering, which broke any database keyed on the old fingerprint. JA4 answers both by sorting the ciphers and extensions before hashing, so ordering no longer perturbs the output, and by splitting the fingerprint into labelled sections instead of one opaque digest.
That second decision is what turns JA4 from a single method into a suite. The README puts it plainly: all JA4+ fingerprints use an a_b_c format, and the split exists so you can hunt on ab or ac or c alone. The canonical example comes from GreyNoise. They had an actor scanning the internet while rotating a single TLS cipher on every connection, which under JA3 spawned a flood of unrelated MD5 hashes. Under JA4 only the middle section, the cipher hash, changes; the a and c sections hold still, so GreyNoise tracks the actor on the JA4_ac join and ignores b entirely. Locality is the whole point. Similar inputs produce strings that are similar in the places that matter, instead of an avalanche of unrelated hashes.
The suite as of 2026 covers TLS client (JA4), TLS server (JA4S), HTTP client (JA4H), latency and distance (JA4L and its server twin JA4LS), X.509 certificates (JA4X), SSH sessions (JA4SSH), TCP client and server (JA4T and JA4TS), an active TCP scanner variant (JA4TScan), and DHCP (JA4D, JA4D6). Each reads a different layer or direction of the same conversation.
*Where each JA4+ method sits on one connection: TCP fingerprints from the SYN exchange, JA4 from the ClientHello, JA4S and JA4X from the server's reply, JA4H once the HTTP request is in the clear, and JA4L from the timing across it all.*A note on licensing, because it shapes who runs what. JA4 (the TLS client method) is BSD 3-Clause, the same permissive license as JA3, and FoxIO states it is not pursuing patent coverage for it, so anything on JA3 can swap to JA4 freely. Every other member, JA4S, JA4H, JA4L, JA4X, JA4SSH, JA4T and the rest, is under the FoxIO License 1.1: free for academic and internal use, but commercial productization needs an OEM license. All of them are marked patent pending. That asymmetry is why you see JA4-only support at a lot of vendors (Cloudflare, Vercel, ngrok, AWS CloudFront and WAF, Akamai, Google Cloud Armor) while the full suite shows up mostly in security tooling like Wireshark, Zeek, Arkime, Suricata, GreyNoise and Censys.
JA4: the TLS client fingerprint
JA4 is the anchor, and it is worth restating its shape precisely because the other members borrow its conventions. The string has three sections. The example FoxIO ships is t13d1516h2_8daaf6152771_e5627efa2ab1, and it decodes field by field.
The a section, t13d1516h2, is the human-readable summary of the ClientHello. The first character is the transport: t for TLS over TCP, q for QUIC, d for DTLS. The next two characters are the TLS version, read from the supported_versions extension (0x002b) when it is present and falling back to the legacy version field otherwise, so 13 means TLS 1.3, 12 means 1.2, down to s3 for SSL 3.0. Then one character for SNI: d if a Server Name Indication extension is present (a domain destination), i if it is absent (an IP destination). Then a two-digit count of cipher suites and a two-digit count of extensions, both with GREASE values excluded and capped at 99. The cipher count counts SCSV and experimental values but ignores GREASE; the extension count includes SNI and ALPN even though those two are excluded from the hash later. Finally two characters for ALPN, the first and last alphanumeric character of the first ALPN value, so HTTP/2 (h2) prints as h2 and an absent ALPN prints as 00.
The b section is a 12-character truncated SHA-256 of the cipher list, written as four-character lowercase hex values, comma-delimited, sorted in hex order, GREASE removed. Sorting is the deliberate fix for cipher stunting: a client that shuffles its cipher order on every connection now lands on the same b every time, because the sort discards the order before hashing.
The c section is a 12-character truncated SHA-256 of the extension list, again sorted by hex value with GREASE removed, but with two extensions deliberately dropped: SNI (0x0000) and ALPN (0x0010), because those vary with the destination and the negotiated protocol rather than with the client itself. After the sorted extension list comes an underscore and then the list of signature algorithms in their original, unsorted order. Keeping the signature algorithms ordered is what restores the uniqueness that sorting the extensions would otherwise wash out. The README is direct about the consequence: JA4 fingerprints change as TLS libraries update, roughly once a year, so do not treat a browser’s JA4 as a permanent constant.
Two refinements matter in practice. JA4 ships a raw variant (the -r and -o output options) that keeps the cipher and extension lists unhashed, so you can see exactly which values went in; this is what you want when building a database or debugging a near-miss. And empty hash fields render as 000000000000 rather than null, which is why malware fingerprints in the FoxIO examples sometimes carry a string of zeros in one section. If you want the byte-level account of what the ClientHello carries before any of this hashing happens, the ClientHello field reference covers the raw structure, and the GREASE post explains the values JA4 has to strip.
JA4S: the server’s half of the handshake
JA4 reads the client. JA4S reads the server’s response to that client, and the pairing is where a lot of the detection value comes from. FoxIO’s framing is that JA4 alone identifies the client’s underlying TLS library, while JA4 plus JA4S can identify the specific application or malware family, because the server’s choices narrow things down.
The string is shorter because a ServerHello carries less than a ClientHello. The example is t120400_c030_4e8089b08790. The a section, t120400, is transport (t TCP, q QUIC), then the TLS version the server settled on (12), then a two-digit count of extensions in the ServerHello, then the chosen ALPN value (00 if none). The b section is the single cipher suite the server picked, written as its four-character hex, here c030 (which is TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384). The c section is a 12-character truncated SHA-256 of the server’s extensions in the order they appear. Note the asymmetry with JA4: the server’s extensions are hashed in their observed order, not sorted, because the server is not the thing performing extension randomization, so its ordering is itself signal.
JA4S only works on a full ServerHello, which means it does not exist for a connection the server refused or one that completed via session resumption without a fresh negotiation. When it does fire, the JA4_JA4S pair is a strong join. The IcedID example in the FoxIO database pairs a client JA4=t13d201100_2b729b4bf6f3_9e7b989ebec8 with a server JA4S=t120300_c030_5e2616a54c73, and the Sliver C2 example pairs JA4=t13d190900_... with JA4S=t130200_1301_a56c5b993250. The server side is what lets you say “this is that malware family” rather than just “this is some Go TLS stack.”
JA4H: the HTTP request
Once TLS is terminated, by a WAF, a load balancer, or anywhere the plaintext request is visible, JA4H fingerprints the HTTP request itself. It is computed per request, and it has four sections rather than three. The example is ge20cr13enus_974ebe531c03_b66fa821d02c_e97928733c74.
The a section, ge20cr13enus, packs the request line and a few flags. The first two characters are the HTTP method (ge for GET, po for POST, pu for PUT, and so on). The next two are the HTTP version (20 for HTTP/2, 11 for HTTP/1.1). Then one character for cookies: c if a Cookie header is present, n if not. Then one character for referer: r if a Referer header is present, n if not. Then a two-digit count of headers that explicitly excludes Cookie and Referer. Then the first four characters of the primary Accept-Language value (enus for en-US), or 0000 if no Accept-Language is sent.
The three hashes carry the rest. The b section is a truncated SHA-256 of the header names in the order they appear, which is itself a strong tell because real browsers emit headers in a stable, version-specific order that hand-built HTTP clients rarely reproduce. The c section is a truncated SHA-256 of the cookie field names, sorted. The d section is a truncated SHA-256 of the cookie names paired with their values, sorted. Splitting cookie names from cookie name-value pairs is the part that makes JA4H genuinely privacy-aware: an operator can match on JA4H_c to group clients by which cookies they carry without ever hashing the values, and reach for JA4H_d only when they need to distinguish individual sessions.
The header-order hash is the reason JA4H is good at catching bots. FoxIO’s note is that a missing Accept-Language is a classic bot tell, and the malware database backs it up: the IcedID dropper carries JA4H=ge11cn020000_9ed1ff1f7b03_cd8dafe26982, where the cn shows a cookie present and no referer, and the 0000 at the end shows no Accept-Language at all. Cobalt Strike’s JA4H=ge11cn060000_4e59edc1297a_4da5efaf0cbd has the same 0000 language field. DarkGate and LummaC2 examples print as po10nn... and po11nn..., POST requests with no cookie, no referer, no language, and only the a_b sections because they send no cookies for the c and d hashes to chew on. Where HTTP/2 frame ordering and pseudo-header layout matter on top of JA4H, the HTTP/2 fingerprinting post covers that adjacent surface.
JA4L: latency as a proxy for distance
JA4L is the odd one out, and the most clever. It does not fingerprint the contents of any packet. It measures timing, and from timing it estimates physical distance. The string is three numbers, for example 5191_42_45014. The first is the one-way TCP latency in microseconds, derived from the time between the SYN-ACK and the ACK. The second is the observed TTL on the inbound packet. The third is a one-way application-handshake latency, in microseconds, measured later in the connection.
The distance estimate falls out of a simple speed-of-light calculation. FoxIO gives the formula as D = j * c / p, where j is the one-way latency, c is the speed of light in fibre (about 0.128 miles per microsecond, or 0.206 km per microsecond), and p is a propagation delay factor between roughly 1.5 and 2.0 that accounts for routing not being a straight line. Plug a latency in and you get a rough radius. With several vantage points you can triangulate toward a city. The TTL is a second, coarser signal: an initial TTL of 64 points at Linux, macOS, phones and IoT, 128 at Windows, 255 at Cisco and F5 networking gear, and the difference between the initial TTL and the observed TTL gives an estimated hop count.
The reason JA4L matters for the rest of the suite is composition, not standalone use. A latency number on its own is noisy. But pair JA4L with JA4 and JA4H and you get a session-continuity check: if a session cookie suddenly shows up from a fingerprint that reports a different operating system, a different application, and a physical location hundreds of miles away from where it was a minute ago, that is a session-hijacking signature that none of the three signals would flag on its own. JA4LS is the same measurement taken from the server-to-client direction, so you can sanity-check the estimate from both ends.
*JA4L converts a one-way latency into a distance radius. The estimate is coarse on its own; its value is as a consistency check against the client and HTTP fingerprints on the same session.*The honest caveat is that JA4L is an estimate built on assumptions that often do not hold. VPNs, CDNs, anycast, and middleboxes all distort latency and TTL. FoxIO’s own follow-up work measures VPN exit nodes precisely because the VPN sits between you and the real client. So read JA4L as a weak prior that gets strong only in combination, never as a geolocation oracle.
JA4X: how a certificate was made, not what it says
JA4X fingerprints the X.509 certificate the server presents, but with a twist that makes it useful against adversaries who randomize their certs. It does not hash the certificate’s values. It hashes the structure: which Relative Distinguished Names appear in the issuer, which appear in the subject, and which extensions are present, each in the order they occur, with the actual field contents discarded.
The example is 96a6439c8f5c_96a6439c8f5c_aae71e8db6d7. The a section is a truncated SHA-256 of the issuer RDN object identifiers in order, the b section the same for the subject RDNs, and the c section the same for the certificate extensions. Because only the OIDs and their ordering go into the hash, two certificates with completely different common names, organizations and serial numbers produce the same JA4X if they were generated by the same code path. That is exactly the situation with malware that mints a fresh self-signed cert per host. The certificate values randomize; the way the library assembles the cert does not.
This is what makes JA4X effective against Sliver, the open-source C2 framework. Sliver generates randomized certificates, so matching on certificate contents is hopeless, but its certificate-generation routine leaves a stable JA4X. Driftnet used a JA4X feed to enumerate default Sliver C2 servers across the internet; the FoxIO examples list Sliver as JA4X=000000000000_4f24da86fad6_bf0f0589fc03 and JA4X=000000000000_7c32fa18c13e_bf0f0589fc03, where the all-zero a section means the issuer RDN set was empty (a self-signed cert with no issuer distinguished name to speak of). The same mechanism catches SoftEther VPN, whose distinctive certificate-generation signature, JA4X=d55f458d5a6c_d55f458d5a6c_0fc8c171b6ae, made it identifiable even when Microsoft reported the Flax Typhoon group using SoftEther to blend into ordinary HTTPS traffic. The matching a and b hashes there are the tell that issuer and subject were built identically, which is what a self-signed cert looks like.
FoxIO’s guidance is to combine JA4X with JA4, JARM, or the certificate’s issuer organization string to cut false positives, because a popular certificate library will give the same JA4X to many unrelated, legitimate servers. Structure alone is a coarse bucket; it is the join with the other fingerprints that sharpens it.
JA4T and JA4SSH: down to TCP and over to SSH
Two more members reach outside TLS entirely. JA4T fingerprints the TCP layer from the client’s SYN packet, before any TLS exists at all. The example for Windows 11 is JA4T=64240_2-1-3-1-1-4_1460_8. The first field is the TCP window size, the second the list of TCP options in the order they appear (here option kinds 2, 1, 3, 1, 1, 4), the third the maximum segment size, and the fourth the window scale multiplier. JA4TS is the same read off the server’s SYN-ACK, and JA4TScan is an active variant that adds a fifth field of retransmission timings probed by sending crafted packets.
What TCP leaks is mostly operating system and network path. Windows does not emit the TCP timestamp option (kind 8) while Unix-like systems do, so the options list alone separates the two families. The MSS is a path signal: a value of 1460 is a clean Ethernet path, while a reduced MSS like 1380 implies tunnelling overhead from a VPN or proxy. FoxIO’s VPN research leans on exactly this, since a VPN frequently rewrites the MSS and sometimes the window size to fit its own encapsulation. JA4T sits below TLS, so it survives even when the higher-layer fingerprints are spoofed by a tool that carefully forges its ClientHello but runs on an unmodified OS TCP stack. That mismatch, a browser-perfect JA4 sitting on top of a TCP fingerprint that says “this is a Linux server, not a Windows desktop,” is one of the second-order tells that catch impersonation tooling, and it is the same class of inconsistency the curl-impersonate and uTLS detection post digs into.
*JA4T reads four fields off a single SYN packet; JA4SSH summarises the shape of an encrypted SSH session over a window of packets. Neither touches TLS.*JA4SSH goes the other direction, into a protocol that is already encrypted. It cannot read SSH payloads, so it fingerprints the shape of the traffic instead, recomputed every 200 SSH packets by default. The example is c36s36_c55s75_c70s0. The two values in the first section are the statistical mode of client packet lengths and server packet lengths. The second section counts SSH packets sent by client and server. The third counts bare ACKs each way. These numbers describe behaviour, not bytes, and the behaviour is surprisingly diagnostic. An interactive shell, where someone is typing, pads to 36 bytes (the minimum length over ChaCha20-Poly1305) with all the bare ACKs coming from the client side that is doing the typing. A reverse SSH shell flips that: it double-pads to 76 bytes and the ACKs come from the server. A WinSCP file transfer to the client shows a server max window of 1460 with ACKs from the client. So JA4SSH=c76s76_c71s59_c0s70, with its 76-byte padding and server-side ACKs, is the signature of a reverse shell, which is why the FoxIO examples list exactly that string under “Reverse SSH Shell.” You catch a reverse shell without ever decrypting a byte, purely from the rhythm of who is typing.
How the parts compose
The reason to treat JA4+ as a suite rather than a pile of separate tools is that the a_b_c grammar makes the pieces joinable, and the joins are where weak signals turn into strong ones. Three patterns recur.
The first is partial matching within one fingerprint. Because each method splits into labelled sections, you can drop the volatile parts and match on the stable ones. The GreyNoise cipher-stunting case is the canonical example: match on JA4_ac, ignore the churning b, and a thousand JA3 hashes collapse to one actor. The same trick works inside JA4H. Match JA4H_bc to group requests by header structure and cookie names while ignoring the volatile cookie values in d, or match JA4H_d alone when you specifically want to follow one session. The locality is what makes this cheap: you are slicing a string, not recomputing a hash.
The second is cross-method joins on one connection. JA4 plus JA4S identifies a malware family where JA4 alone only identifies a TLS library. JA4 plus JA4T catches a forged ClientHello running on an unforged OS stack. JA4X plus the issuer organization cuts the false positives that JA4X alone would generate. Each join adds a dimension that the adversary has to get right simultaneously, and getting all of them right at once is much harder than spoofing any one.
The third is composition over time and space, which is where JA4L earns its place. A session is supposed to keep the same client fingerprint, the same HTTP fingerprint, and roughly the same physical distance for its whole life. When a cookie reappears attached to a different JA4, a different JA4H, and a JA4L that puts the client in a different city, the combination is a hijack signature even though no single fingerprint is alarming on its own. Cloudflare’s production use points the same direction without using the latency method: their JA4 Signals aggregate behaviour per fingerprint per hour, deriving features like a browser ratio and an HTTP/2-plus-HTTP/3 ratio across, by their figures, more than 15 million unique JA4 fingerprints seen against over 500 million user agents. A single JA4 says what software you claim to be. The signals around it say whether fingerprints like yours behave like that claim. The suite is the same idea applied across protocols instead of across time.
Closing thought
JA4+ is best understood as one grammar applied to every layer of a connection that leaks anything, with the deliberate constraint that the output stay readable and sliceable. JA4 reads the client’s TLS, JA4S the server’s, JA4H the HTTP request, JA4X the certificate’s construction, JA4T the bare TCP, JA4SSH the rhythm of an encrypted shell, and JA4L the time it takes light to get there and back. None of them is hard to evade alone. A determined client can forge a ClientHello, normalize its headers, and route through a proxy. What is hard is making all seven agree with each other and keep agreeing for the length of a session, because the fingerprints sit at layers the client controls independently and rarely reconciles.
That is the practical lesson for anyone building or detecting against this. The strength was never in any single hash; cipher stunting and Chrome’s extension randomization already proved a single hash is brittle. The strength is in the joins, and the joins are only possible because every member of the suite was built to the same a_b_c shape from day one. The licensing split tells you how the industry actually deploys it: JA4 the client fingerprint is everywhere because it is BSD-licensed, while the parts that make the joins powerful sit behind the FoxIO License, which is why the richest JA4+ work still happens in security tooling rather than in the CDN edge that most scraped traffic passes through.
Sources & further reading
- FoxIO (2025), FoxIO-LLC/ja4 repository README — the canonical spec, format rationale, licensing split, vendor-support table, and worked malware fingerprint examples.
- FoxIO (2023), JA4+ Technical Details — per-method field diagrams for JA4, JA4S, JA4H, JA4L, JA4X, JA4T and JA4SSH.
- FoxIO (2023), JA4.md technical specification — exact section encoding for the TLS client fingerprint, hashing and GREASE rules.
- John Althouse / FoxIO (2023), JA4+ Network Fingerprinting — the release blog covering JA4/S/H/L/X/SSH, the GreyNoise and SoftEther cases, and the JA4L distance formula.
- John Althouse / FoxIO (2024), JA4T: TCP Fingerprinting — JA4T field structure and how TCP options, MSS and window scale leak OS and tunnel overhead.
- APNIC Blog (2023), JA4 network fingerprinting — independent write-up confirming the 22 November 2023 release date and the JA3-to-JA4 lineage.
- Cloudflare (2024), Advancing threat intelligence: JA4 fingerprints and inter-request signals — production use of JA4 plus the aggregated JA4 Signals features and scale figures.
- John Althouse / Salesforce (2019), TLS fingerprinting with JA3 and JA3S — the 2017 predecessor whose limitations JA4+ was built to fix.
- Fastly (2023), The state of TLS fingerprinting — context on cipher stunting and the failure modes JA4 addresses.
- Zeek (2026), How to use JA4 network fingerprints in Zeek — a current example of JA4+ deployed in open-source network monitoring.
- Crawlex, TLS fingerprinting: from ClientHello bytes to JA4 — the companion piece on the JA3-to-JA4 transition and cipher/extension sorting.
Frequently asked questions
Why does the JA4+ suite use the a_b_c section format instead of one combined hash?
The labelled a_b_c layout keeps the readable summary up front and hashes only the high-cardinality lists, so an analyst can match on part of the string without recomputing anything. This gives locality: similar inputs produce strings that stay similar in the parts that matter. The canonical case is GreyNoise tracking an actor that rotated one cipher per connection by matching on JA4_ac and ignoring the churning b section, where JA3 would have spawned a flood of unrelated hashes.
How does JA4 stop cipher stunting and Chrome's TLS extension randomization from breaking the fingerprint?
JA4 sorts both the cipher list and the extension list by hex value before hashing, so a client that shuffles cipher order on every connection lands on the same b section every time, and Chrome randomizing extension order no longer perturbs the c section. The signature algorithms are deliberately kept in their original unsorted order to restore the uniqueness that sorting would otherwise wash out.
What does JA4S add over JA4 alone for identifying malware?
JA4 reads the client and on its own mostly identifies the client's underlying TLS library. JA4S reads the server's response, and its extensions are hashed in observed order rather than sorted because the server is not the thing randomizing them. Pairing the two as JA4_JA4S narrows a connection from some generic TLS stack to a specific application or malware family, as shown by the IcedID and Sliver C2 pairings in the FoxIO database. JA4S only fires on a full ServerHello.
How does JA4L turn network latency into an estimated physical distance, and how reliable is it?
JA4L measures one-way latency from the handshake and applies D = j * c / p, where c is the speed of light in fibre, around 0.128 miles per microsecond, and p is a propagation factor of roughly 1.5 to 2.0 for non-straight routing. The result is a coarse distance radius, and the string also carries the observed TTL as a hop-count signal. The estimate is unreliable alone because VPNs, CDNs, anycast and middleboxes distort latency and TTL, so it is best used as a consistency check against the other fingerprints on a session.
Why can JA4X identify malware like Sliver that randomizes its TLS certificates?
JA4X hashes the structure of an X.509 certificate rather than its values: the issuer relative distinguished name OIDs, the subject RDN OIDs, and the extensions, each in order, with field contents discarded. Two certificates with completely different common names and serial numbers produce the same JA4X if the same code generated them. Sliver mints a fresh randomized cert per host but its generation routine leaves a stable JA4X, which is how Driftnet enumerated default Sliver C2 servers.
Further reading
TLS extension ordering and the Chrome randomization that broke JA3
How the order of TLS extensions became a fingerprint, why Chrome started shuffling that order in early 2023 with a Fisher-Yates permutation in BoringSSL, and how JA4 answered by sorting the list.
·20 min readHow Cloudflare uses TLS and HTTP/2 fingerprints in bot scoring
A reference on Cloudflare's network-layer fingerprinting: how JA3, JA4, and the HTTP/2 frame profile are computed at the edge, what cf.bot_management exposes, and how those signals feed the 1-99 bot score.
·23 min readTLS fingerprinting: from ClientHello bytes to JA4
What a ClientHello actually contains, why JA3 worked for six years and then stopped, and what JA4 fixes, with a Python reference you can run against your own packet captures.
·15 min read