On this page
01 Why fingerprint at all
A user-agent string is a sentence the client writes about itself. A fingerprint is what the client does without thinking: the order it lists ciphers, the window it opens, the pause between clicks. Declared crawlers are handled by identity checks, covered in verifying AI crawlers by DNS and address range. Fingerprinting is for everything that does not declare itself: scrapers, credential stuffers, inventory bots and headless browsers that claim to be a desktop browser. The work sits in the monitoring hub because it starts as measurement, not blocking.
02 TLS ClientHello fingerprints
The ClientHello is sent in clear, even under TLS 1.3. It lists the TLS version, cipher suites, extensions, supported groups and signature algorithms, each in an order fixed by the client library. Hash those fields and you get a compact label for the library. The JA3 method hashes them in the order sent; the later JA4 approach sorts extensions and adds readable prefixes, because modern browsers now randomise extension order and would otherwise produce a new hash per connection. GREASE values (RFC 8701) must be dropped before hashing for the same reason.
The value is in mismatch. A request whose user-agent claims a current desktop browser but whose ClientHello matches a scripting language HTTP library is almost certainly automated. The limit is that anyone can load a browser TLS profile into a client library, and every browser version shifts the fingerprint slightly, so keep the reference table current.
03 HTTP/2 SETTINGS and header order
An HTTP/2 client opens with a SETTINGS frame: initial window size, header table size, maximum concurrent streams and a few others. Browsers use distinct values and send pseudo-headers (:method, :authority, :scheme, :path) in a fixed order that differs between engines. Regular headers follow a stable order too. A client library that sends a browser user-agent with its own default SETTINGS and alphabetical headers gives itself away. Reading any of this requires terminating TLS, so the collection point is the proxy or a Layer 7 balancer, a point made in what a Layer 7 device can read.
04 TCP/IP stack signals
The SYN is a passive operating system fingerprint. Stacks pick an initial TTL (commonly 64 or 128, sometimes 255), a starting window, an MSS and an option order. Observed TTL counts down by one per hop, so round up to the nearest common starting value. A browser user-agent claiming a desktop OS over a SYN that looks like a Linux server stack is a mismatch worth a point. The signal is weak alone: load balancers, NAT devices and proxies rewrite or originate the SYN you see.
src 198.51.100.23 ttl 52 -> initial 64, 12 hops win 64240 mss 1460 wscale 7 options: mss,sackOK,ts,nop,wscale claimed UA: desktop browser on a 128-TTL OS verdict: stack mismatch, +2
Collect SYNs from the balancer or from a mirrored copy of the uplink; sampling is fine because the stack does not change between connections from one client, and sampled sFlow headers include the TCP options of any SYN they happen to catch.
05 Timing, sessions and headless browsers
People are irregular. They load a page, its assets arrive in a burst within a second, then nothing happens for tens of seconds while they read. Scripts are regular or impossibly fast: evenly spaced requests, no asset loads, deep URLs in sequence, no cookies carried between requests. Plot inter-request intervals per client and the difference is usually visible without any statistics.
Headless browsers close most of these gaps. They run a real engine, so TLS, HTTP/2 and asset loading all look correct. What remains is behaviour: navigation that never scrolls, sessions that start at a deep URL with no referrer, identical timings across hundreds of addresses, and automation flags left set in the engine. Expect to catch the careless ones and to raise the cost for the rest.
06 False positives
- Corporate NAT and carrier-grade NAT. Thousands of people behind one address look like one very fast client. Never score on per-address rate alone.
- Forward proxies and security gateways. They terminate and re-originate TLS, so the fingerprint is the proxy, not the browser behind it.
- Accessibility tools. Screen readers and assistive browsers can skip assets, move through pages in a linear order and have unusual timing. Blocking them counts as a failure.
- Privacy browsers and extensions. Blocked scripts and trimmed headers produce odd but human sessions.
- Monitoring and uptime checks. Your own probes are bots. Allowlist them by address before scoring.
07 Combining signals into a score
Give each signal a weight, add them per client key and act on thresholds. A mismatch between claimed browser and TLS fingerprint might score 3; a SYN mismatch 2; no asset loads over ten pages 2; regular sub-second intervals 2. Illustrative bands: 0 to 3 pass, 4 to 6 challenge or shape, 7 and above block. Tune the weights against a labelled sample of your own traffic, review what lands in the middle band by hand, and feed the result to the balancer as a key for per-client rate limits rather than as an outright block.
Fingerprints are identifiers. Hash them, keep them for days rather than months, do not join them to personal accounts unless there is a stated reason, and document the processing in your privacy notice where the law requires it.
08 A worked bot traffic score
The weights in the previous section are only useful once applied to a real client, so here is one, with values chosen to be plausible rather than measured. The client key is the address plus the TLS fingerprint, the window is ten minutes, and the score is the sum of the weights whose conditions hold. Each row names the signal, the observation and the points it contributed.
| Signal | Observed for client 198.51.100.23 | Points |
|---|---|---|
| Claimed user-agent | Current desktop browser on a 128-TTL operating system | 0 (a claim, not a signal) |
| TLS ClientHello | Cipher and extension list matches a scripting-language HTTP library | +3 (mismatch with the claim) |
| HTTP/2 SETTINGS | Connection used HTTP/1.1 only; no SETTINGS observed | +1 (browsers negotiate HTTP/2) |
| TCP SYN | TTL 52, window 64240, options mss,sackOK,ts,nop,wscale | +2 (Linux server stack, not the claimed OS) |
| Asset loads | 0 of 31 HTML pages followed by a CSS, script or image request | +2 |
| Inter-request interval | Median 1.9 s, interquartile range 0.1 s | +2 (regular) |
| Session start | First request was a page 6 levels deep, no referrer, no cookie | +1 |
| Total | 11: block band |
ts=2026-09-24T09:14:03Z key=198.51.100.23/t13d1516h1_8daaf6152771_b0da82dd1658 ua="desktop browser, claimed" ja4=t13d1516h1_8daaf6152771_b0da82dd1658 ja4_class=script-lib syn_ttl=52 syn_win=64240 syn_opts=mss,sackOK,ts,nop,wscale os_guess=linux h2=no pages=31 assets=0 iqr_ms=100 median_ms=1900 entry_depth=6 referrer=- cookie=- score=11 band=block action=429 rule=bot-score-v7 review=false
The record keeps the raw observations, not just the verdict. That is what makes the decision reviewable when a real user complains, and it is what lets the weights be retuned later against the same data. A log line that says only score=11 cannot be argued with and cannot be learned from. The rule field names the version of the weights in force, because a score of 11 under version 7 and under version 8 are different claims.
09 What a fingerprint record proves
A TLS fingerprint proves that a connection was made with a particular client library configuration. A SYN fingerprint proves which stack, or which middlebox, originated the connection you saw. Neither proves who was behind it, and a score is an inference drawn from them, not a fact about the client. Say so when the record is used: "this connection matched a scripting library and fetched 31 pages with no assets" is defensible; "this was a bot" is a conclusion the reader should be allowed to draw.
Where a client wants to be identified, the burden reverses. A crawler that resolves under its operator's domain, appears in a published list or signs its requests has offered evidence, and the checks that test it are described under proving a bot's identity at the edge. For everything else, the fingerprint record above is the evidence, and it is only as good as the log it sits in: synchronised time, untouched files and a stated retention, the standard set out in the edge log as a tamper-evident record.
10 Expert tips
- Score on the ClientHello, act on the session. The handshake is cheap to read and stable per client; the session shows intent. A high handshake score with human behaviour is a privacy browser, not a bot.
- Refresh the reference table on browser release day. Every major browser release shifts the ClientHello. A table that is two versions old scores real users as scripting libraries.
- Sample the middle band by hand every week. Ten sessions from the challenge band, read as a human would, tell you more about the weights than any aggregate.
- Version the weights. Put the rule version in every record and change it whenever a weight changes, so that old records can be re-scored under the old rule.
Agents that drive a real browser defeat most of these signals; the behaviour of AI agent sessions explains what still works.
11 Questions
What is a JA3 or JA4 fingerprint?
A hash or structured label built from fields in the TLS ClientHello: version, cipher suites, extensions, groups and signature algorithms. It identifies the TLS library a client uses. JA4-style labels sort extensions so browsers that randomise order still produce a stable value.
Can TLS fingerprinting work without decrypting traffic?
Yes. The ClientHello is sent before encryption begins, even in TLS 1.3, so a passive tap or the balancer can read it. HTTP/2 SETTINGS and header order, by contrast, require terminating TLS.
Why does corporate NAT cause false positives?
Many users share one public address, so per-address request rates look like a single aggressive client. Combine rate with fingerprint and session signals, and key limits on more than the address.
Can headless browsers be detected reliably?
Not reliably. They run a real browser engine, so protocol fingerprints match. Behaviour, automation flags and consistency across many addresses catch careless ones; well-built automation raises the cost of detection rather than evading it entirely.
Should a high bot score always mean a block?
No. Use bands: pass, shape or challenge, block. Shaping keeps false positives usable while limiting damage, and the middle band is where you learn whether the weights are right.
How often should the fingerprint reference table be updated?
Whenever a major browser ships a new version, and immediately if the pass band starts filling with a new unknown fingerprint. Capture ClientHellos from known-good sessions, add them as the new browser profile, and keep the old one for a month while stragglers update.