How to Fix Puppeteer’s Postinstall Script Failure
Fix Puppeteer postinstall failures, missing Chrome errors, blocked install scripts, cache problems, and container launch issues with a practical diagnosis guide.
The fastest fix is usually: from your project directory, run npx puppeteer browsers install. Puppeteer downloads a compatible Chrome for Testing browser during npm i puppeteer, but package-manager policies can block that install script. When the download is skipped, Puppeteer later reports Could not find Chrome (ver. ...). The official installation guide documents this failure mode and the browser installer command.
After running the installer, make sure the browser cache and runtime user are the same ones used by your application. If the download was intentionally disabled, configure a compatible system browser and pass its executable path instead.
1. Identify which failure you have
| Symptom | Likely cause | Next step |
|---|---|---|
Could not find Chrome (ver. ...) |
The dependency script was blocked, or the browser was never downloaded. | Run npx puppeteer browsers install, then check script policy. |
| Install completes without downloading a browser | PUPPETEER_SKIP_DOWNLOAD, skipDownload, or package-manager policy. |
Inspect configuration and either enable the download or provide an external browser. |
| Browser is present but Puppeteer cannot find it | Different user, home directory, cache directory, or build/runtime environment. | Align PUPPETEER_CACHE_DIR, users, and mounted paths. |
| Browser is found but launch fails | Missing Linux libraries, unwritable profile directories, sandbox restrictions, or permissions. | Fix the runtime image and writable directories; do not treat --no-sandbox as a universal fix. |
Puppeteer’s installation guide explains that a normal puppeteer install downloads a compatible Chrome for Testing browser. puppeteer-core does not download a browser and therefore requires you to provide one.
2. Run the supported browser installer
Run this in the same project directory, as the same user that will run your application:
npx puppeteer browsers install
This is the supported recovery when a package-manager install script was skipped. Verify that the command uses the project-local Puppeteer version:
npx puppeteer --version
npm ls puppeteer puppeteer-core
Then run a minimal launch check:
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch({ headless: true });
const page = await browser.newPage();
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
console.log(await page.title());
await browser.close();
})();
For an ES module project:
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch({ headless: true });
const page = await browser.newPage();
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
console.log(await page.title());
await browser.close();
3. Allow Puppeteer’s install script when policy blocked it
Package managers can block dependency lifecycle scripts. If your policy is the cause, add Puppeteer to the package manager’s approved script list. For npm, the installation documentation gives this configuration pattern:
{
"allowScripts": {
"puppeteer": true
}
}
Place the setting where your npm setup expects it, or apply the equivalent allow-list mechanism for the package manager used by your project. Then reinstall if the browser download must happen during dependency installation:
rm -rf node_modules
npm install
npx puppeteer browsers install
Do not assume that a successful package install means the browser exists. A package manager may finish installing JavaScript files while silently skipping the download script.
4. Check download configuration
Puppeteer supports deliberate download control. Inspect your shell, CI configuration, Dockerfile, and project configuration for these settings:
| Setting | Effect |
|---|---|
PUPPETEER_SKIP_DOWNLOAD |
Prevents the browser download. |
skipDownload |
Configuration equivalent for disabling the download. |
PUPPETEER_CACHE_DIR |
Changes where downloaded browsers are stored. |
PUPPETEER_EXECUTABLE_PATH |
Supplies the browser executable path. |
Environment variables override configuration where applicable. A common mistake is leaving PUPPETEER_SKIP_DOWNLOAD=true in a CI environment and then expecting puppeteer.launch() to discover a downloaded Chrome.
After changing download settings, install the browser again:
unset PUPPETEER_SKIP_DOWNLOAD
npx puppeteer browsers install
5. Use an operating-system browser intentionally
Skipping Puppeteer’s download is valid when your container image or operating system manages Chrome or Chromium. In that setup, you own browser version compatibility and updates. Provide the executable explicitly:
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch({
headless: true,
executablePath: process.env.PUPPETEER_EXECUTABLE_PATH
});
const page = await browser.newPage();
await page.goto('https://example.com', { waitUntil: 'networkidle2' });
await page.screenshot({ path: 'example.png' });
await browser.close();
})();
Use this only when the path points to a compatible browser. If you manage the browser separately or connect to a remote browser endpoint, puppeteer-core is the explicit package choice because it does not download one:
const puppeteer = require('puppeteer-core');
(async () => {
const browser = await puppeteer.launch({
executablePath: '/usr/bin/google-chrome'
});
const page = await browser.newPage();
await page.goto('https://example.com');
await browser.close();
})();
6. Align cache, users, and build artifacts
Since Puppeteer v19, the default browser cache is ~/.cache/puppeteer. A browser downloaded as one user may be invisible to another user, and a cache created during image build may not exist in the runtime container.
- Print the home directory and cache directory during both build and runtime.
- Set a stable
PUPPETEER_CACHE_DIRwhen the default home directory is not stable. - Mount or copy that directory into the runtime image.
- Ensure the runtime user can read and execute the browser files.
- Run
npx puppeteer browsers installagain after changing the cache location.
export PUPPETEER_CACHE_DIR=/opt/puppeteer-cache
npx puppeteer browsers install
ls -la "$PUPPETEER_CACHE_DIR"
7. Separate installation errors from launch errors
Fixing the postinstall step only proves that a browser was downloaded. Launch can still fail in a minimal Linux image.
Missing shared libraries
Headless Chrome requires system libraries. If the error mentions a missing shared object or library, add the dependencies required by your Linux distribution or use a base image that includes them. The exact package names vary by distribution and image.
Read-only or restricted containers
Chrome needs writable locations for configuration, cache, and user data. Give the runtime user writable XDG and profile directories, or configure temporary directories appropriate for your container. A read-only filesystem can therefore fail after a successful download.
Sandbox and permissions
Sandbox errors depend on the container’s user and kernel configuration. Correct the environment and permissions first. Adding --no-sandbox globally is not a general repair and changes the browser’s security model.
8. Reinstall cleanly when state is ambiguous
Use a clean reinstall when the package version, cache owner, or install policy changed:
rm -rf node_modules
rm -rf ~/.cache/puppeteer
npm cache verify
npm install
npx puppeteer browsers install
Only remove a shared cache when you know it is safe to do so. In CI, prefer a deterministic cache key tied to the Puppeteer version and the browser revision.
9. Troubleshooting checklist
| Error or symptom | Cause | Fix |
|---|---|---|
Could not find Chrome (ver. ...) |
Download skipped or cache unavailable. | Run npx puppeteer browsers install; align cache and user; remove unintended skip settings. |
| Install script never runs | Package-manager script policy. | Approve Puppeteer’s script or make browser installation an explicit build step. |
| Browser downloaded during build but missing at runtime | Different image layer, home directory, user, or cache mount. | Set and preserve PUPPETEER_CACHE_DIR; install and run as the same user. |
ENOENT for executable |
Incorrect executablePath. |
Check the path inside the running container, not only on the host. |
| Missing library or shared object | Minimal OS image lacks Chrome dependencies. | Install the required system libraries or use a compatible image. |
| Permission denied | Runtime user cannot read the cache or execute Chrome. | Fix ownership and permissions, then retry. |
| Sandbox error | Container user or kernel restrictions. | Resolve the environment-specific sandbox configuration; avoid blanket flags. |
| Remote browser connection fails | Using puppeteer-core without a valid endpoint or executable. |
Provide the correct connection details or local executable. |
10. Performance, reliability, and cost considerations
- Build time: downloading Chrome during every install is slower than caching the Puppeteer browser directory.
- Reproducibility: pin your Puppeteer dependency and keep its browser cache tied to the same build version.
- Runtime reliability: use the same user, cache path, OS libraries, and filesystem assumptions in build and production.
- External browsers: an OS-managed browser reduces download work but transfers version and compatibility maintenance to your team.
- Remote browsers:
puppeteer-coreavoids bundled downloads but requires explicit endpoint or executable configuration.
11. Or skip the browser setup
If your goal is a clean website screenshot rather than maintaining a local Chromium installation, ScreenshotNeo provides a single API request. Its capture pipeline accepts cookie and consent banners before the shot and removes more than 60 known consent platforms, newsletter popups, and chat widgets. Bot checks, 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.
See the ScreenshotNeo API documentation for all options. A basic request:
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}`);
ScreenshotNeo supports full-page and element captures, dark mode, device presets, custom viewports, retina scale, PDF output, custom CSS and JavaScript, clicks, waits, request blocking, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, selectable cache TTLs, signed links, asynchronous jobs with signed webhooks, bulk capture for up to 100 URLs per call, usage data, and an OpenAPI specification. It also provides an MCP server with take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients.
Plans include 1,000 screenshots per month free with no card. Paid plans start at $5 for 3,000 shots; every feature is available on every plan. Create a free ScreenshotNeo account.
12. FAQ
Does puppeteer-core download Chrome?
No. It expects an externally managed browser and requires an executable path or connection details.
Should I rerun npm install after npx puppeteer browsers install?
Usually no. The browser installer is the direct recovery. Reinstall when a blocked script, changed package policy, or changed download configuration must be applied during dependency installation.
Why does it work locally but fail in CI?
CI often uses a different user, home directory, cache mount, package-manager policy, or minimal container image. Compare those values and preserve the Puppeteer cache between build and runtime.
Can I keep browser downloads disabled?
Yes, when a compatible system or remote browser is managed separately. Set the executable or endpoint explicitly and maintain its compatibility yourself.
Is a postinstall failure the same as a Chrome launch failure?
No. A postinstall failure concerns the browser download step. Launch failures occur later and commonly involve libraries, permissions, writable directories, sandbox configuration, or an incorrect executable path.


