All posts

October 2, 20265 min readscrapingbrowseranti-detection

Headless Chrome Is Why You're Getting Blocked

You can patch every fingerprint you know about and still be detected, because headless is visible on its own. What we changed after chasing this for months.

By Scrappy Team

If your scraper works for a week and then starts seeing captchas on every run, the usual next step is to go hunting for fingerprints. Patch navigator.webdriver. Spoof the plugin array. Fix the WebGL vendor string. Add a user agent that matches a real Chrome build.

You can do all of that and still be blocked, and it is worth understanding why before spending another month on it.

Headless is its own signal

The thing most stealth guides get wrong is treating "headless" as a collection of fingerprints to patch. It is not. It is a different rendering mode, and it differs from headful Chrome in ways that have nothing to do with the properties people usually spoof.

Timing differs, because there is no compositor driving frames to a display. Font rasterisation differs. Certain GPU paths resolve differently when there is no window system underneath. Some events that a real window manager generates never fire at all.

A detection vendor does not need to find navigator.webdriver to notice any of this. And because these differences come from the rendering mode itself, no amount of property-patching in JavaScript removes them — you are editing the labels on a box whose contents are the wrong shape.

Our conclusion, after watching this play out repeatedly: headless is independently detectable regardless of the fingerprint injected over it. So Scrappy's browsers run headful. Real Chrome, real display, inside the container. It costs more compute. It stops a whole class of blocks that no patch was ever going to fix.

The second signal: your browser disagrees with itself

Here is the one that catches people who have already solved the first.

You route your browser through a residential proxy in Chicago. Good. But the browser is still reporting UTC as its timezone, because that is what the container's clock says. And its geolocation API, if asked, answers from somewhere else again.

A site now sees a visitor whose IP is in Illinois, whose clock is in Greenwich, and whose stated locale is something else entirely. No real person produces that combination. It is not a subtle tell; it is contradictory evidence on the first page load.

The fix is not glamorous but it has to be done per session: resolve the timezone from the exit IP the session is actually using, and emulate that timezone in the browser context. Scrappy does this on every session created with a proxy. The session panel displays the resulting timezone next to the egress IP, and if those two ever disagree, the session is advertising itself as automated.

If you build this yourself, that pairing — exit IP and emulated timezone, visible side by side — is the single most useful diagnostic you can give yourself.

The third signal: not all residential IPs are residential

This one surprised us enough to change how we buy.

We measured a pool of twenty addresses sold as "static high quality US residential". Checked against commercial IP reputation data, fifteen of the twenty were already flagged as proxies, and nineteen of the twenty had no subscriber reverse DNS — the PTR record a real consumer connection almost always has.

They were residential in the sense that the ASN said so. They were not residential in the sense that mattered, which is whether a reputation database had already seen them being used as proxies by somebody else.

The consequence is worse than a bad average, because of how sticky assignment usually works. If you hash a user onto a pool deterministically — which you want, so a returning user keeps the same exit — then a user who lands on a flagged address lands on it every single time. Their experience is not "occasionally blocked". It is "permanently blocked", while another user on the same pool has no problems at all and nobody can reproduce the bug.

So: measure the addresses you were sold, and prefer the clean ones explicitly. A pool is not interchangeable just because the invoice says it is.

What this adds up to

Four things, in rough order of how much they buy you:

  1. Run headful. Headless is detectable on its own terms, and no fingerprint patch addresses it.
  2. Make the browser agree with its exit. Timezone emulated from the exit IP, per session. Contradictions are the cheapest possible detection.
  3. Keep an identity stable. Same profile, same cookies, same exit, same fingerprint, every run. A returning user is ordinary; a new user from a new country holding last week's cookie is not.
  4. Audit your IPs. Buy on measured reputation, not on the word "residential".

None of this is about defeating anyone. It is about not transmitting, on the first request, three independent signals that you are a robot — and then concluding the site has unbeatable defences when really it just noticed.

The part people skip

If you are being blocked, measure before you patch. Load a fingerprinting page in your own session and read what it actually says about you. Nearly every team we have talked to about this was patching something that was already fine, while the timezone sat at UTC in plain sight.


Scrappy's cloud browser runs headful Chrome with per-session timezone emulation and reputation-checked residential exits, so you do not have to build any of the above. Start with 100 free credits.

Keep reading