2026-09-15 · anti-bot

Cloudflare Error 1020 Access Denied: Playwright Fixes

Cloudflare Error 1020 Access Denied: Playwright Fixes

What Cloudflare Is Actually Checking

If you’re hitting cloudflare error 1020 access denied or its cousins (1015, 1010, 1012, 1006, 1007) from Playwright or Puppeteer, you’re running into Cloudflare’s mitigation stack, not a random bug.

At a high level Cloudflare evaluates:

Different errors map to different slices of that system. The fix that holds is always: match real traffic on all the dimensions Cloudflare cares about. Swapping User-Agent or adding a stealth plugin only touches a thin layer.

We’ll walk through:

If you’re designing a more general-purpose agent/browser stack, the broader patterns also tie into the anti-bot cluster here: AI Agent Browser Automation vs “Just Use Playwright”.

Reading Cloudflare Headers From Node

Before guessing, inspect what Cloudflare is telling you. Errors are split between:

Useful headers:

From Playwright you can inspect errors like this:

// playwright-cloudflare-debug.js
import { chromium } from 'playwright';

(async () => {
  const browser = await chromium.launch({ headless: true });
  const page = await browser.newPage();

  page.on('response', async (response) => {
    const url = response.url();
    const status = response.status();
    const headers = response.headers();

    if (status >= 400 || headers['cf-mitigated']) {
      console.log('\n=== Cloudflare debug ===');
      console.log('URL:', url);
      console.log('Status:', status);
      console.log('cf-ray:', headers['cf-ray']);
      console.log('cf-mitigated:', headers['cf-mitigated']);
      console.log('server-timing:', headers['server-timing']);
    }
  });

  await page.goto('https://target.example/', { waitUntil: 'domcontentloaded' });
  await browser.close();
})();

In Puppeteer the pattern is similar:

import puppeteer from 'puppeteer';

const browser = await puppeteer.launch();
const page = await browser.newPage();

page.on('response', async (response) => {
  const headers = response.headers();
  if (headers['cf-ray']) {
    console.log('CF response', response.status(), headers['cf-mitigated']);
  }
});

await page.goto('https://target.example/');

Collect real headers from your failing requests before changing your setup. Most of the confusion around 1020/1015/1010 is guessing without looking.

Error 1020: Access Denied (Firewall Rule / IP Reputation)

Error 1020: Access denied is Cloudflare’s way of saying “a firewall rule said no”. That rule may be explicit (zone owner block rule) or implicit (managed WAF / IP reputation).

Typical characteristics:

Common triggers for automation:

Durable fixes that hold:

Anti-patterns that rarely hold:

If you don’t want to build this stack yourself, tools like Human Browser — pay as you go, no subscription sit on top of residential IPs and real browser execution, exposing a simple agent API.

Playwright: Hardening Against 1020

Cloudflare tends to dislike default dev-tool fingerprints. Make your Playwright session closer to a real user:

import { chromium } from 'playwright';

(async () => {
  const browser = await chromium.launch({ headless: true });
  const context = await browser.newContext({
    viewport: { width: 1280, height: 720 },
    userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) ' +
      'AppleWebKit/537.36 (KHTML, like Gecko) ' +
      'Chrome/124.0.0.0 Safari/537.36',
    javaScriptEnabled: true,
  });

  const page = await context.newPage();

  // Do not block images/CSS/JS – Cloudflare uses them for fingerprinting
  await page.goto('https://target.example/', {
    waitUntil: 'networkidle',
    timeout: 45_000,
  });

  console.log('Content length:', (await page.content()).length);
  await browser.close();
})();

Combine that with a residential proxy layer and you’ll see many 1020s disappear.

Error 1015: You Are Being Rate Limited

Diagram showing a series of HTTP requests exceeding a rate limit and then slowing down after 1015 errors
Rate limiting kicks in when bursts exceed per-IP thresholds.

Error 1015 is explicitly about rate limiting.

Signals:

Cloudflare can rate-limit:

Bot-style patterns it dislikes:

Durable fixes:

Example pacing logic with Playwright:

// crude token-bucket style pacing per context
class Pacer {
  constructor({ intervalMs, maxTokens }) {
    this.intervalMs = intervalMs;
    this.maxTokens = maxTokens;
    this.tokens = maxTokens;
    setInterval(() => {
      this.tokens = Math.min(this.maxTokens, this.tokens + 1);
    }, this.intervalMs);
  }

  async consume() {
    while (this.tokens <= 0) {
      await new Promise(r => setTimeout(r, this.intervalMs));
    }
    this.tokens -= 1;
  }
}

const pacer = new Pacer({ intervalMs: 1000, maxTokens: 2 });

async function safeGoto(page, url) {
  await pacer.consume();
  const response = await page.goto(url, { waitUntil: 'domcontentloaded' });
  if (response?.status() === 429) {
    console.warn('Hit 429, backing off');
    await new Promise(r => setTimeout(r, 10_000));
  }
}

Distinguishing 1015 From 1020 Using Headers

Symptom1015 rate limited1020 access denied
Page text"You are being rate limited""Error 1020: Access denied"
Status code429 (usually)403
cf-mitigated headeroften limit / challengeoften block / ban
Behavior after cooldownEventually recoversStays blocked until IP / rule changes
Primary root causeToo many requestsFirewall rule / IP / country / reputation

If errors vanish after slowing down, it was 1015. If they persist indefinitely, suspect 1020 or a ban code.

Error 1010: Browser Signature Mismatch

Error 1010 generally points at Cloudflare deciding “this is not a real browser”. You’ll often see it after trying to be clever with headers or headless flags.

Typical triggers with Playwright/Puppeteer:

Durable fixes:

For Puppeteer, avoid obviously-automated launch options like forcing very small viewports, unusual language combos, or stripping all plugins. Stick close to defaults and focus on IP/frequency.

Errors 1012, 1006, 1007: Banned or Blocked IP

These codes are variations on “this IP is not welcome here anymore”. They often show as Cloudflare stock pages with those codes in body text.

Common causes:

Durable response:

If you see 1006/1007/1012 plus cf-mitigated: ban (or no cf-mitigated, but the same IP fails across paths), that IP is effectively dead for that zone.

403 With cf-mitigated: Silent Blocks

Sometimes you won’t see a pretty Cloudflare error page. Instead you get a 403 with HTML from the origin, but headers show that Cloudflare interfered.

Signs:

This usually means:

Your mitigation strategy is the same stack as 1020, but more subtle:

503 Challenge Loops and Turnstile

Illustration of a browser page with a Turnstile challenge and an automation workflow solving it
Turnstile challenges require full browser execution and session reuse.

Another pattern: 503 plus a Cloudflare challenge that never resolves in automation, sometimes looping forever.

Characteristics:

Today most challenges are Turnstile-based: a widget rendered in the page that you must solve (often automatically, but associated with your IP/session and site key).

If you run headless Chromium and ignore the widget, you’ll loop forever.

Durable fixes:

This is the main reason tooling like Human Browser exists: the service provides:

If you want to integrate a service like that into an agent workflow, the A2A endpoint is documented at https://agent.humanbrowser.cloud/a2a, and there’s a simple install flow at /install.

Playwright vs Puppeteer Against Cloudflare

Playwright and Puppeteer are both capable of behaving like real browsers when used correctly. The important bits are outside the test runner: IPs, pacing, and avoiding obvious fingerprints.

AspectPlaywrightPuppeteer
Browsers supportedChromium, Firefox, WebKitChromium, Firefox (via DevTools/WebDriver BiDi)
Default headlessHeadless by default for testsHeadless by default
Install experiencenpm init playwright@latest sets up confignpm i puppeteer downloads compatible Chrome
Typical Cloudflare riskParallel tests hammering same IP / profileMinimal header tweaks breaking fingerprint
Mitigation knobsContexts with UA/viewport/locale per projectBrowser launch + per-page header control

You can make either work; pick the one you’re comfortable hardening, and focus on the network layer rather than chasing one more stealth plugin.

Putting It Together: A Durable Mitigation Stack

Treat Cloudflare as an adversarial system. To make your automation survive over months instead of days:

  1. Decode errors correctly
  1. Stabilise IP & ASN
  1. Behave like a real user
  1. Pace traffic
  1. Handle challenges

When you outgrow simple scripts, it’s often easier to step up to a managed anti-bot-aware browser layer than to re-implement all of this yourself. That’s the design space tools like Human Browser live in: real browsers, residential IPs, and an API that agents and scrapers can call when plain Playwright or Puppeteer keep slamming into cloudflare error 1020 access denied.

Where This Fits In Anti-Bot Architecture

All of this sits inside a broader anti-bot landscape:

If you’re designing a system that should outlive tweaks to a single provider, think in terms of:

The anti-bot cluster article, AI Agent Browser Automation vs “Just Use Playwright”, goes deeper into how to design these layers so Cloudflare-specific fixes don’t infect your whole codebase.

Frequently asked questions

What does Cloudflare error 1020 access denied mean for Playwright?

Cloudflare error 1020 means a firewall rule blocked your request, often due to IP reputation, country blocks, or WAF rules. Fixes usually require better IPs and more realistic browser behavior, not just a new User-Agent string.

How do I fix Cloudflare 1015 rate limited in Puppeteer?

Implement global per-IP rate limiting, reduce parallel page loads, and back off when you see 429 responses. If requests resume working after a cooldown, your pacing is the issue, not fingerprinting.

Why does Cloudflare show error 1010 when I use headless Chrome?

Error 1010 is generally a browser signature mismatch. Headless defaults, stripped headers, or blocked scripts make your session look non-human. Use standard browser settings, keep JS enabled, and avoid over-customising headers.

How can I detect if Cloudflare is blocking me via headers?

Check for `cf-ray`, `cf-mitigated`, and `server-timing` headers on 4xx/5xx responses. A 403/429/503 with these headers usually indicates Cloudflare mitigation even if the page is branded like the origin site.

Can I bypass Cloudflare challenges with plain HTTP clients?

Not reliably. Modern Cloudflare challenges rely on full browser execution and Turnstile widgets. You typically need a real browser environment like Playwright, Puppeteer, or a managed browser service that can execute the page’s JavaScript and maintain cookies.

When should I move from raw Playwright to a managed browser service?

If you keep hitting 1020/1010/503 loops despite tuning headers, pacing, and proxies, or you need large-scale residential IP coverage and automatic challenge handling, a managed browser layer such as Human Browser can be more effective than extending Playwright scripts indefinitely.

Related guides

Top up — $20

Loading secure checkout…

More coins →