> For the complete documentation index, see [llms.txt](/llms.txt)

# Proxies

# Proxies

This page covers routing browser traffic through Browserless's [built-in residential and datacenter proxies](#built-in-proxies). It also covers connecting [your own third-party proxy](#third-party-proxies) with Puppeteer or Playwright.

- A Browserless API token from your [account dashboard](https://browserless.io/account/)
- Puppeteer or Playwright installed locally

## Built-in Proxies

Browserless offers two built-in proxy networks for all users:

| Network | Description | Cost |
|---|---|---|
| `residential` (default) | Routes through real residential IP addresses. Harder to detect. | **6 units/MB** |
| `datacenter` | Routes through datacenter IPs. Cheaper but more easily detected. | **2 units/MB** |

Both networks support country and city targeting and sticky sessions, though the datacenter pool covers fewer countries. If a country has no datacenter coverage, requests won't exit from the requested location. Choose `datacenter` when you mainly need low-cost IP rotation, such as working around rate limits on sites without strict IP reputation checks.

To use either proxy network, adjust your code or API calls to let Browserless route the request for you. For both library connect and REST API calls, the process is the same!

> **Bot Detection**
> For strict bot detectors where browsers and a proxy aren't enough to get past, we recommend using [**BrowserQL**](/browserql/start).

### Puppeteer

The following uses our built-in residential proxy, targeting a node in the US:

```js

const TOKEN = "YOUR_API_TOKEN_HERE";

// Simply add proxy=residential and (optionally) a country
const browserWSEndpoint = `wss://production-sfo.browserless.io?token=${TOKEN}&proxy=residential&proxyCountry=us`;
const url = "https://ip-api.com/";
let browser;

try {
  browser = await puppeteer.connect({ browserWSEndpoint });
  const page = await browser.newPage();
  await page.setViewport({ width: 1920, height: 1080 });
  await page.goto(url);

  const $ipInfo = await page.$("section");
  await $ipInfo.screenshot({ path: "ip.png" });
} catch (e) {
  console.log("Error during script:", e.message);
} finally {
  browser && browser.close();
}
```

> **Sticky Sessions**
> By default, all requests will go through a random node in the proxy pool. This may not be desireable and can cause other issues. In order to keep your session "sticky" (use the same IP node), add a `proxySticky` parameter:
> 
> ```js
> "http://production-sfo.browserless.io/content?token=YOUR_API_TOKEN_HERE&proxy=residential&proxyCountry=us&proxySticky";
> ```

> **City-Level Proxying**
> You can also target a specific city within a country using the `proxyCity` parameter:
> 
> ```js
> "http://production-sfo.browserless.io/content?token=YOUR_API_TOKEN_HERE&proxy=residential&proxyCountry=us&proxyCity=chicago";
> ```
> 
> City-level proxying requires a **Scale plan** (500k+ units). Plans under 500k units will receive a `401` error.
> 
> To get a list of available cities, use the following endpoints:
> 
> - **All supported cities**: `https://production-sfo.browserless.io/proxy/cities?token=YOUR_TOKEN`
> - **Cities for a specific country**: `https://production-sfo.browserless.io/proxy/cities?country=US&token=YOUR_TOKEN`

> **Locale Matching**
> On the stealth endpoints (`/stealth`, `/chrome/stealth`, `/chromium/stealth`), you can add the `proxyLocaleMatch` parameter to automatically set the browser's language to match the proxy's geographic location. This ensures websites render content, currency, and formatting in the local language. Recommended when using `proxyCountry`.
> 
> ```js
> "wss://production-sfo.browserless.io/stealth?token=YOUR_API_TOKEN_HERE&proxy=residential&proxyCountry=de&proxyLocaleMatch=1";
> ```
> 
> For example, using `proxyCountry=br` with `proxyLocaleMatch=1` will set the browser language to Portuguese (pt-BR), causing websites like Google or Amazon to display in Portuguese.
> 
> Without this parameter, the browser defaults to English (en-US).

### Playwright

Our proxy service is also available for Playwright browsers. You only need to set the same parameters as for Puppeteer.

  
**Javascript:**

```js
import playwright from "playwright-core";

const TOKEN = "YOUR_API_TOKEN_HERE"

// Simply add proxy=residential and (optionally) a country
const pwEndpoint = `wss://production-sfo.browserless.io/chrome/playwright?token=${TOKEN}&proxy=residential&proxyCountry=us`;
const browser = await playwright.chromium.connect(pwEndpoint);

const context = await browser.newContext();
const page = await context.newPage();

await page.goto("https://ip-api.com/");
const $ipInfo = await page.$("section");
await $ipInfo.screenshot({ path: "ip-pw.png" });
await browser.close();
```
  
  
**Python:**

```python
from playwright.sync_api import sync_playwright

TOKEN = "YOUR_API_TOKEN_HERE"
pw_endpoint = f"wss://production-sfo.browserless.io/chrome/playwright?token={TOKEN}&proxy=residential&proxyCountry=us"

with sync_playwright() as p:
    # Connect to the Browserless endpoint
    browser = p.chromium.connect(pw_endpoint)
    context = browser.new_context()
    page = context.new_page()

    # Navigate to the URL and take a screenshot
    page.goto("https://ip-api.com/")
    page.screenshot(path="chrome.png")

    # Close the browser connection
    browser.close()

print("Screenshot saved as chrome.png")
```
  
  
**Java:**

```java
package org.example;

import com.microsoft.playwright.*;

import java.nio.file.Path;

public class Main {
    public static void main(String[] args) {
        String TOKEN = "YOUR_API_TOKEN_HERE";
        String PW_ENDPOINT = "wss://production-sfo.browserless.io/chrome/playwright?token="
                + TOKEN + "&proxy=residential&proxyCountry=us";

        try (Playwright playwright = Playwright.create()) {
            Browser browser = playwright.chromium().connect(PW_ENDPOINT);
            System.out.println("Connected to remote Chrome browser");

            BrowserContext context = browser.newContext();
            Page page = context.newPage();

            page.navigate("https://ip-api.com/");
            Locator ipInfo = page.locator("section.bg-gray");
            ipInfo.screenshot(new Locator.ScreenshotOptions().setPath(
                    Path.of("ip-pw.png")
            ));
            System.out.println("Screenshot saved as ip-pw.png");

            browser.close();
        }
    }
}
```
  
  
**C#:**

```csharp
using System;
using System.Threading.Tasks;
using Microsoft.Playwright;

class Program {
    static async Task Main(string[] args) {
        string TOKEN = "YOUR_API_TOKEN_HERE";
        string PW_ENDPOINT = $"wss://production-sfo.browserless.io/chrome/playwright?token={TOKEN}&proxy=residential&proxyCountry=us";

        var playwright = await Playwright.CreateAsync();
        var browser = await playwright.Chromium.ConnectAsync(PW_ENDPOINT);
        Console.WriteLine("Connected to remote Chrome browser");

        var context = await browser.NewContextAsync();
        var page = await context.NewPageAsync();

        await page.GotoAsync("https://ip-api.com/");
        var ipInfo = await page.QuerySelectorAsync("section.bg-gray");
        await ipInfo.ScreenshotAsync(new LocatorScreenshotOptions { Path = "ip-pw.png" });
        Console.WriteLine("Screenshot saved as ip-pw.png");

        await browser.CloseAsync();
    }
}
```
  

> **Sticky Sessions**
> By default, all requests will go through a random node in the proxy pool. This may not be desireable and can cause other issues. In order to keep your session "sticky" (use the same IP node), add a `proxySticky` parameter:
> 
> ```js
> "http://production-sfo.browserless.io/content?token=YOUR_API_TOKEN_HERE&proxy=residential&proxyCountry=us&proxySticky";
> ```

## Third Party Proxies

Both Browserless, and Chrome itself, support the usage of external proxies. If you want to use an external, or 3rd party proxy, there are two approaches:

1. **Using `externalProxyServer` query parameter** (recommended) - A simple query parameter that includes credentials directly in the URL.
2. **Using `--proxy-server` Chrome flag** - The traditional approach using Chrome's command-line flag with separate authentication.

### Using externalProxyServer (Recommended)

The simplest way to use an external proxy is with the `externalProxyServer` query parameter. This approach allows you to specify the proxy URL with credentials included, eliminating the need for separate authentication handling.

**Format:** `http(s)://[username:password@]host:port`

  
**Puppeteer:**

```js
import puppeteer from "puppeteer-core";

const TOKEN = "YOUR_API_TOKEN_HERE";
const proxyUrl = encodeURIComponent("http://username:password@proxy.example.com:8080");
const browserWSEndpoint = `wss://production-sfo.browserless.io?token=${TOKEN}&externalProxyServer=${proxyUrl}`;

const browser = await puppeteer.connect({ browserWSEndpoint });
const page = await browser.newPage();
await page.goto("https://ip-api.com/");
console.log(await page.content());
await browser.close();
```
  
  
**Playwright:**

```js
import playwright from "playwright-core";

const TOKEN = "YOUR_API_TOKEN_HERE";
const proxyUrl = encodeURIComponent("http://username:password@proxy.example.com:8080");
const pwEndpoint = `wss://production-sfo.browserless.io/chrome/playwright?token=${TOKEN}&externalProxyServer=${proxyUrl}`;

const browser = await playwright.chromium.connect(pwEndpoint);
const context = await browser.newContext();
const page = await context.newPage();
await page.goto("https://ip-api.com/");
console.log(await page.content());
await browser.close();
```
  

> **Remember to URL-encode the proxy URL when including it as a query parameter, especially if it contains special characters in the username or password.**
> 

### Using --proxy-server Chrome Flag

Alternatively, you can use Chrome's `--proxy-server` flag. This approach requires separate authentication handling if your proxy requires credentials.

#### Specifying the proxy

Regardless of whether or not you're using our REST API's or the puppeteer integration, you'll need to specify _where_ the proxy is. Chrome has a command-line flag to do this, and we support this in Browserless via the following query-string parameter:

```bash
?--proxy-server=https://YOUR-PROXY-SERVER-DOMAIN:PORT
```

You can set this parameter in our [live debugger](https://chrome.browserless.io/debugger) by clicking on the gear icon on the left panel and modifying the Browser URL field.

If you're using a proxy that doesn't require a password (maybe just an IP address filter), then that's it! You're free to now use this proxy going forward! Otherwise read on.

#### Using username and password

##### Method 1: page.authenticate (Puppeteer)

Most proxies will require some means of authentication. There's generally two ways you can do this in Puppeteer, and also in Browserless. The first more common method is the `page.authenticate`:

```js
await page.authenticate({
  username: 'joel',
  password: 'browserless-rocks',
});
```

Doing this will apply these parameters to your network requests going forward.

In our REST API's you can specify these fields with the following in your POST JSON body. These parameters work for the `pdf`, `content` and `screenshot` APIs:

```json
{
  "authenticate": {
    "username": "joel",
    "password": "browserless-rocks"
  }
}
```

##### Method 2: page.setExtraHTTPHeaders (Puppeteer)

The other mechanism is to use HTTP headers to send in extra authorization information. Puppeteer makes this pretty easy by allowing us to send in new HTTP headers via `page.setExtraHTTPHeaders`:

> NOTE: This is deprecated in most libraries now, so it's worth keeping in mind that using authentication methods in each library is now the standard.

```js
// Remember to base64 encode your username:password!
await page.setExtraHTTPHeaders({
  'Proxy-Authorization': 'Basic username:password',
  // OR
  Authorization: 'Basic username:password',
});
```

Refer to your libraries documentation on what the name of the headers is, and how to properly encode it.

We also allow this in our REST APIs as well, via the `setExtraHTTPHeaders` property:

```json
{
  "setExtraHTTPHeaders": {
    "Proxy-Authorization": "Basic username:password",
    // OR
    "Authorization": "Basic username:password"
  }
}
```

This will allow your REST APIs to utilize the prior provided proxy!

#### Using Proxies with Playwright

Playwright handles proxies differently than Puppeteer. Instead of using `page.authenticate()` or `setExtraHTTPHeaders()`, Playwright allows you to specify proxy settings directly at the context level, which means all pages created from that context will use the specified proxy.

##### Method: browser.newContext() with proxy option (Playwright)

When using Playwright with Browserless, you can set up a proxy by providing proxy configuration to the `newContext()` method:

```js

const browser = await playwright.chromium.connectOverCDP(
  "wss://production-sfo.browserless.io?token=YOUR_API_TOKEN_HERE"
);
const context = await browser.newContext({
  proxy: {
    server: "http://domain:port",
    username: "username",
    password: "password",
  },
});
const page = await context.newPage();

await page.goto("https://icanhazip.com/");
console.log(await page.content());

await browser.close();
```

## FAQ & Troubleshooting

<details>
<summary>Why am I getting a <code>403 Forbidden</code> error?</summary>

Your API token is missing or expired. Pass it as a `?token=` query parameter in the WebSocket or HTTP URL. Verify the token in your [account dashboard](https://browserless.io/account/).

</details>

<details>
<summary>My script works locally but fails on Browserless</summary>

Local browser settings may differ from the Browserless environment. Use `launch` parameters to match your local setup (viewport, user agent, timezone). See [launch parameters](/baas/launch-options) for the full list.

</details>

<details>
<summary>My proxy isn't working or I'm getting connection errors</summary>

Verify your plan supports the proxy type you selected. Built-in proxies require a paid plan. If using an external proxy, check that the `externalProxyServer` URL (or `--proxy-server` flag plus authentication credentials) is correct and the proxy is reachable.

</details>

<details>
<summary>How are proxy units calculated?</summary>

Built-in proxy usage is billed per MB of traffic routed through the proxy: 6 units/MB for residential and 2 units/MB for datacenter. External proxies don't consume proxy units. Check your [dashboard](https://browserless.io/account/) for current usage.

</details>

## Next steps

  <a href="/baas/start" className="next-step-card-link">
    
      <h3 className="next-step-card-title">BaaS Quickstart</h3>
      get started with Puppeteer or Playwright on Browserless
    
  </a>
  <a href="/baas/launch-options" className="next-step-card-link">
    
      <h3 className="next-step-card-title">Launch parameters</h3>
      configure browser behavior and environment
    
  </a>