Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Yes. Install puppeteer-core and replace puppeteer.launch() with puppeteer.connect() to control a Chromium instance running in a managed service or your own Browserless container. Your page code—navigation, selectors, waits, screenshots and PDFs—can remain almost unchanged. The browser runs on another machine; your Node.js process only maintains the WebSocket connection.

The minimal remote-Puppeteer example

Install the client library, not the full Puppeteer package. The full package normally downloads a browser for local use; puppeteer-core provides the automation API without downloading or launching Chromium.

npm install puppeteer-core

Then connect to a WebSocket endpoint supplied by your managed browser or self-hosted Browserless instance:

import puppeteer from "puppeteer-core";

const TOKEN = process.env.BROWSERLESS_TOKEN;
const browser = await puppeteer.connect({
  browserWSEndpoint: `wss://production-sfo.browserless.io?token=${TOKEN}`,
});

try {
  const page = await browser.newPage();
  await page.setViewport({ width: 1440, height: 900, deviceScaleFactor: 1 });
  await page.goto("https://example.com", { waitUntil: "networkidle2" });
  console.log(await page.title());
} finally {
  await browser.close();
}

Put the token in an environment variable rather than committing it to source control. The finally block is important: closing the connection ends the remote session. An abandoned session can remain active until the provider’s timeout and consume usage.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

What changes—and what does not

launch() versus connect()

puppeteer.launch() starts a browser process in the same environment as your Node.js program. It therefore needs a compatible Chrome or Chromium binary, operating-system libraries and enough memory. puppeteer.connect() attaches to a browser that has already been started elsewhere through the Chrome DevTools Protocol WebSocket.

Once connected, standard page methods continue to work: page.goto(), CSS and XPath selectors, $eval(), waits, screenshots and PDF generation. The main difference is where the browser process and its network traffic run.

Use puppeteer-core

Because the remote service supplies Chrome, puppeteer-core avoids downloading a local browser that your application will never launch. Keep the Puppeteer client version compatible with the remote browser’s supported protocol; if a provider documents a required version, follow that requirement.

Close the right resource

With a local launch, browser.close() terminates the child browser process. With a remote connection, it closes your remote session. Either way, call it in finally, including when navigation or an assertion fails.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Managed browser or self-hosted container?

Decision area Managed BaaS Self-hosted Browserless container
Infrastructure ownership The provider starts and operates browsers; your application receives a WebSocket endpoint. Your team owns the container, host, networking, upgrades and security.
Setup time Obtain an endpoint and token, then connect. Deploy the documented Docker image and expose its local WebSocket endpoint.
Scaling and concurrency Provider-managed capacity and limits; verify the plan’s concurrency terms. You choose instance size and replica count and must implement capacity planning.
Browser updates Provider controls the browser image and update schedule. You decide when to pull, test and roll out new images.
Network region Choose an available regional endpoint close to the sites you visit. Place your container in the region and network where you need it.
Observability Use the provider’s session logs and diagnostics, where offered. Integrate container, host and browser logs into your own monitoring.
Security boundary Pages and browser sessions run in the provider’s environment; review its data and isolation policies. Browser traffic and credentials stay within infrastructure you control, but you must harden it.
Cost Usage is billed under the provider’s current plan; the supplied documentation does not establish comparable prices. You pay for compute, storage, egress and operations; no universal total can be stated.

Managed BaaS is the practical choice when you already have Puppeteer or Playwright code and want cloud execution without rewriting it. Self-hosting is appropriate when infrastructure control, private networking or a fixed browser image outweighs the operational work.

Make remote runs reproducible

A remote browser has its own defaults. Do not assume it shares your laptop’s viewport, locale, timezone, user agent or filesystem.

Set viewport and device scale

await page.setViewport({
  width: 1365,
  height: 768,
  deviceScaleFactor: 1,
});

Set these before navigation when responsive layout matters. Use a higher deviceScaleFactor when you need retina-style screenshots, while remembering that larger images consume more bandwidth.

Set user agent, locale and timezone

await page.setUserAgent("Mozilla/5.0 (compatible; ScreenshotJob/1.0)");
await page.emulateTimezone("UTC");
await page.setExtraHTTPHeaders({
  "Accept-Language": "en-US,en;q=0.9",
});

Apply the same settings on every run if you compare output or test localized pages. A provider’s browser region also affects latency and the IP-based content a site returns.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Handle files correctly

Paths such as /tmp/report.pdf refer to the remote browser machine, not your application host. For uploads and downloads, use the provider’s file-transfer features or transfer the bytes through your application. Do not expect a file written by the browser to appear on your local disk automatically.

Wait for the real readiness condition

await page.goto(url, { waitUntil: "domcontentloaded" });
await page.waitForSelector("main article", { timeout: 30000 });
await page.waitForNetworkIdle({ idleTime: 500, timeout: 30000 });

networkidle2 is useful for many pages, but analytics, advertisements and WebSockets can keep a page busy indefinitely. A specific selector or application-ready flag is often more reliable. Add a bounded timeout so a failed page cannot hold a remote session forever.

Authentication, headers and sensitive pages

Remote execution moves page requests and any credentials you provide outside your application host. Use TLS WebSocket endpoints, restrict tokens, rotate them, and avoid logging full URLs that contain secrets.

await page.setExtraHTTPHeaders({
  Authorization: `Bearer ${process.env.SITE_TOKEN}`,
});
await page.setCookie({
  name: "session",
  value: process.env.SESSION_COOKIE,
  domain: "example.com",
  path: "/",
  secure: true,
  httpOnly: true,
});

For a login flow, create a fresh browser context where your provider supports it, authenticate, perform the work, and close the session. Never reuse a context between unrelated tenants unless you have deliberately isolated cookies, local storage and permissions.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Retries, timeouts and reliability

  • Bound every operation. Set navigation, selector and overall job deadlines.
  • Retry transient failures. Reconnect and retry a small, finite number of times for WebSocket disconnects, DNS failures or provider capacity errors. Do not blindly retry authentication failures or deterministic selector errors.
  • Capture diagnostics. On failure, record the URL, final response status, console errors, failed requests and a small screenshot or HTML excerpt, while redacting credentials.
  • Close on every path. Put both normal work and error handling inside try/finally.
  • Choose a nearby region. Browser-to-site latency depends on the browser’s location. A regional endpoint near the target site can reduce round trips.
async function run(url, attempts = 3) {
  let lastError;
  for (let i = 0; i < attempts; i++) {
    let browser;
    try {
      browser = await puppeteer.connect({
        browserWSEndpoint: process.env.BROWSER_WS_ENDPOINT,
        timeout: 30000,
      });
      const page = await browser.newPage();
      page.setDefaultTimeout(30000);
      await page.goto(url, { waitUntil: "networkidle2", timeout: 60000 });
      return await page.title();
    } catch (error) {
      lastError = error;
      await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
    } finally {
      if (browser) await browser.close().catch(() => {});
    }
  }
  throw lastError;
}

Common failures and fixes

“Failed to connect” or WebSocket errors

  • Check that the endpoint is a WebSocket URL beginning with wss:// (or the documented local ws:// address).
  • Verify the token, firewall egress rules and provider region.
  • Do not append a Puppeteer launch command to a connect endpoint; pass the exact endpoint supplied by the service.

Chrome launches but the script hangs

Look for missing browser.close(), an unbounded network-idle wait, or a page that keeps connections open. Add explicit timeouts and wait for a page-specific selector.

“Browser is not defined” or protocol incompatibility

Import the client correctly and check that your Puppeteer version is supported by the remote browser image. Update one side at a time and test navigation, screenshots and PDFs after each change.

Uploads or downloads cannot be found

The path belongs to the remote machine. Transfer the file through the provider API, or upload bytes from your application to the page rather than relying on a local path.

The page differs from local Chrome

Compare viewport, device scale, user agent, timezone, locale, fonts, permissions and browser version. Also check the remote region: sites can vary content by IP geography.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Or skip the browser setup

If your goal is a clean screenshot or PDF rather than arbitrary browser automation, ScreenshotNeo provides a one-request website screenshot API. It accepts the cookie or consent banner before capture and removes more than 60 known consent platforms, newsletter popups and chat widgets; each cleanup step can be disabled. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads and cache hits are not billed, and the response identifies the result with X-Page-Verdict and X-Billed headers.

cURL:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

Python:

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)

Node.js:

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

See the ScreenshotNeo API documentation for options. It also offers an MCP server with take_screenshot, get_page_info and capture_pdf tools for Claude, Cursor and other MCP clients. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots. Sign up for the free plan.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

When remote Puppeteer is the right tool

  • Use remote Puppeteer when you need interaction: login flows, clicking, form submission, custom assertions, DOM extraction or multi-step workflows.
  • Use a managed service when you want to avoid browser packaging and operations.
  • Use a self-hosted container when private networking and infrastructure control justify maintaining Chrome yourself.
  • Use a screenshot API when the required output is a page image or PDF and you do not need to script every browser action.

FAQ

Can I run this in AWS Lambda or CI?

Yes. Your function or CI job needs Node.js and network access to the remote WebSocket endpoint, but it does not need to bundle Chromium. Keep the job timeout longer than the navigation timeout and close the session before the function exits.

Does headless mode determine whether Chrome is remote?

No. Headless and headful describe whether Chrome displays a window. A remote browser can run headless or headful; the choice does not determine where the process runs.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Can remote Puppeteer generate PDFs?

Yes. After connecting, use Puppeteer’s PDF methods as you would locally, subject to the remote provider’s browser version and any service-specific limits.

Frequently Asked Questions

Can I run this in AWS Lambda or CI?

Yes. Install Node.js dependencies, allow outbound access to the remote WebSocket endpoint, set bounded timeouts, and close the session before the job exits.

Does headless mode determine whether Chrome is remote?

No. Headless/headful is a display setting; local versus remote is determined by whether you launch a process or connect to an existing browser.

Can remote Puppeteer generate PDFs?

Yes. Puppeteer PDF methods continue to work after a successful remote connection, subject to the browser service’s limits.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.