Puppeteer with Residential Proxy in 2026: Complete Setup Guide
If you're using Puppeteer for scraping or browser automation and getting blocked by Cloudflare, DataDome, or PerimeterX — a residential proxy is the most important fix you can make. This guide covers exactly how to configure one, with working 2026 code examples, common mistakes to avoid, and an honest comparison of Puppeteer vs Playwright for anti-bot bypass.

Why You're Getting Blocked (Even with a Proxy)
Most developers reach for a proxy when they start getting blocked. That's the right instinct, but many miss a critical detail: bot detection in 2026 runs two separate checks, not one.
- Check 1 — IP reputation: Is this IP from a datacenter (AWS, Hetzner, DigitalOcean)? If yes, block immediately. This fires before any JavaScript runs.
- Check 2 — Browser fingerprint: Does the browser's TLS handshake, Canvas output, WebGL renderer, and font list look like a real user? Headless Chromium fails this.
A datacenter proxy only changes your IP but keeps it in the "data center range" — Cloudflare already knows those subnets. A residential proxy routes your traffic through a real home internet connection with an ISP-assigned IP, which passes Check 1. You still need to handle Check 2 separately.
The common mistake: Using a premium datacenter proxy (Luminati, Oxylabs enterprise, Bright Data shared) and wondering why Cloudflare still blocks you. Datacenter ≠ residential. You need a genuinely ISP-assigned IP.

How to Add a Residential Proxy to Puppeteer
Puppeteer passes proxy settings via the --proxy-server launch argument. For authenticated proxies (all residential providers require credentials), you also need to call page.authenticate().
const puppeteer = require('puppeteer');
const browser = await puppeteer.launch({
headless: 'new',
args: [
// Point to your residential proxy
'--proxy-server=http://ro.yourprovider.com:10001',
// Required for modern Chromium
'--no-sandbox',
'--disable-setuid-sandbox',
]
});
const page = await browser.newPage();
// Residential proxies require authentication
await page.authenticate({
username: 'your_username',
password: 'your_password',
});
await page.goto('https://example.com');
console.log(await page.title());
await browser.close();page.authenticate() applies per-page: If you open multiple pages/tabs, call authenticate() on each one. It doesn't carry over from the browser context.
Country Selection with Residential Proxies
Different residential providers handle country selection differently. The two most common patterns:
Pattern 1: Separate hostname per country (Decodo, Smartproxy)
// Romania
'--proxy-server=http://ro.decodo.com:10001'
// United States
'--proxy-server=http://us.decodo.com:10001'
// Japan
'--proxy-server=http://jp.decodo.com:10001'Pattern 2: Session string in username (most providers)
// US residential, sticky session
page.authenticate({
username: 'user-country-us-session-abc123',
password: 'your_password',
});The Fingerprinting Problem (Why a Proxy Isn't Enough)
Even with a residential IP, headless Puppeteer fails bot detection checks because the browser fingerprint is obviously robotic:
- User-Agent:
HeadlessChrome/120.0— instantly flagged by most WAFs - Canvas fingerprint: Headless Chrome produces a different canvas output than GUI Chrome
- WebGL renderer: Returns
SwiftShaderin headless mode — a known headless indicator - Timezone mismatch: Your proxy is in Romania but the browser timezone says UTC — a red flag
- Missing browser APIs:
navigator.pluginsis empty in headless mode
You need to patch all of these. The puppeteer-extra + puppeteer-extra-plugin-stealth combination helps, but it's increasingly ineffective against Cloudflare's 2026 detection algorithms — partly because it only patches the fingerprint, not the IP.
const puppeteer = require('puppeteer-extra');
const StealthPlugin = require('puppeteer-extra-plugin-stealth');
puppeteer.use(StealthPlugin());
const browser = await puppeteer.launch({
headless: 'new',
args: ['--proxy-server=http://ro.yourprovider.com:10001']
});
// Still need to manually set locale/timezone to match proxy country
const page = await browser.newPage();
await page.authenticate({ username: 'user', password: 'pass' });
await page.setExtraHTTPHeaders({ 'Accept-Language': 'ro-RO,ro;q=0.9' });
// Timezone still requires CDP: Emulation.setTimezoneOverride
Puppeteer's proxy auth model dates from 2018. It shows.
Puppeteer vs Playwright for Residential Proxy in 2026
If you're starting a new project or willing to migrate, Playwright handles residential proxies much more cleanly than Puppeteer. Here's the comparison:
| Feature | Puppeteer | Playwright |
|---|---|---|
| Proxy config | --proxy-server arg + page.authenticate() | Built-in proxy option in context (no workaround) |
| Multi-page proxy auth | Must call authenticate() on each page | Set once in context, applies everywhere |
| Browser engines | Chrome/Chromium only | Chromium, Firefox, WebKit |
| Stealth plugins | puppeteer-extra-plugin-stealth (less maintained) | playwright-extra stealth + Human Browser |
| Timezone/locale override | CDP only, complex | Built-in: timezoneId, locale in context |
| 2026 maintenance activity | Stable but slower feature pace | Active Microsoft-backed development |
For pure Playwright with residential proxy, see our guide: Playwright with Residential Proxy: Complete Setup Guide 2026. And if your specific target is Cloudflare, the dedicated walkthrough is how to bypass Cloudflare with Playwright.
The Easiest Solution: Human Browser (Playwright Drop-in)
If you're tired of wiring together puppeteer-extra, stealth plugins, proxy credentials, timezone patches, and User-Agent strings — Human Browser packages all of it into a single function call. It's Playwright-based but the API is identical to what Puppeteer users expect:
import { launchHuman } from '@virixlabs/humanbrowser';
// Residential IP (Romania by default; needs a Human Browser balance, try it for $1)
// iPhone 15 Pro fingerprint (Canvas, WebGL, fonts all consistent)
// Human-like behavior (Bezier mouse, 60-220ms typing, scroll patterns)
const { browser, page } = await launchHuman({ country: 'us' });
await page.goto('https://cloudflare-protected-site.com');
console.log(await page.title());
await browser.close();What it handles automatically:
- Fetches residential proxy credentials for your token (try it for $1, then top up from $5)
- Matches timezone and locale to the proxy country
- Spoofs Canvas, WebGL, fonts, and TLS fingerprint (consistent iPhone 15 Pro profile)
- Adds human-like behavior — Bezier mouse curves, natural typing speed variation
Stop Fighting Proxies — Just Use Human Browser. 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. Works on Cloudflare, DataDome, PerimeterX. Drop-in for Playwright. Human Browser — pay as you go, no subscription
Debugging Proxy Issues in Puppeteer
Common problems and fixes:
ERR_TUNNEL_CONNECTION_FAILED
The proxy server address or port is wrong, or the proxy requires a different connection protocol (SOCKS5 vs HTTP). Check the exact host:port from your provider dashboard.
407 Proxy Authentication Required
You're not calling page.authenticate(), or the credentials are wrong. Note: authenticate() must be called before goto().
Still getting Cloudflare 403 with residential proxy
Your IP is residential but the fingerprint is still headless. Add puppeteer-extra-plugin-stealth and manually override timezone/locale to match the proxy country. Or switch to Human Browser which handles all of this automatically.
proxy.authenticate() not working on new pages
This is a known Puppeteer limitation — proxy authentication must be set per-page. Wrap your page creation in a helper function that calls authenticate() immediately after newPage().
FAQ
How do I add a proxy to Puppeteer?
Pass --proxy-server=http://host:port to puppeteer.launch({ args: [...] }). For authenticated proxies, call page.authenticate({ username, password }) after creating each page.
Does Puppeteer support residential proxies?
Yes — Puppeteer supports any HTTP, HTTPS, or SOCKS5 proxy including residential. You need credentials from a residential provider (Decodo, Bright Data, Webshare) and must pass them via page.authenticate().
Why is Puppeteer still getting blocked even with a residential proxy?
Bot detection in 2026 checks both IP reputation and browser fingerprint. A residential proxy fixes the IP check but headless Chromium still fails on Canvas, WebGL, fonts, and TLS fingerprint. Use puppeteer-extra-plugin-stealth or switch to Human Browser which patches everything automatically.
Should I use Puppeteer or Playwright with a residential proxy?
Playwright is the better choice in 2026. It has built-in proxy support per-context (no need to call authenticate() on each page), supports multiple browser engines, and the stealth ecosystem is more actively maintained.
What's the best free residential proxy for Puppeteer testing?
Human Browser lets you try it for $1 — about 4 typical tasks, enough to verify your setup works. After that, top up from $5 — billing is pay-as-you-go, no subscription.