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 (0)