Puppeteer Proxy Setup Guide
A practical Puppeteer proxy setup guide for authentication, IP verification, rotation, sticky sessions, timeouts, and browser automation reliability.
Puppeteer is one of the most popular tools for browser automation in Node.js. Developers use it to open pages, render JavaScript, take screenshots, test user flows, scrape dynamic websites, generate PDFs, and monitor web apps.
But once Puppeteer automation moves beyond a few test pages, one issue appears quickly: network identity.
If every browser session comes from the same server IP, websites may throttle requests, return 403 errors, show CAPTCHA pages, block the IP, or serve incomplete content. That is especially common when running repeated checks, scraping public pages, testing regional content, or automating browser workflows at scale.
A proxy helps by routing Puppeteer traffic through another IP address.
Instead of the target website seeing your server IP, it sees the proxy IP. This gives developers more control over location, session separation, and request distribution.
But proxy setup in Puppeteer has a few details that matter. You need the right launch argument, the right authentication method, and the right rotation strategy.
Why Use a Proxy With Puppeteer?
Developers use proxies with Puppeteer for several practical reasons.
The first is IP distribution. A single server IP can become overused if it sends too many requests to the same site. Proxies help spread traffic across different IPs.
The second is location testing. Websites may show different content, prices, search results, ads, or compliance banners depending on the visitor’s region. A proxy lets your Puppeteer script test from specific locations.
The third is session separation. Different browser sessions can use different proxy IPs, reducing overlap between unrelated workflows.
The fourth is reliability. If one IP is blocked or slow, your system can move the task to another proxy instead of failing the entire job.
For serious automation, a random free list is not enough. A working proxy list should be stable, authenticated, and suitable for repeatable workflows, not just temporarily alive.
Basic Puppeteer Proxy Setup
The simplest Puppeteer proxy setup uses the --proxy-server launch argument.
import puppeteer from "puppeteer";
const browser = await puppeteer.launch({
headless: true,
args: [
"--proxy-server=http://proxy.example.com:8080"
]
});
const page = await browser.newPage();
await page.goto("https://example.com", {
waitUntil: "domcontentloaded"
});
console.log(await page.title());
await browser.close();This routes browser traffic through the proxy server.
The proxy URL usually includes protocol, host, and port:
http://host:port
https://host:port
socks5://host:portUse the format your proxy provider gives you.
Puppeteer Proxy Authentication
Many proxies require a username and password.
Do not put proxy credentials directly inside the --proxy-server argument unless you know the setup supports it cleanly. A common Puppeteer pattern is to launch the browser with the proxy server, then authenticate on the page.
import puppeteer from "puppeteer";
const browser = await puppeteer.launch({
headless: true,
args: [
"--proxy-server=http://proxy.example.com:8080"
]
});
const page = await browser.newPage();
await page.authenticate({
username: "proxy_user",
password: "proxy_pass"
});
await page.goto("https://httpbin.org/ip", {
waitUntil: "domcontentloaded"
});
console.log(await page.content());
await browser.close();The page.authenticate() method provides credentials when the proxy asks for them.
If authentication fails, the proxy may return a 407 Proxy Authentication Required error. That usually means the username, password, host, port, or auth method is wrong.
Use Environment Variables for Credentials
Hardcoding credentials is a bad practice.
Use environment variables instead:
PROXY_SERVER=http://proxy.example.com:8080
PROXY_USERNAME=myuser
PROXY_PASSWORD=mypassThen read them in your script:
import puppeteer from "puppeteer";
const proxyServer = process.env.PROXY_SERVER;
const proxyUsername = process.env.PROXY_USERNAME;
const proxyPassword = process.env.PROXY_PASSWORD;
if (!proxyServer) {
throw new Error("Missing PROXY_SERVER");
}
const browser = await puppeteer.launch({
headless: true,
args: [`--proxy-server=${proxyServer}`]
});
const page = await browser.newPage();
if (proxyUsername && proxyPassword) {
await page.authenticate({
username: proxyUsername,
password: proxyPassword
});
}
await page.goto("https://httpbin.org/ip", {
waitUntil: "domcontentloaded"
});
console.log(await page.textContent?.("body"));
await browser.close();This keeps secrets out of your source code and makes deployment easier.
You can use different proxy settings for local development, staging, and production without editing the script.
Verify the Proxy IP
Before running your real workflow, test whether Puppeteer is actually using the proxy.
Use an IP-check endpoint:
await page.goto("https://httpbin.org/ip", {
waitUntil: "domcontentloaded"
});
const body = await page.evaluate(() => document.body.innerText);
console.log(body);If the returned IP matches the proxy, the setup works.
If it shows your server IP, the proxy was not applied.
If the page fails, check the proxy protocol, host, port, credentials, IP allowlisting, firewall settings, and whether the proxy supports browser traffic.
Get ProxiesThatWork’s stories in your inbox
Join Medium for free to get updates from this writer.
Skipping this step wastes time. Proxy failures can look like normal browser automation problems if you do not verify routing first.
Rotating Proxies in Puppeteer
For larger workflows, you may need more than one proxy.
A basic rotation pattern assigns one proxy per browser session:
import puppeteer from "puppeteer";
const proxies = [
{
server: "http://proxy1.example.com:8000",
username: "user",
password: "pass"
},
{
server: "http://proxy2.example.com:8000",
username: "user",
password: "pass"
},
{
server: "http://proxy3.example.com:8000",
username: "user",
password: "pass"
}
];
async function runWithProxy(url, proxy) {
const browser = await puppeteer.launch({
headless: true,
args: [`--proxy-server=${proxy.server}`]
});
try {
const page = await browser.newPage();
await page.authenticate({
username: proxy.username,
password: proxy.password
});
await page.goto(url, {
waitUntil: "domcontentloaded",
timeout: 30000
});
return await page.title();
} finally {
await browser.close();
}
}
for (const proxy of proxies) {
const title = await runWithProxy("https://example.com", proxy);
console.log(title);
}This approach is simple and predictable.
Each browser session uses one proxy. That makes debugging easier because you can log which proxy was used for each task.
Sticky Sessions vs Constant Rotation
Do not rotate proxies blindly.
For Puppeteer, session consistency matters. Browser sessions include cookies, local storage, cache, login state, and navigation history. If the IP changes constantly during a session, websites may treat the behavior as suspicious.
Use sticky sessions for logins, dashboards, carts, multi-step forms, account pages, and region-specific flows.
Use rotating proxies for independent public pages, search result checks, product pages, and one-off screenshots.
The practical rule is simple: assign one proxy per session or task. Avoid changing IPs in the middle of a workflow unless there is a clear reason.
Handling Common Proxy Errors
A Puppeteer proxy setup can fail in several ways.
A 407 Proxy Authentication Required error usually means the proxy credentials are wrong or not being passed correctly.
A timeout may mean the proxy is slow, dead, overloaded, or unable to reach the target website.
A 403 Forbidden response may mean the target site rejected the IP, browser fingerprint, headers, cookies, or behavior.
A 429 Too Many Requests response means the workflow is sending too much traffic or reusing IPs too aggressively.
A blank page, CAPTCHA, or challenge page may still return a 200 status code, so do not rely only on HTTP status.
For serious workflows, save screenshots, final URLs, status codes, proxy IDs, and timing data for failed pages. Debugging without logs is guesswork.
Avoiding Blocks With Puppeteer
Proxies help with IP-based blocks, but they do not make automation invisible.
Websites can also evaluate browser fingerprinting, request timing, headers, JavaScript behavior, mouse events, cookies, session history, and repeated navigation patterns.
If your Puppeteer script opens pages too quickly, repeats the same action sequence, ignores normal wait times, and sends too much traffic, proxies alone will not fix the problem.
For a broader explanation, this guide on using proxies with Puppeteer and Playwright covers how developers approach proxy-based browser automation across both tools.
The best results come from combining proxy routing with reasonable pacing, session control, and clean error handling.
Best Practices
Test the proxy with an IP-check endpoint before using it on a target site.
Use environment variables for proxy credentials.
Assign one proxy per browser session.
Use sticky sessions for login or cart-based workflows.
Rotate only between independent tasks.
Set navigation timeouts.
Limit concurrency by domain.
Track proxy success rates.
Back off after 429 responses.
Investigate 403 responses instead of retrying endlessly.
Save screenshots of blocked or suspicious pages.
Avoid free public proxies for production workflows.
These practices improve reliability and make failures easier to diagnose.
Final Takeaway
Puppeteer proxy setup is straightforward once you know the pattern: launch the browser with --proxy-server, authenticate with page.authenticate() when needed, verify the outgoing IP, then add rotation, sticky sessions, timeouts, and logging.
The proxy gets your browser traffic onto a different route. Good automation design keeps that traffic stable, consistent, and easier to manage.









