Skip to content
[menu][close]

FOUNDRYNETNo. 001SEPTEMBER 2026MONITORING

Bot vs Human Traffic: Fingerprinting Automated Clients

No single signal separates a script from a person, but a TLS handshake, an HTTP/2 preface, a TCP SYN and a click rhythm each leak something, and together they make a usable score.

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.

Signals by layerSIGNALS BY LAYER04Behaviour: timing, assets, paths03HTTP/2: SETTINGS, header order02TLS: ClientHello lists01TCP/IP: TTL, window, optionsSignals by layerSIGNALS BY LAYER04Behaviour: timing, assets, paths03HTTP/2: SETTINGS, header order02TLS: ClientHello lists01TCP/IP: TTL, window, options
Lower layers are harder to fake cheaply; the top layer is where intent shows.

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.

Captured SYN, fields a scorer reads (illustrative)
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.

PRIVACY

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.

No single row would justify a block. Five of them together leave little doubt, and the record shows why.
SignalObserved for client 198.51.100.23Points
Claimed user-agentCurrent desktop browser on a 128-TTL operating system0 (a claim, not a signal)
TLS ClientHelloCipher and extension list matches a scripting-language HTTP library+3 (mismatch with the claim)
HTTP/2 SETTINGSConnection used HTTP/1.1 only; no SETTINGS observed+1 (browsers negotiate HTTP/2)
TCP SYNTTL 52, window 64240, options mss,sackOK,ts,nop,wscale+2 (Linux server stack, not the claimed OS)
Asset loads0 of 31 HTML pages followed by a CSS, script or image request+2
Inter-request intervalMedian 1.9 s, interquartile range 0.1 s+2 (regular)
Session startFirst request was a page 6 levels deep, no referrer, no cookie+1
Total11: block band
Scoring record written to the log for that client, illustrative
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.