JustProxies

Reference

HTTPS & TLS fingerprinting

5-min readtls-fingerprinting
A growing number of targets fingerprint TLS Client Hello messages to distinguish bots from real browsers — regardless of your IP or User-Agent. The fix is not a proxy setting; it's the HTTP client you choose.

What TLS fingerprinting is

When a TLS connection opens, the client sends a Client Hello message that lists its supported cipher suites, TLS extensions, elliptic curves, and other parameters. These lists are specific to the TLS library in use — Chrome uses one combination, Firefox another, Python's ssl module yet another.

Two fingerprinting schemes are widely deployed:

  • JA3 — an MD5 hash of five Client Hello fields. Consistent for a given library version; trivially computed by any HTTPS server.
  • JA4 — a newer, more granular scheme that also captures extension order and ALPN values.

Cloudflare, Akamai, Datadome, PerimeterX, and several in-house bot protection systems check JA3/JA4. A Chrome User-Agent paired with a Python TLS fingerprint is an instant signal.

Why Python requests gets detected

Python's ssl module (which requests and standard httpx use) produces a JA3 fingerprint that is trivially identifiable as non-browser traffic. It's not a flaw — it's just the OpenSSL defaults on that Python version. No amount of header manipulation fixes this; the fingerprint is negotiated before HTTP even starts.

ClientJA3 varies?Looks like browser?
requestsNo (fixed per Python version)No
httpx (http/2)Slightly different from requestsNo, but less obvious
curl_cffi (Chrome impersonation)Yes, per Chrome versionYes
curl (default)No (fixed per curl/OpenSSL build)No
curl with BoringSSL / --tlsv1.3Closer to ChromeCloser
undici (Node)Varies; less fingerprinted than requestsPartial

Python solutions

Option 1 — curl_cffi (recommended for fingerprint-sensitive targets): wraps libcurl with BoringSSL and can impersonate specific Chrome, Firefox, and Safari versions.

curl-cffi.pypython
from curl_cffi import requests as cffi_requests

# Impersonate Chrome 124 — full TLS fingerprint match.
r = cffi_requests.get(
    "https://target.com/",
    impersonate="chrome124",
    proxies={
        "http":  "http://USER:[email protected]:8080",
        "https": "http://USER:[email protected]:8080",
    },
    timeout=20,
)
print(r.status_code)
installshell
pip install curl_cffi
Available impersonation targets include chrome110, chrome116, chrome124, firefox120, safari17_0, and others. Match the version you want to mimic. Rotate between versions if the target tracks Client Hello changes over time.

Option 2 — httpx with HTTP/2: a different fingerprint from plain requests, less recognisable on targets that haven't built a specific rule for it.

httpx-h2.pypython
import httpx

with httpx.Client(
    http2=True,
    proxy="http://USER:[email protected]:8080",
    timeout=20,
) as client:
    r = client.get("https://target.com/")
    print(r.status_code)

Node solutions

undici (and Node's native fetch) uses Node's built-in TLS stack, which produces a fingerprint distinct from Python but still detectable by sophisticated systems. For targets that specifically fingerprint Node, the options are:

  • undici with custom TLS options — adjust cipher suites and ALPN to move the fingerprint closer to a browser.
  • playwright / puppeteer — a real Chromium instance has a real Chrome fingerprint. The heavyweight option, but unbeatable for fingerprint matching.
undici-tls.jsjavascript
import { ProxyAgent, fetch } from "undici";

const dispatcher = new ProxyAgent({
  uri: "http://USER:[email protected]:8080",
  connect: {
    // Nudge the cipher list closer to a browser profile.
    ciphers: [
      "TLS_AES_128_GCM_SHA256",
      "TLS_AES_256_GCM_SHA384",
      "TLS_CHACHA20_POLY1305_SHA256",
      "ECDHE-ECDSA-AES128-GCM-SHA256",
      "ECDHE-RSA-AES128-GCM-SHA256",
    ].join(":"),
  },
});

const r = await fetch("https://target.com/", { dispatcher });
console.log(r.status);

What the proxy can and can't do

The proxy is a CONNECT tunnel for HTTPS traffic — the TLS handshake happens between your client and the target, invisibly to us. We cannot modify your Client Hello or change your fingerprint. The fingerprint is set entirely by your HTTP client library.

Switching from datacenter to residential proxies does not help with TLS fingerprinting. The exit IP changes, but the Client Hello stays the same. Fix the fingerprint first; choose the pool for other reasons.
Found a gap, or something wrong?
A real human reads support email.