JA3/JA4 TLS fingerprint rotation¶
The gateway randomizes its outbound TLS ClientHello per request to defeat JA3/JA4-based anti-bot systems (Akamai, Cloudflare). The SDK exposes the profile selector as a request kwarg or module default.
Motivation¶
JA3 fingerprints the TLS ClientHello bytes; JA4 (introduced by Akamai
in Feb 2026) extends that to include ALPN, extensions, and cipher ordering.
A vanilla Go crypto/tls ClientHello is a single, well-known fingerprint
that targets can block in one rule. Mimicking the ClientHello of Chrome,
Firefox, or Safari makes the gateway indistinguishable from a real browser
at the TLS layer.
Usage¶
import tierproxy
# Per-request profile
r = tierproxy.get("https://example.com", tls_fingerprint="chrome")
# Default for the whole client
from tierproxy import TierProxy
g = TierProxy(tls_fingerprint="random")
g.get("https://target.com") # rotates ClientHello on every outbound dial
Profiles: chrome (default), firefox, safari, random (rotates each
request).
How it works¶
The gateway uses refraction-networking/utls
to construct ClientHello bytes matching the chosen browser preset. The SDK
sends the X-TierProxy-TLS-Profile: <name> header on each CONNECT; the
gateway picks the matching uTLS preset before dialing the upstream.
Caveats¶
HTTPS only. Plain-HTTP forward proxying does not negotiate TLS, so there is no ClientHello to spoof.
randomrotates per outbound dial, not per pool slot. A socket already pre-dialed in the pool keeps its original ClientHello until it is reaped. With the default poolidleTimeout=90s, rotation is bounded.HTTP/2 ALPN. uTLS handles ALPN advertisement for each preset, but not every combination is exercised in CI. Open an issue if you hit a target that detects an HTTP/2 mismatch.
The target still sees the upstream’s IP and User-Agent. TLS fingerprint rotation is necessary but not sufficient. Pair it with
country=rotation, stickysession_idlifecycle management, and a realisticUser-Agentheader.Browser presets drift. Real Chrome updates its ClientHello every few releases; the uTLS preset lags. The gateway pins to a specific preset version per release — check
gateway/internal/pool/tls_profile.gofor the current pin.
Source¶
Gateway:
gateway/internal/pool/tls_profile.goSDK:
tls_fingerprint=kwarg onclient.get()and the module-level convenience functions.