ScreenshotNeo

BlogHow-to

How to Use Your Own Proxy with a Headless Browser API

Configure authenticated proxies in Browserless, Playwright, Puppeteer, or Docker, with scope, geography, troubleshooting, and a ScreenshotNeo alternative.

By the ScreenshotNeo team1 October 20268 min read

Direct answer: pass your proxy at the layer your headless browser API supports. Browserless accepts an externalProxyServer connection parameter; native Playwright accepts proxy on browser.newContext(); self-hosted Browserless accepts Chromium’s --proxy-server flag. URL-encode proxy credentials when they are embedded in a WebSocket URL.

The correct scope matters. A native Playwright context can have its own proxy. In Browserless CDP mode, launch-level settings are inherited by the default context, while a newly created context may not inherit them. Puppeteer can receive proxy settings through its connection or launch configuration.

1. Choose the proxy and connection model

Decision Use when Trade-off
Datacenter proxy High-volume, low-cost collection where the target accepts hosting-provider IPs Browserless documents 2 units/MB and says these IPs are easier to detect.
Residential proxy The target needs an IP that resembles a household connection Browserless documents 6 units/MB and describes residential routing as harder to detect.
Direct egress You do not need a proxy Omit the proxy setting and use the host machine’s IP.
Native Playwright You need multiple independent contexts and per-context proxy settings Use Playwright’s native connection API.
CDP You need Chromium compatibility or a Browserless CDP endpoint There is one default context with launch-level settings; context inheritance differs.

Browserless supports country targeting with proxyCountry, city targeting with proxyCity on a Scale plan with 500k or more units, sticky sessions with proxySticky=true, and locale alignment with proxyLocaleMatch. Plain REST and WebSocket requests otherwise use a random proxy node by default. See the Browserless proxy documentation for the current parameter set.

2. Browserless hosted API with an authenticated proxy

Browserless documents externalProxyServer as an external proxy URL in the form http(s)://[username:password@]host:port. Encode reserved characters in the complete value before adding it to the WebSocket URL.

wss://production-sfo.browserless.io?token=YOUR_TOKEN&externalProxyServer=http%3A%2F%2Fuser%3Apass%40proxy.example.com%3A8080

For example, the unencoded proxy value is http://user:pass@proxy.example.com:8080. If the password contains @, :, /, or another reserved character, percent-encode it. Browserless states that third-party proxy use requires a paid cloud-unit plan; free plans reject it with HTTP 401.

Verify the egress IP before opening the target

import { chromium } from "playwright-core";

const proxy = encodeURIComponent("http://user:password@proxy.example.com:8080");
const browser = await chromium.connectOverCDP(
  `wss://production-sfo.browserless.io?token=${process.env.BROWSERLESS_TOKEN}&externalProxyServer=${proxy}`
);
const context = browser.contexts()[0];
const page = await context.newPage();
await page.goto("https://ipinfo.io/ip", { waitUntil: "domcontentloaded" });
console.log(await page.textContent("body"));
await browser.close();

Compare the printed address with the proxy provider’s expected egress address, then test the destination site. An IP check only proves routing; it does not prove that the target accepts the IP.

3. Playwright: set a proxy on a browser context

With a native Playwright connection, set the proxy when creating the context. This keeps proxy credentials out of page code and lets separate contexts use different proxies.

import { chromium } from "playwright-core";

const browser = await chromium.connect("wss://production-sfo.browserless.io?token=YOUR_TOKEN");
const context = await browser.newContext({
  proxy: {
    server: "http://proxy.example.com:8080",
    username: "username",
    password: "password"
  }
});
const page = await context.newPage();
await page.goto("https://example.com", { waitUntil: "networkidle" });
console.log(await page.title());
await browser.close();

This is the documented Browserless pattern for context-level credentials. Browserless’s feature matrix says browser.newContext({ proxy }) is supported for native Playwright connections, but not in the default CDP context; query-parameter proxying works in both modes. In CDP mode, use browser.contexts()[0] when you need launch-level proxy inheritance.

Multiple proxies in one process

const us = await browser.newContext({
  proxy: { server: "http://us-proxy.example:8080", username: "u", password: "p" }
});
const de = await browser.newContext({
  proxy: { server: "http://de-proxy.example:8080", username: "u", password: "p" }
});

const [usPage, dePage] = await Promise.all([us.newPage(), de.newPage()]);
await Promise.all([
  usPage.goto("https://example.com"),
  dePage.goto("https://example.com")
]);

Close contexts when each job finishes. Do not assume changing a proxy on an existing context will affect already-open connections.

4. Puppeteer and Browserless

For hosted Browserless, use the WebSocket URL and either the external proxy query parameter or the launch configuration supported by your endpoint.

import puppeteer from "puppeteer-core";

const browser = await puppeteer.connect({
  browserWSEndpoint:
    "wss://production-sfo.browserless.io?token=YOUR_TOKEN&externalProxyServer=http%3A%2F%2Fuser%3Apass%40proxy.example.com%3A8080"
});
const page = await browser.newPage();
await page.goto("https://example.com", { waitUntil: "networkidle2" });
console.log(await page.title());
await browser.close();

Puppeteer’s official configuration guide lists HTTP_PROXY, HTTPS_PROXY, and NO_PROXY for downloading and running the browser. Those variables do not automatically configure puppeteer-core; review the distinction before relying on environment configuration.

5. Self-hosted Browserless Docker

Open-source Browserless does not bundle a proxy server. Supply your own proxy per session with Chromium’s --proxy-server flag in the WebSocket URL.

const browser = await puppeteer.connect({
  browserWSEndpoint:
    "ws://localhost:3000?token=YOUR_TOKEN&--proxy-server=http://proxy.example.com:8080"
});
const page = await browser.newPage();
await page.goto("https://example.com");
await browser.close();

The same flag works for Playwright over CDP:

import { chromium } from "playwright-core";
const browser = await chromium.connectOverCDP(
  "ws://localhost:3000?token=YOUR_TOKEN&--proxy-server=http://proxy.example.com:8080"
);
const page = await browser.contexts()[0].newPage();
await page.goto("https://example.com");

Custom Chromium arguments are powerful but risky. Playwright warns that unsupported arguments can break browser functionality, so add one flag at a time and remove it if navigation, downloads, or isolation changes unexpectedly.

6. Credentials, geography, and session behavior

  • Encode credentials: Prefer separate username and password fields where the API supports them. When credentials must be in a URL, percent-encode reserved characters.
  • Keep secrets out of source: Read tokens and proxy passwords from environment variables or a secret manager. Never log the full WebSocket URL.
  • Country and city: Use ISO country codes for proxyCountry. City targeting requires Browserless Scale with at least 500k units.
  • Sticky IP: Add proxySticky=true when a workflow needs the same address across requests. “Sticky” means Browserless attempts to retain the node; it is not a guarantee that an IP will remain available.
  • Locale: proxyLocaleMatch can align browser language and formatting with the proxy location, reducing obvious region mismatches.
  • Authentication type: Confirm whether your provider offers HTTP(S) proxy authentication. A SOCKS endpoint cannot be substituted for an HTTP endpoint unless the browser API explicitly supports it.

7. Troubleshooting

Symptom Likely cause Fix
401 from Browserless Third-party proxying on a free cloud-unit plan Use a paid cloud-unit plan or remove the external proxy option.
Proxy authentication failed Wrong credentials, unsupported auth method, or an unencoded reserved character Test the proxy independently, encode the URL, and verify the provider’s scheme and port.
Requests still show the host IP Proxy was applied at the wrong scope Use browser.contexts()[0] for inherited CDP launch settings, or set proxy on a native Playwright context.
New context bypasses the proxy CDP context inheritance behavior Use the default context or pass the proxy through the Browserless query parameter.
Navigation hangs Dead proxy node, blocked destination, DNS failure, or overly short timeout Check the egress IP, test a known page, rotate the node, and set a realistic navigation timeout.
Only some assets fail The proxy blocks domains, resource types, or CONNECT tunneling Inspect failed requests and allow the required destinations in the proxy policy.
Puppeteer environment variables have no effect The script uses puppeteer-core Configure the connection or launch options explicitly; Puppeteer configuration files and environment variables are ignored by puppeteer-core.
Browser behaves differently after adding a flag Unsupported Chromium argument Remove custom arguments and reintroduce only the documented proxy flag.

8. Reliability, performance, and cost

Reliability checklist

  • Check proxy reachability before expensive pages.
  • Use bounded connect, navigation, and overall job timeouts.
  • Retry transient connection failures with a new proxy node; do not blindly retry authentication failures.
  • Record the effective egress IP, target URL, proxy region, and browser error category without storing credentials.
  • Use sticky routing only when session continuity matters; otherwise random nodes can distribute failures.

Performance considerations

Proxy routing adds connection setup and network latency. Residential routes usually cost more and can be slower than datacenter routes. Reuse a browser connection where safe, but create isolated contexts for separate identities. Avoid loading unnecessary resources when your API supports request filtering, and wait only for the readiness condition your page requires instead of always waiting for network idle.

Cost considerations

Browserless documents proxy routing at 2 units/MB for datacenter and 6 units/MB for residential. Large images, videos, and repeated retries increase transferred bytes. Measure the complete workflow, including failed attempts, before choosing a proxy class or geography.

9. Or skip the browser setup

If your goal is a clean screenshot rather than browser automation, ScreenshotNeo provides a one-call screenshot API. The API accepts a URL and returns PNG, JPEG, WebP, or PDF; its options include custom headers, cookies, user agents, Authorization, timezone, geolocation, waits, request blocking, caching, signed links, asynchronous jobs, bulk capture, and HTML/CSS rendering. See the ScreenshotNeo API documentation.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
const data = Buffer.from(await res.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', data));

Cookie banners, newsletter popups, and chat widgets are removed before the shot. Bot checks, blank pages, timeouts, failed loads, and cache hits are never billed, and response headers identify the page verdict and billing status. An MCP server lets Claude, Cursor, and other MCP clients call take_screenshot, get_page_info, and capture_pdf. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000.

Create a free ScreenshotNeo account and start with 1,000 screenshots a month at no charge.

10. FAQ

Can I use different proxies for different pages?

Yes with native Playwright contexts: create one context per proxy. In CDP mode, use query parameters or the default context for launch-level settings.

Should I choose residential or datacenter routing?

Choose datacenter for lower documented Browserless unit cost and residential when the target is more sensitive to hosting-provider IPs.

Does a proxy hide browser fingerprints?

No. A proxy changes network egress; it does not automatically change browser fingerprinting signals, cookies, JavaScript behavior, or CAPTCHA outcomes.

Why does a proxy work for navigation but fail for assets?

The proxy may restrict CONNECT destinations or specific resource types. Inspect failed requests and verify that every required host is allowed.