What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

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

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

For the simplest headless Chrome setup, install puppeteer: it normally downloads a compatible Chrome for Testing binary, then launch it with puppeteer.launch(). If you install puppeteer-core instead, you must supply an existing browser with executablePath or channel. When an install script is skipped, run npx puppeteer browsers install explicitly.

This guide shows a working Node.js API example, explains browser ownership and cache locations, and fixes the Linux, Docker, CI and Cloud Run failures that produce “Could not find Chrome” or sandbox errors.

Choose how Puppeteer will obtain Chrome

Puppeteer is a JavaScript library that controls Chrome (and, through its supported protocols, Firefox) from Node.js. The package choice determines who owns the browser binary.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Strategy Install Browser ownership Launch requirement Best fit
Bundled Puppeteer npm i puppeteer Puppeteer downloads Chrome for Testing Usually no path is needed Local development and version-matched automation
Managed browser npm i puppeteer-core You provide Chrome, Chromium or a remote browser executablePath or channel is required System packages, custom images and remote endpoints
Manual Puppeteer download Puppeteer package plus npx puppeteer browsers install Puppeteer cache Use Puppeteer’s resolved executable CI or package managers that suppress post-install hooks

The Puppeteer maintainers recommend the Chrome for Testing version downloaded by the package. Arbitrary system-browser versions are not guaranteed to work with every Puppeteer release.

Install Node.js and Puppeteer

Start a new project

  1. Install a currently supported Node.js release and npm.
  2. Create a project and initialize its package manifest:
mkdir headless-demo
cd headless-demo
npm init -y

Use the batteries-included package

npm i puppeteer

A normal installation downloads a recent Chrome for Testing build. The current installation guide estimates the download at approximately 170 MB on macOS, 282 MB on Linux and 280 MB on Windows. Those are approximate binary sizes, not a promise about total disk usage after caches, profiles and dependencies are included. Puppeteer also downloads a chrome-headless-shell binary as part of its browser flow; that binary has been included since Puppeteer v21.6.0.

When the download was skipped

npm, pnpm, Yarn Berry, Bun and Deno policies can block package install scripts. The package can appear installed while no browser exists in the cache. Install the browser in a separate, visible build step:

npx puppeteer browsers install

In CI, make this command part of the image or build job rather than relying on a developer laptop’s cache. If your security policy intentionally disables scripts, keep that policy and run the explicit browser-install command instead. If scripts are allowed, permit Puppeteer’s install script and verify that the resulting cache is copied into the runtime image.

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

Run the Node API

Minimal bundled-browser example

Save this as index.mjs and run node index.mjs:

import puppeteer from 'puppeteer';

const browser = await puppeteer.launch({headless: true});
const page = await browser.newPage();
await page.goto('https://example.com', {waitUntil: 'networkidle2'});
console.log(await page.title());
await browser.close();

puppeteer.launch() returns a promise for a Browser. The example creates a page, waits until network activity is mostly idle, reads the title and closes Chrome. Always close the browser in production code, including error paths, so orphaned processes do not consume memory.

Use an installed Chrome with puppeteer-core

puppeteer-core contains the automation library but does not download a browser. Pass the absolute executable path supplied by your image or host:

import puppeteer from 'puppeteer-core';

const browser = await puppeteer.launch({
  executablePath: process.env.CHROME_BIN
});
const page = await browser.newPage();
await page.goto('https://example.com', {waitUntil: 'networkidle2'});
console.log(await page.title());
await browser.close();

On machines with a recognized Chrome channel, you can use a channel instead:

import puppeteer from 'puppeteer-core';
const browser = await puppeteer.launch({channel: 'chrome'});
try {
  const page = await browser.newPage();
  await page.goto('https://example.com', {waitUntil: 'networkidle2'});
  console.log(await page.title());
} finally {
  await browser.close();
}

With puppeteer-core, one of executablePath or channel is mandatory. Validate the path outside Node first (for example, execute the binary with its version flag) so a missing file is not mistaken for a Puppeteer API problem.

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

Understand the browser cache

Since Puppeteer v19.0.0, downloaded browsers are cached under ~/.cache/puppeteer by default. The cache belongs to the user that ran the install command. A common deployment failure is installing as root during a build and running as an unprivileged user later; the runtime user then cannot read the browser.

  • Persist the cache between CI jobs or image layers.
  • Ensure the runtime user can read the cache and write a temporary profile directory.
  • If your platform preserves node_modules but not a home directory, configure Puppeteer’s cache inside the application tree, such as node_modules/.puppeteer_cache, following the platform’s documented runtime workaround.
  • Do not assume a cached node_modules directory proves that Chrome was downloaded; post-install hooks may have been skipped.

Linux and Docker requirements

Install shared libraries

Chrome can fail before your script reaches page.goto() when a shared library is absent. On Debian-family images, check the binary directly:

ldd /path/to/chrome | grep not

The troubleshooting guide specifically calls out packages such as libnss3, libgbm1, libgtk-3-0, libasound2, libx11-6 and libx11-xcb1. Package names can vary by distribution release, so install the equivalent runtime libraries for your base image and rerun ldd.

Run as a non-root user

Create a non-root user in the container, give it ownership of its home, Puppeteer cache and temporary profile directories, and launch Chrome as that user. Chrome’s sandbox is a host-protection layer; bypassing it weakens isolation.

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

The --no-sandbox argument is an environment-specific exception only when the opened content is absolutely trusted and the container cannot provide a usable sandbox. It is not a default fix for permissions or missing libraries. Prefer correcting user IDs, kernel settings and container capabilities first.

Alpine Linux

Chrome does not support Alpine out of the box. If you choose Alpine, use a Chromium package matched to your Puppeteer version, provide all required libraries and test the exact image. A setup that works on Debian is not evidence that the same Dockerfile will work on Alpine.

Hosted runtimes and build pipelines

Google Cloud Run

Cloud Run’s default Node.js runtime does not include the system packages required by Headless Chrome. Build and deploy a custom image containing Chrome (or the Puppeteer-downloaded browser), its shared libraries, a writable cache/profile location and a non-root runtime user.

App Engine standard and Cloud Functions

The documented Google App Engine standard and Google Cloud Functions runtimes include the needed system packages. You still need to place the Puppeteer cache somewhere that survives the build and is readable at runtime, especially when deployment policies may skip install hooks.

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

Continuous integration

  1. Install JavaScript dependencies.
  2. Run npx puppeteer browsers install if the browser is not already present.
  3. Persist the Puppeteer cache or bake it into the image.
  4. Run a smoke test that launches Chrome, opens a known URL and closes the browser.
  5. Publish the browser and library versions with build logs so a later failure can be correlated with an image change.

Diagnose common launch failures

Symptom Likely cause Fix
“Could not find Chrome” or an empty executable path Install script was blocked, cache was discarded, or the wrong user is running the process Run npx puppeteer browsers install, persist ~/.cache/puppeteer, and check ownership. For puppeteer-core, set executablePath or channel.
“Failed to launch the browser process” immediately Missing Linux shared library Run ldd chrome | grep not and install the corresponding packages, including the commonly required libraries listed above.
Chrome exits only in a container Sandbox, user, permissions or unwritable profile/cache directory Use a non-root user with owned directories; provide a usable sandbox. Treat --no-sandbox as a last-resort exception for trusted content.
Works locally but not in CI Different browser version, skipped post-install, or cache not present in the runtime layer Install the browser explicitly in CI, persist the cache and log the resolved executable and package versions.
System Chrome launches but pages behave unpredictably Browser version is outside the combination Puppeteer supports Prefer Puppeteer’s downloaded Chrome for Testing, or pin and test the managed browser image together with your Puppeteer version.
Alpine image fails while Debian succeeds Unsupported base image assumptions or mismatched Chromium package Match Alpine’s Chromium package to Puppeteer, install its dependencies and test the complete image; otherwise use a Debian-family base.

Reliability, performance and cost considerations

  • Cold starts: downloading Chrome during every invocation adds latency and can fail without network access. Bake the browser into an image or persist the cache.
  • Memory: each browser and page consumes resources. Reuse one browser for a controlled batch of pages, but close pages and the browser when the job ends.
  • Navigation waits: networkidle2 is useful for pages that finish loading, but applications with long polling may never become idle. Choose a page-specific readiness condition in your own code rather than treating a timeout as a browser-install error.
  • Version drift: Puppeteer’s downloaded Chrome is the tested pairing. If you manage Chrome yourself, pin both the image and Puppeteer dependency and exercise the same pair in CI.
  • Disk: reserve space for the approximate Chrome download, cache growth, temporary profiles and application logs; the headline binary size is not the complete runtime footprint.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Or skip the browser setup

If your goal is a reliable website screenshot rather than browser automation itself, ScreenshotNeo provides a hosted API and MCP server. It accepts one GET request and returns PNG, JPEG, WebP or PDF without requiring you to package Chrome.

Use the ScreenshotNeo API documentation for the full parameter list. A basic call is:

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

The equivalent Python request is:

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)

And in 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}`);

ScreenshotNeo accepts 63 options, including full-page capture with lazy images loaded, CSS-selector element capture, dark mode, 12 device presets or a custom viewport, retina scale, PDF paper size/margins/landscape/page ranges, HTML/CSS-to-image, custom JavaScript and CSS, pre-capture clicks, hidden selectors, waits for a selector/delay/network idle, request and resource blocking, custom headers/cookies/user agent/Authorization, timezone and geolocation, transparent backgrounds, resizing, configurable-TTL caching, signed links, asynchronous jobs with signed webhooks, bulk capture of up to 100 URLs per call, a usage API and an OpenAPI specification. Parameter names used by other screenshot APIs also work for easier migration.

Before capture, it accepts cookie and consent banners and removes more than 60 known consent platforms, newsletter popups and chat widgets; each cleanup step can be disabled. Only clean shots are billed: bot checks or CAPTCHAs, blank pages, timeouts, failed loads and cache hits cost nothing. Response headers identify the result with X-Page-Verdict and X-Billed. Its MCP server exposes take_screenshot, get_page_info and capture_pdf to Claude, Cursor and other MCP clients.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Plan Allowance Price
Free 1,000 shots/month $0, no card
Starter 3,000 shots $5
Growth 15,000 shots $15
Pro 60,000 shots $39
Scale 250,000 shots $99
Business 1,000,000 shots $249

Yearly billing gives two months free, and every feature is available on every plan. Create a free ScreenshotNeo account to get 1,000 screenshots a month with no card.

Frequently asked questions

Why does a successful npm install still leave no browser?

Package installation and browser installation are separate when a package manager suppresses lifecycle scripts. Run npx puppeteer browsers install in the same build environment that will execute your Node process, then preserve the resulting cache.

Should I choose a browser channel or an executable path?

Use channel when the host exposes a recognized Chrome installation. Use executablePath when your container or deployment image stores the binary at a specific location, or when you need to select among several installed browsers.

Is --no-sandbox a general Docker fix?

No. It removes a Chrome isolation layer and should be considered only for absolutely trusted content when the environment cannot provide a usable sandbox. Correct the container user, permissions, libraries and sandbox configuration first.

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

Frequently Asked Questions

Can I use puppeteer-core without installing Chrome on the same machine?

Yes. The browser can be supplied by a managed host or remote setup, but the launch configuration still must identify it with an executable path or channel.

Where should a serverless build keep Puppeteer’s browser cache?

Use a build-persistent, runtime-readable directory. When the platform preserves node_modules but not the home directory, a path such as node_modules/.puppeteer_cache is the documented pattern.

What does ScreenshotNeo return when a target page is blocked?

Its response identifies the page result with X-Page-Verdict and whether it was billed with X-Billed; bot checks, blank pages, timeouts and failed loads are not billed.

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.

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