Good proxies, still blocked?
A common story: you switch your Puppeteer scraper from datacenter to premium residential proxies, and you still see CAPTCHAs and "Access denied". That's because modern bot protection scores much more than the IP. It inspects the browser itself, and default headless Chrome has many tell-tale signs.
This guide covers how to reduce those signs and how to align the browser with the proxy. It also explains the limits: stealth helps, but it's not a guarantee.
What gives Puppeteer away
navigator.webdriverset totruein automated Chrome.- Headless indicators. Older headless modes exposed differences in user agent ("HeadlessChrome"), plugins, permissions, rendering and APIs. Chrome's newer headless mode is much closer to headful Chrome, but differences remain.
- Default viewport of 800x600, uncommon on real devices.
- Missing or inconsistent properties such as languages, plugins, WebGL vendor.
- CDP side effects that sophisticated scripts can probe.
- Mismatches with the proxy: timezone, language and geolocation that don't fit the IP.
- Behaviour: no mouse movement, instant form filling, perfectly regular timing.
- TLS/HTTP2 fingerprint (Chrome's is genuine, which is an advantage of real browsers over HTTP libraries).
See browser fingerprinting and proxies.
puppeteer-extra and the stealth plugin
puppeteer-extra wraps Puppeteer with a plugin system. The stealth plugin applies a set of evasions for common detection checks:
import puppeteer from 'puppeteer-extra';
import StealthPlugin from 'puppeteer-extra-plugin-stealth';
puppeteer.use(StealthPlugin());
const browser = await puppeteer.launch({
headless: true,
args: ['--proxy-server=http://gate.example.com:7777'],
});
const page = await browser.newPage();
await page.authenticate({ username: 'customer-USER-country-us-session-x1', password: 'PASS' });
await page.goto('https://example.com');It helps against basic and intermediate checks. Against top-tier bot protection, which is updated continuously, public evasions are often detected. Be aware that the plugin itself is no longer actively developed: its latest release (2.11.2) dates from March 2023 and its repository hasn't changed since mid-2024, so treat it as a baseline rather than a current fix, and test regularly.
Align the browser with the proxy
If your proxy exits in Chicago, your browser should look like it's in Chicago:
await page.emulateTimezone('America/Chicago');
await page.setExtraHTTPHeaders({ 'Accept-Language': 'en-US,en;q=0.9' });
await page.setViewport({ width: 1366, height: 768 });You can also launch with a matching language flag, for example --lang=en-US. For geolocation API checks:
const ctx = browser.defaultBrowserContext();
await ctx.overridePermissions('https://example.com', ['geolocation']);
await page.setGeolocation({ latitude: 41.8781, longitude: -87.6298 });Look up the proxy's location first (request an IP-info endpoint through it) and set these values dynamically. See geo-targeted proxies.
Prevent WebRTC leaks
WebRTC can reveal your real IP even through a proxy. Chromium flags such as --force-webrtc-ip-handling-policy=disable_non_proxied_udp reduce the risk. See WebRTC leaks and proxies.
Behave more like a person
- Add small random delays between actions.
- Move the mouse and scroll before clicking.
- Type with per-key delays (
page.type(selector, text, { delay: 50 + Math.random() * 100 })). - Don't open dozens of pages per second from one IP.
Proxy tip: Want to skip proxy management on some targets? Geonode offers both raw proxies and a Scraper API that returns HTML or Markdown with optional JavaScript rendering. We compare the two approaches in using Geonode with Puppeteer.
Headful mode and real Chrome
Some teams run headful Chrome (with a virtual display on servers) or use the user's installed Chrome via executablePath to reduce differences. This costs more resources but can raise success rates on strict sites.
Know when to stop fighting
If a target is protected by aggressive enterprise bot management and you're spending days on evasions, compare the cost with a web unblocker or scraping API.
A practical hardening checklist
- [ ] Recent Puppeteer and Chrome versions
- [ ] Stealth plugin (or equivalent evasions), kept updated
- [ ] Realistic viewport and device scale factor
- [ ] Timezone and language matched to the proxy exit
- [ ] WebRTC restricted to proxied routes
- [ ] Human-like pacing and interaction where relevant
- [ ] Sticky session per browser context
- [ ] Cookies persisted per identity when appropriate
- [ ] Block detection and rotation on challenge
Testing your setup
Use public browser fingerprint and bot-detection test pages through your proxy to see what signals your setup exposes. Compare results with a normal Chrome browser on the same machine. Differences highlight what to fix. Re-test after updates; Chrome and evasion libraries change often.
Matching the proxy type to stealth effort
| Target strictness | Proxy | Stealth effort |
|---|---|---|
| Lenient | Datacenter | Minimal |
| Moderate | Residential | Stealth plugin + locale matching |
| Strict | Residential or mobile | Full hardening, headful mode, human-like behaviour |
| Very strict | Unblocker or API | Outsourced |
See Puppeteer proxies and headless browser proxies.
Beyond the stealth plugin
With the stealth plugin effectively frozen, teams that need more than baseline evasion usually move to one of these approaches:
- Real Chrome, headful. Launch the installed Chrome (
channel: 'chrome') in headful mode, on a virtual display on Linux servers. It removes a whole class of headless differences. - New headless mode. Modern headless Chrome is the same browser as headful Chrome, which closes many of the gaps the stealth plugin was written for. See headless browser proxies.
- Antidetect browsers with automation APIs, which manage fingerprints per profile and pair each profile with its own proxy. See antidetect browser proxies.
- Patched automation stacks. Community forks and patches target leaks that come from the DevTools protocol itself, which no page-level JavaScript patch can fully hide. Check how actively each one is maintained before depending on it.
- Not using a browser at all. For many targets, an HTTP client with a browser-like TLS fingerprint is cheaper and harder to tell apart than a patched browser.
Whatever you choose, test regularly against the protection your targets actually use, keep the browser's timezone, language and locale aligned with the proxy, and accept that some sites don't want automated visitors. For the signals involved, see browser fingerprinting and proxies; for when to stop, see ethical web scraping.
Key takeaways
- Residential proxies fix IP signals; stealth techniques fix browser signals. Protected sites usually require both.
puppeteer-extra-plugin-stealthdefeats many common checks but not top-tier bot management.- Match timezone, language and geolocation to the proxy exit, and restrict WebRTC.
- Human-like pacing and interaction help on interactive pages.
- When evasion work becomes expensive, compare with unblockers or scraping APIs. See web unblocker proxies.
FAQ
Does the stealth plugin make Puppeteer undetectable?
No. It defeats many common checks but not all, and bot protection vendors update continuously.
Do I still need residential proxies with stealth?
On protected sites, yes. Stealth fixes browser signals; proxies fix IP signals. You usually need both.
Is Playwright better at stealth than Puppeteer?
Both drive real browser engines and face similar detection. See Playwright proxies.
Editorial note: Proxy providers change their pools, features and prices often, so treat any figures here as typical ranges at the time of writing and confirm current terms with the provider before you buy.