Bypass TLS Fingerprinting with curl_cffi & tls-client (JA3/JA4)

Published June 2, 2026 · 9 min read

You bought clean residential IPs, you rotate them perfectly, and you still get 403 on the first request. The IP is not the problem — your TLS handshake is. TLS fingerprinting lets a server identify the client library before a single byte of your HTTP request is read. This guide explains JA3/JA4, shows why Python's requests is trivial to detect, and gives working code to impersonate real browsers with curl_cffi and tls-client.

What Is TLS Fingerprinting (JA3 / JA4)?

When any client opens an HTTPS connection, it sends a ClientHello that advertises its TLS version, cipher suites, extensions, elliptic curves, and the order of all of them. That ordering is remarkably specific to the library that built it. Hash it and you get a JA3 fingerprint (or the newer, more robust JA4).

Chrome produces one fingerprint. Firefox another. Python's requests (built on urllib3 and OpenSSL) produces a fingerprint that screams "automation" — and it is identical across millions of bots. Anti-bot systems keep a blocklist of these. No matter how residential your IP is, a known-bot JA3 gets flagged.

See Your Own Fingerprint First

Before fixing anything, measure it. Send a request from your scraper to JIBAO's free JA3/JA4 fingerprint checker and compare it to what a real Chrome shows. If the JA3 hashes differ, the target can tell you apart from a human — and so can Cloudflare.

Why requests and httpx Can't Fix This

requests and httpx hand TLS to the system's OpenSSL. You can change headers and even the User-Agent all day, but the handshake underneath is still OpenSSL's, not Chrome's. Header spoofing without handshake spoofing is the most common reason "I copied the browser headers and it still blocks me" happens. You need a client that mimics the browser's actual TLS stack.

Fix It with curl_cffi

curl_cffi binds to curl-impersonate, a build of curl that reproduces real browser TLS and HTTP/2 fingerprints. One argument and your handshake looks like Chrome:

# pip install curl_cffi
from curl_cffi import requests

# impersonate a real Chrome TLS + HTTP/2 fingerprint
r = requests.get(
    "https://tls.browserleaks.com/json",
    impersonate="chrome131",
)
print(r.json()["ja3_hash"])   # now matches real Chrome

The API mirrors requests, so migrating an existing scraper is mostly a one-line import change. Supported targets include recent Chrome, Edge, Safari, and Firefox builds — pick one and keep it current, because fingerprints rotate as browsers update.

Combine It with Residential Proxies

Fingerprint matching and IP rotation are complementary, not alternatives. Use both: a browser-grade handshake over a clean residential IP is what actually gets through:

from curl_cffi import requests

PROXY = "socks5h://USERNAME:[email protected]:10001"

r = requests.get(
    "https://example.com/protected",
    impersonate="chrome131",
    proxies={"http": PROXY, "https": PROXY},
    timeout=30,
)
print(r.status_code)

Alternative: tls-client

tls-client (a Python wrapper over a Go uTLS implementation) is another solid option, with a large library of named profiles:

# pip install tls-client
import tls_client

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

session.proxies = {
    "http": "socks5h://USERNAME:[email protected]:10001",
    "https": "socks5h://USERNAME:[email protected]:10001",
}

r = session.get("https://example.com/protected")
print(r.status_code)

random_tls_extension_order=True shuffles extension order to avoid a static, reusable fingerprint — helpful against systems that track exact-match JA3 over time.

When You Still Need a Real Browser

Some targets check JavaScript-execution signals (canvas, WebGL, event timing) that no HTTP client reproduces. There, drive a real browser instead — see using proxies with Playwright and Puppeteer with stealth. The trade-off is cost and speed: HTTP clients like curl_cffi are far cheaper per request, so use them wherever they suffice and reserve full browsers for the hardest targets.

Checklist

TLS fingerprinting is the missing layer in most "clean IP but still blocked" stories. Fix the handshake, keep the residential IP, and the success rate jumps. For the full stack against the toughest WAF, read the 2026 Cloudflare bypass recipe.

Beat Fingerprinting with Clean IPs

Get $5 free credit and residential IPs to pair with browser-grade TLS fingerprints.

Start Free Trial
Universal for All IP Products · Massive Nodes Always Available

Join now & enjoy up to 100% deposit bonus.

New users get $5 USDT instantly, plus an extra first-deposit reward — limited-time offer.