The most honest thing your client sends
Everything above comes from the handshake that secured this very connection, via Fastly’s client connection variables. The cipher suites and extensions your client offered, in the exact order it offered them, are decided by its TLS library — not by any setting you can casually change. That makes the offer list a signature.
JA4 and JA3: a name for your TLS stack
Hash the interesting parts of a Client Hello in a defined order and you get a compact fingerprint that groups clients by their TLS implementation. JA3 (the MD5 above) did this first; JA4 is its structured successor — human-skimmable segments covering protocol, SNI presence, cipher and extension counts and digests. Two very different-looking user agents with the same fingerprint are probably the same software wearing different name tags, which is why these hashes earn their keep in bot defence and abuse triage.
Fastly computes both at the edge as tls.client.ja4 and tls.client.ja3_md5 — JA4 landed in VCL in 2024, so if you have not looked at this variable family in a while, that one is new.
Compare clients from the command line
The plain-text version lives at /raw. Fetch it with two different tools and diff the fingerprints — curl and your browser will not agree, and that disagreement is the entire point.
The same variables drive edge logic
Everything on this page is a live VCL expression, evaluated on every request before your origin is ever involved. That means each value can conditionalise the request flow — route, block, redirect, rewrite, rate limit, or vary the cache on it. A taste, in this site’s dialect:
The other half is insight: every variable here can be emitted through Fastly’s real-time log streaming from vcl_log — per-request, from every POP, with no JavaScript beacons or client-side analytics. Stream them into your warehouse and you know your audience’s client stacks and fingerprint prevalence at whatever depth you like.
One service, three hostnames, zero origins
geoip, tls and device are a single origin-less Fastly VCL service, routed by req.http.host. Every response is a synthetic response: the variables above are captured into request headers in vcl_recv (most are not available in vcl_error, where the page is assembled) and interpolated into the HTML at the POP nearest you. Values echoed from client-controlled input are HTML-escaped in VCL first.
The whole family works this way — see http.alpagot.net and h3.alpagot.net for the other experiments, and Fastly’s VCL variables reference for everything the edge can see.
Richard Alpagot
Senior Cloud Engineer at Fastly, collector of small sites that explain themselves.
- websitewww.alpagot.net
- blogblog.alpagot.net
- linkedin/in/alpagot