Why Stealth Plugins Break in Production (2026)

Bottom line
Last updated: June 7, 2026.
puppeteer-extra-plugin-stealthandplaywright-extra-plugin-stealthare runtime monkey-patches. They hide a fixed list of JavaScript signals. In 2026 that list is both incomplete and, in places, counterproductive.- The reason they pass locally and fail in prod is rarely the plugin itself. It is the IP, the headless mode, and the stripped-down container fingerprint that change when you deploy.
- The fix is two parts: a maintained, protocol-patched browser (Patchright or Camoufox) plus the layers the plugin never touched — a residential IP, realistic behavior, and a matching TLS fingerprint.
Jump to: the 5 reasons they break · a quick diagnosis · what to use instead · migration code · FAQ. If you are specifically on Playwright and already past the basics, read Playwright stealth not working in 2026 next.
The plugins everyone starts with
If you have ever scraped a protected site with a headless browser, you have almost certainly typed one of these:
# Puppeteer
npm i puppeteer-extra puppeteer-extra-plugin-stealth
# Playwright
npm i playwright-extra puppeteer-extra-plugin-stealthNote that the Playwright stack reuses the same Puppeteer stealth plugin — playwright-extra-plugin-stealth is, under the hood, the Puppeteer plugin wired into playwright-extra. That shared lineage matters: both ecosystems inherit the same evasion list and the same blind spots.
These plugins were excellent. From roughly 2019 to 2022 they were the correct answer to "make my headless browser look real." They hide navigator.webdriver, fake the Chrome runtime object, patch the famous iframe.contentWindow leak, normalize the permissions API, and a few dozen other things. You added three lines and your scraper passed. The problem is not that the work was bad. The problem is what changed underneath it.
5 reasons stealth plugins break in production
1. Monkey-patching can't reach the leak that matters now
The single most important detection signal in 2026 is the CDP Runtime.enable leak. When Puppeteer or Playwright drives Chromium, it speaks the Chrome DevTools Protocol. Enabling the runtime domain — which the automation libraries do by default — leaves a side-channel that anti-bot scripts can probe from inside the page. This signal is emitted by the protocol layer, before any of your page JavaScript runs. A stealth plugin runs as page JavaScript. By definition it is too late and in the wrong place to patch it. This is the architectural ceiling: no amount of clever monkey-patching can fix a leak that lives below the layer the plugin operates in.

2. The evasion list went stale — and stale patches become tells
Anti-bot vendors stopped scoring individual leaks years ago. Cloudflare Bot Management, DataDome, and PerimeterX now score combinations of signals. That flips the economics of a stealth plugin: a patch that makes one value look "correct" in isolation can create a combination no real browser ever produces — a too-perfect navigator sitting next to a default container timezone, say. The detector does not need to know your specific patch; it just learns that "this exact bundle of values = automation." An unmaintained plugin freezes its bundle in place while detectors keep moving. By 2026, puppeteer-extra-plugin-stealth has gone without a substantive evasion update long enough that several of its patches are now signals in their own right.
3. You only patched the browser — and the browser is one third of the problem
This is the big one, and it is why the plugin gets blamed for failures that are not its fault. Modern anti-bot runs three checks, roughly in this order:

If you deploy a stealth-patched browser on a datacenter IP, you fail at step one and never even reach the layer your plugin was working on. This is the most common production failure we see, and it looks exactly like "the stealth plugin stopped working" when it is really "the IP was never going to pass."
4. Your dev environment lied to you
Stealth plugins pass on your laptop for reasons that evaporate in production:
- IP: your home connection is a real residential IP with good reputation. The cloud VM you deploy to is a datacenter IP on a flagged ASN.
- Headless: you test headful (a visible window) and deploy headless. Even the new headless mode renders and reports differently, and several fingerprint surfaces diverge.
- Fingerprint entropy: your laptop has a real GPU, dozens of fonts, a true timezone, and audio hardware. A minimal Docker image has a software renderer, a handful of fonts, UTC, and no audio device. That stripped-down profile is itself anomalous.
None of that is the plugin's job to fix, which is precisely why "it works on my machine" is the most misleading sentence in scraping.
5. Behavior is unpatchable by a plugin entirely
Bot Management increasingly scores how you act: mouse paths, dwell time, scroll cadence, the gap between page load and first interaction. A stealth plugin is a static shim — it has no notion of behavior. A scraper that loads a page and fires a click 40ms later, with no mouse movement, is behaviorally a bot no matter how clean its navigator object looks.
Diagnose it in two minutes
Before you rewrite anything, find out which layer is actually leaking. Run your production browser config (not your laptop) against a fingerprint endpoint:
- bot.sannysoft.com — catches the obvious WebDriver/runtime leaks. If you fail here, your browser layer is broken.
- iphey.com — richer report including canvas and audio hashes.
- creepjs — the strictest. A non-zero trust score puts you in the top percentile.
Then check the other two layers separately: curl -s https://api.ipify.org through your scraper's proxy and look up the ASN — if it says your cloud provider, that is your problem, not the plugin. If the fingerprint tests pass but real targets still block you, you are failing on IP or behavior.
What to use instead
Replace the stealth plugin with a maintained, protocol-patched browser, then add back the layers it never covered.
- Patchright — a drop-in Playwright/Puppeteer-style fork with the CDP
Runtime.enableleak patched at the protocol layer. This is the closest thing to a direct successor to the stealth plugin for Chromium. - Camoufox — a Firefox fork with C++-level fingerprint patches. Best when the target fingerprints Chrome variants aggressively.
- A residential proxy — non-negotiable for the IP layer. See how to set up Playwright with a residential proxy and which providers actually pass Cloudflare.
- Behavioral pacing — humanlike timing, mouse movement, and scroll before interaction.
The catch is that this is now four moving parts — a patched browser, a proxy contract, a CAPTCHA path, and behavioral code — each with its own bill and its own way of breaking. That stitching is itself a maintenance burden, and it is the reason managed stacks exist.
The managed alternative
Human Browser ships all four layers behind one endpoint: a protocol-patched engine (both Chromium and Firefox), residential IPs, CAPTCHA handling, and an agent loop with behavioral pacing built in. It is the "maintained alternative" answer to the stealth-plugin problem — instead of pinning a dead npm package and bolting three vendors onto it, you make one API call and the evasion stack is kept current for you. Pay-as-you-go, try it for $1, then top up from $5. Try Human Browser.
Migration: from stealth plugin to a patched browser
The good news: Patchright keeps the Playwright API, so for most scripts the migration is a couple of lines. Before:
// Before — playwright-extra + stealth plugin
const { chromium } = require('playwright-extra');
const stealth = require('puppeteer-extra-plugin-stealth')();
chromium.use(stealth);
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage();
await page.goto('https://target-site.com');After:
// After — Patchright (CDP Runtime.enable patched) + residential proxy
const { chromium } = require('patchright');
const browser = await chromium.launch({
headless: true,
proxy: {
server: process.env.PROXY_SERVER, // residential endpoint
username: process.env.PROXY_USER,
password: process.env.PROXY_PASS,
},
});
const page = await browser.newPage();
// pace it like a human: wait, move, then act
await page.goto('https://target-site.com', { waitUntil: 'domcontentloaded' });
await page.waitForTimeout(800 + Math.floor(Math.random() * 1200));Or skip the assembly entirely and let the managed stack carry the browser, IP, and behavior:
// Managed — one call, all four layers handled
const { launchHuman } = require('@virixlabs/humanbrowser');
const { page } = await launchHuman(); // patched engine + residential IP + pacing
await page.goto('https://target-site.com', { waitUntil: 'networkidle' });
const html = await page.content();Two migration gotchas worth knowing: Patchright bundles its own patched Chromium, so do not point it at a stock Chromium binary; and any code that called the raw Runtime.enable CDP command directly will need to move to higher-level APIs, since that is exactly the leak the fork closes.
The honest summary
Stealth plugins are not a scam and they were not badly built. They are a 2020 answer to a 2026 problem. They patch a frozen list of JavaScript signals, they cannot reach the protocol-level leak that detectors now lean on, and they only ever addressed one of the three layers that decide whether you get blocked. Swap the plugin for a maintained patched browser, add a residential IP and realistic behavior, and you have a stack that survives the trip from your laptop to production — or hand the whole stack to a managed service and stop maintaining it yourself.
Human Browser — the maintained stealth stack
Protocol-patched Chromium & Firefox + residential IPs + CAPTCHA handling + behavioral pacing, behind one API. Pay-as-you-go: $0.10/hr browser time + $0.02 per agent step (AI included) + $4/GB residential proxy, no subscription, try it for $1, then top up from $5.
Human Browser — pay as you go, no subscription