DEV Community

Xavier Fok
Xavier Fok

Posted on

TLS Fingerprinting: The Bot Detection Method You Cannot Ignore

You have the right proxy, the right browser fingerprint, and human-like behavior. But you are still getting blocked. The culprit? TLS fingerprinting — a detection method that works at the network level, before your JavaScript even loads.

What Is TLS Fingerprinting?

When your browser connects to a website over HTTPS, it performs a TLS handshake. The very first message in this handshake — the Client Hello — contains a wealth of identifying information:

  • Cipher suites — Which encryption methods your client supports
  • TLS extensions — Additional features like SNI, ALPN, signature algorithms
  • Elliptic curves — Which curves your client supports
  • Extension order — The sequence in which extensions appear
  • Compression methods — Supported compression algorithms

JA3 and JA4 Fingerprints

JA3

The original TLS fingerprinting method. It hashes the Client Hello parameters into a 32-character MD5 hash:

JA3 = MD5(
    TLSVersion,
    Ciphers,
    Extensions,
    EllipticCurves,
    EllipticCurvePointFormats
)
Enter fullscreen mode Exit fullscreen mode

Example JA3 hashes:

Chrome 121 on Windows:  cd08e31494f9531f560d64c695473da9
Firefox 121 on Windows: e4bb02e2d9e1e3e0e5c84899b8a949c9
Python requests:        b32309a26951912be7dba376398abc3b
curl:                   456523fc94726331a8d0515e28e1a8e7
Enter fullscreen mode Exit fullscreen mode

Notice how Python requests and curl have completely different fingerprints from real browsers. This is a dead giveaway.

JA4

The next generation, providing more detailed fingerprinting:

JA4 = Protocol + Version + SNI + CipherCount + ExtensionCount + ALPN +
      Truncated SHA256(Ciphers) + Truncated SHA256(Extensions + SigAlgs)
Enter fullscreen mode Exit fullscreen mode

JA4 is harder to spoof because it captures more handshake details.

Why This Matters for Proxy Users

Consider this scenario:

  1. Your anti-detect browser claims to be Chrome 121 on Windows
  2. Your proxy provides a clean residential IP
  3. Your browser fingerprint (Canvas, WebGL) is perfect
  4. BUT your automation tool uses Python requests, which has a completely different TLS fingerprint

The server sees a Client Hello from Python, not Chrome. Instant detection.

Expected:  Chrome TLS fingerprint + Chrome User-Agent
Actual:    Python TLS fingerprint + Chrome User-Agent
Result:    BLOCKED
Enter fullscreen mode Exit fullscreen mode

Detection in the Wild

Major platforms and CDNs using TLS fingerprinting:

  • Cloudflare — Compares JA3 to known bot signatures
  • Akamai — Uses TLS fingerprinting in Bot Manager
  • PerimeterX — Cross-references TLS with browser fingerprint
  • DataDome — Real-time TLS analysis
  • Google — Uses TLS signals in bot detection

Solutions

Solution 1: Use Real Browsers

The most reliable approach. Puppeteer and Playwright control real browser engines, so TLS fingerprints match genuine browsers.

from playwright.sync_api import sync_playwright

# Playwright uses a real Chromium engine
# TLS fingerprint matches Chrome
with sync_playwright() as p:
    browser = p.chromium.launch()
    page = browser.new_page()
    page.goto("https://target.com")  # Real Chrome TLS fingerprint
Enter fullscreen mode Exit fullscreen mode

Solution 2: TLS-Spoofing Libraries

For HTTP-level requests without full browser overhead:

curl_cffi (Python):

from curl_cffi import requests

# Impersonate Chrome TLS fingerprint
response = requests.get(
    "https://target.com",
    impersonate="chrome121",
    proxies={"https": "http://user:pass@proxy:port"}
)
Enter fullscreen mode Exit fullscreen mode

tls-client (Python):

import tls_client

session = tls_client.Session(
    client_identifier="chrome_121",
    random_tls_extension_order=True
)

response = session.get(
    "https://target.com",
    proxy="http://user:pass@proxy:port"
)
Enter fullscreen mode Exit fullscreen mode

Solution 3: Custom TLS Configuration

For advanced users, configure TLS parameters manually:

import ssl
import urllib3

# Create custom SSL context matching Chrome
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
ctx.set_ciphers(
    "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:"
    "TLS_CHACHA20_POLY1305_SHA256:ECDHE-ECDSA-AES128-GCM-SHA256"
)
ctx.minimum_version = ssl.TLSVersion.TLSv1_2
Enter fullscreen mode Exit fullscreen mode

Testing Your TLS Fingerprint

Verify your fingerprint before deploying:

# Check your JA3 fingerprint
response = requests.get("https://ja3er.com/json")
print(response.json())
# Compare against known browser fingerprints
Enter fullscreen mode Exit fullscreen mode

Best Practices

  1. Match TLS to User-Agent — If claiming Chrome, use Chrome TLS fingerprint
  2. Use browser engines for account operations — Never use raw HTTP libraries for logged-in sessions
  3. Test fingerprints regularly — Browser updates change TLS fingerprints
  4. Use TLS-spoofing libraries for scraping — curl_cffi is excellent for high-volume scraping
  5. Monitor for new detection methods — JA4 and HTTP/2 fingerprinting are evolving

For TLS fingerprinting guides and bot detection evasion strategies, visit DataResearchTools.

Top comments (2)

Collapse
 
andremzzr profile image
André Ribeiro

Nice article!
For the ones who came here and are trying scrapping methods with node / bun I recommend got-scrapping lib. It bypasses the TLS Fingerprint problem!

Some comments may only be visible to logged-in visitors. Sign in to view all comments.