guides · datacenter IP

Turnstile works locally and fails in Docker

It is the most-reported Turnstile symptom that has no documented answer: identical code clears the challenge on a laptop and loops forever on a VPS, a Lambda, or a container. The variable that changed is not your code and usually not your browser — it is the address the request comes from, and Cloudflare's own troubleshooting documentation names IP reputation as a reason challenges are issued while its Turnstile documentation says nothing about hosting ranges at all.

The symptom, as people actually report it

Two Stack Overflow questions describe it precisely, and neither has an accepted answer — which is the whole reason this page exists.

QuestionEnvironmentAnswers
*Is it possible to bypass Cloudflare Turnstile from a datacenter IP using Selenium or curl_cffi in 2026? Local works, Docker/Hosted always fails* (79877820, 1,034 views)Windows laptop vs Docker on Hugging Face SpacesOne, scoring zero, and it is an admonishment rather than an answer
*Playwright works locally but gets Cloudflare Turnstile 403 on AWS Lambda* (79997812)Local Chromium vs Chromium in a Lambda containerNone

The first reports that curl_cffi with TLS impersonation works locally and returns 403 immediately in Docker, and that undetected-chromedriver clears the checkbox locally and enters an endless loop hosted — the element is found, clicked, and the challenge never completes. The second is a full Playwright browser: real Chromium, JavaScript running, a French locale and timezone set, navigator.webdriver hidden. It still gets 403 and a __cf_chl_rt_tk challenge parameter from Lambda.

That second case is the one worth sitting with. A complete browser with a real JavaScript engine did not help. Whatever is failing is not the absence of a JS runtime.

What Cloudflare actually documents

Two documented facts do most of the explaining, and one documented absence matters as much as either.

Fact one: IP reputation is an input to whether a challenge is issued. Cloudflare's troubleshooting page lists the sources of a challenge as high threat score, IP reputation, bot detection, WAF custom rules, Browser Integrity Check and Challenge Passage, and describes IP reputation as "If your IP has a history of suspicious activity, it may be flagged." Its remediation advice is blunt: "If your IP address has a poor reputation (often seen with shared VPNs or corporate proxies), try switching to a different, trusted network connection."

Fact two: Cloudflare's canonical example of a block rule is an ASN block on two cloud providers. In their own worked example for allowlisting around a mitigation, the scenario is "Block Amazon Web Services (AWS) and Google Cloud Platform (GCP) because of large volumes of undesired traffic, but allow Googlebot and other known bots that Cloudflare validates", with the expression (ip.src.asnum in {16509 15169} and not cf.client.bot). Site owners are shown how to do exactly the thing people run into.

The absence: Cloudflare's Turnstile documentation contains no statement about datacenter IPs, hosting providers or ASNs. Every file under the Turnstile and challenges documentation was searched for datacenter, data center, hosting provider, VPS, cloud provider and residential; the single hit is a generic sentence about excluding traffic by ASN, and it is in the challenges section, not Turnstile's. Anyone telling you Cloudflare documents a datacenter penalty for Turnstile is telling you something that is not in the documentation.

The honest shape of the claim: Cloudflare documents that IP reputation feeds challenge decisions and shows site owners how to challenge whole cloud ASNs. It does not publish a rule saying "datacenter addresses fail Turnstile". The gap between those two statements is where all the folklore lives.

The other half: automation itself is unsupported

There is a second documented fact that most of these reports miss entirely, and it changes what "fix it" can even mean. Cloudflare's supported-browsers page lists unsupported environments, verbatim:

"Command-line tools such as wget, curl, or others that lack JavaScript execution capabilities required for Cloudflare Challenges." · "Automated browsers are not supported for solving production challenges." · "Browser automation frameworks, such as Selenium, Puppeteer, Playwright, and Cypress, are not supported for solving production challenges. For automated Turnstile testing, use Turnstile test keys."

So the position is not that automation is difficult and might work with better flags. It is that solving a production challenge from Selenium, Puppeteer, Playwright or Cypress is outside what Cloudflare supports, and their documented answer for automated testing is the dummy sitekeys. A datacenter IP makes an unsupported path fail more reliably; it is not the sole reason it fails.

This is also why "it works locally" is a weaker signal than it feels like. A residential address with a good reputation buys enough margin for an unsupported client to pass anyway. Move the same client to an address with no such margin and the underlying situation stops being hidden.

How to tell which of the two you have

The failures look identical and have different fixes, so separate them before changing anything.

  • Run the same code, from the server, through a residential proxy. One variable, and it is the one under suspicion. If it passes, the address is the dominant factor. If it still fails, the client is.
  • Run a real headed browser on the server — Xvfb or a virtual display — rather than a headless one. If headed passes and headless does not on the same address, you are looking at automation detection rather than IP reputation.
  • Check what you are actually being served. A 403 with a __cf_chl_rt_tk parameter is a challenge page, not a Turnstile widget, and the fix is different. Turnstile vs Cloudflare challenge separates them, and our checker will tell you which one a URL serves.
  • Look for a loop rather than a refusal. Cloudflare documents challenge loops as happening "in very specific cases where we detect strong bot signals" — that is a detection outcome, not a network one. The verification loop page covers the causes that are yours to fix.

One anecdote from writing this page, offered because it is a cleaner demonstration than anything we could construct. Cloudflare's own community forum — the thread about being stuck in a Turnstile loop from Hetzner servers — returned 403 and a "Just a moment…" interstitial to a plain HTTPS request from the datacenter address this research ran on. The discussion about datacenter IPs failing challenges was itself unreachable from a datacenter IP.

What actually changes the outcome

In rough order of how much they move the needle, and with the honest cost of each:

ChangeWhat it addressesHonest cost
Use Cloudflare's test sitekeys in CIRemoves the challenge from the test entirelyOnly works on your own property. It is also Cloudflare's own documented recommendation
Move egress to a residential or ISP addressIP reputationOngoing cost, and proxy quality varies enormously
Run headed rather than headlessAutomation fingerprintingMore memory and a display server on every worker
Patch the automation stackAutomation fingerprintingA maintenance treadmill against an adversary that ships continuously
Hand the challenge to a solverBoth at onceA per-solve cost, and it needs the target to be yours or authorised

If the target is your own property and the challenge is only in the way of a test, start at the top of that table. The test keys are deterministic, free, and specifically what Cloudflare tells you to use — most of the effort spent on this problem is spent solving a challenge that did not need to be there.

When the challenge does have to be cleared — staging behind a real widget, synthetic monitoring against your own production — the request is the same regardless of where your code runs, because the solve does not happen from your address. A sitekey and the page URL go in, a token comes back, and you submit it as cf-turnstile-response. Prepaid credits start at $0.40 per 1,000 solves and fall to $0.075 at volume; failed solves are never billed; the first 1,000 are free. The solve reference has the call.

Common questions

Not as a documented rule. Cloudflare's Turnstile documentation says nothing about datacenter addresses, hosting providers or ASNs. What it does document is that IP reputation is one input to whether a challenge is issued, and its own example of a block rule challenges the AWS and GCP ASNs. The effect people observe is real; the rule they assume exists is not published.

Because a JavaScript engine is not the only thing being assessed. Cloudflare states that automated browsers and frameworks including Playwright, Puppeteer, Selenium and Cypress are not supported for solving production challenges, and IP reputation feeds the decision separately. A real browser on an address with no reputation margin fails on both counts at once.

Change one variable. Run the identical code from the same server through a residential proxy. If it passes, the address dominates. If it still fails, run it headed rather than headless on the same address — if that passes, you are looking at automation detection instead.

It addresses one of the two factors. Cloudflare's own advice for a poor-reputation address is to switch to a trusted network, so the direction is right — but automation frameworks remain unsupported for production challenges regardless of the address, and proxy reputation itself decays as pools get used.

Cloudflare's documented answer is the dummy sitekeys: they behave identically every run, cost nothing, and remove the challenge from the test. They only work on a property you control, which is the correct constraint — if you are testing your own form logic, the challenge was never the thing under test.

Related

Sources

More in Guides

Playwright and Cloudflare TurnstileStop Cloudflare Turnstile breaking Playwright tests: use Cloudflare's official test sitekeys on staging, or solve a real widget and inject the token.Python Cloudflare Turnstile SolverSolve Cloudflare Turnstile from Python with plain requests or the solvegate SDK. Complete runnable code, async usage, retries, and error handling.Node.js Turnstile solverSolve Cloudflare Turnstile from Node.js: a zero-dependency global fetch version, the solvegate npm SDK, async/await error handling and TypeScript types.How to find a Turnstile sitekey on a pageFind a Cloudflare Turnstile sitekey in seconds: the data-sitekey attribute, keys starting 0x4, explicit-render calls, and Playwright/Node code that extracts it.How to get the cf-turnstile-response tokenWhat the cf-turnstile-response token is, the hidden input it lives in, how siteverify validates it, and how to get one in your own automated tests.Puppeteer Turnstile bypassHow to handle Cloudflare Turnstile in Puppeteer: Cloudflare's official test sitekeys for pages you own, plus a runnable token-injection script.Selenium and Cloudflare Turnstile in PythonHandle Cloudflare Turnstile in Selenium 4 and Python: test sitekeys, explicit waits, execute_script token injection, and what changes in headless.curl_cffi and Turnstilecurl_cffi impersonates TLS and HTTP/2 fingerprints and has no JavaScript runtime. Its own FAQ says so. What that solves, what it cannot, and where the line is.Scrapy and TurnstileScrapy has no JavaScript engine, so a Turnstile page returns markup and no data. The three documented routes, and which one Scrapy's own docs recommend first.undetected-chromedriver and TurnstileIt binary-patches one string out of chromedriver. Its own README says it does not hide your IP and that headless is unfinished. What that means for Turnstile.SeleniumBase and TurnstileThe uc_gui captcha methods use PyAutoGUI and raise in headless mode — the check is in the source. solve_captcha uses CDP and does not. Which to use where.Camoufox and TurnstileCamoufox is a Firefox fork that patches fingerprints at the C++ level. It ships no Turnstile solver, and the issue asking for one was closed as not planned.nodriver and Turnstilenodriver's README documents a tab.cf_verify() that clicks the Cloudflare checkbox. It is not in the shipped code. What the library does do instead.Patchright and TurnstilePatchright is a drop-in Playwright that closes the Runtime.enable leak and reaches into closed shadow roots — which is exactly where a Turnstile checkbox lives.DrissionPage and TurnstileDrissionPage reaches into non-open shadow roots and switches between browser and HTTP mode. It also forbids commercial use, and its docs are Chinese-only.Go, Colly and TurnstileColly has no JavaScript engine — its dependency graph proves it. What that means for Turnstile in Go, and how chromedp and rod compare when you need a browser.

Automating a gate you own or are authorised to test?

// SolveGate clears Cloudflare Turnstile and WAF challenges through one REST call · first 1,000 solves free