Puppeteer Troubleshooting: Common Issues and Fixes
Diagnose Puppeteer failures by stage, from browser installation and Chrome launch to waits, containers, and cloud deployment.
Puppeteer errors are easiest to fix when you identify the stage that failed: browser discovery, Chrome launch, navigation, an element wait, an interaction, or deployment. Record your Puppeteer version, browser version, operating system or container image, and exact error first. Then change one relevant setting at a time.
This guide follows that diagnostic path, with runnable examples for capturing a page and practical fixes for common failures. Puppeteer behavior and hosting requirements can change, so verify current platform guidance when deploying.
1. Start with the failing stage
Before changing launch flags or increasing timeouts, collect the evidence needed to distinguish an installation problem from a runtime or page problem.
- Record the exact error and stack trace.
- Record the Puppeteer version, browser version, Node.js version, operating system or container image, and process user.
- Identify whether failure occurs during installation, browser discovery,
launch(), navigation, selector waiting, interaction, or shutdown. - Check whether the browser executable and its cache are present and readable by the process.
- Change one relevant setting, reproduce the problem, and keep the result with the environment details.
A timeout is a symptom: it tells you an operation did not finish within its configured wait. Find the operation and the condition it was waiting for before extending its deadline.
2. Install and find the browser
Puppeteer normally downloads a compatible browser during installation. If the package is present but Chrome cannot be found, check whether the browser download actually ran and where its cache was written. Since Puppeteer 19, the documented default download cache is ~/.cache/puppeteer; PUPPETEER_CACHE_DIR can relocate it. See the official troubleshooting guide.
npm install puppeteer
node -e "console.log(require('puppeteer').executablePath())"
The second command prints the executable path Puppeteer expects. Confirm that the path exists in the same build or runtime environment where your application runs, and that the application user can read and execute it.
If your build reuses node_modules across stages or machines, make browser installation and cache handling explicit. A package install that skips Puppeteer’s browser download can leave the runtime with the library but no browser. In some managed runtimes, placing the cache within node_modules is a documented workaround; follow the current instructions for that platform rather than assuming the cache is portable.
3. Check Chrome launch failures
A launch failure can come from missing shared libraries, executable permissions, sandbox configuration, or unwritable profile and cache directories. It is not automatically a Puppeteer code bug.
Verify the executable and Linux libraries
For a Linux browser executable, check that it exists and inspect unresolved shared libraries. The Puppeteer guide suggests using ldd and looking for entries reported as not found; install dependencies appropriate to the target distribution.
CHROME_PATH="$(node -e "process.stdout.write(require('puppeteer').executablePath())")"
ls -l "$CHROME_PATH"
ldd "$CHROME_PATH" | grep 'not found' || true
If you set executablePath, verify the path in the runtime image, not just on your development machine. Puppeteer can launch another browser this way, but its API reference says it is only guaranteed to work with its bundled browser. See LaunchOptions.
Sandbox errors
When Chrome reports No usable sandbox!, inspect the host’s sandbox configuration. Chrome uses multiple sandboxing layers. Puppeteer’s guidance is explicit: “Running without a sandbox is strongly discouraged.” See the Puppeteer troubleshooting documentation and its linked Chromium security guidance.
Do not make --no-sandbox the automatic fix. It changes the security boundary around browser content. Investigate the host configuration and use an environment-specific remedy. Ubuntu 23.10 and newer can have an AppArmor profile that blocks user namespaces for Puppeteer-downloaded Chrome for Testing binaries; the appropriate workaround depends on that host setup.
Windows launch problems
On Windows, check whether Chrome policies conflict with Puppeteer’s default extension behavior. The official troubleshooting guide also describes a downloaded-Chrome permissions workaround for sandbox access errors affecting some older Puppeteer versions or installations. Check the version and policy context before applying it.
4. Fix container startup and writable paths
Chrome writes profile, configuration, and cache data at startup. A read-only container or a user without write access can cause startup crashes, including chrome_crashpad_handler: --database is required.
Give the browser process writable configuration, cache, and user-data directories. In a container, that can mean creating writable directories in the image or mounting volumes owned by the runtime user. For example, create a dedicated profile path and pass it to Puppeteer:
const browser = await puppeteer.launch({
args: ['--user-data-dir=/tmp/puppeteer-profile'],
});
That path must actually be writable in your environment. Apply the same check to any configured cache or configuration directory. Avoid copying a directory layout from another image without checking its user and filesystem permissions.
5. Handle Alpine and distribution-specific issues
Puppeteer’s troubleshooting guide says Chrome does not support Alpine out of the box; compatible system dependencies are needed. It also documents timeout issues with the Chromium version current for Alpine 3.20 when that guidance was written, and discusses matching Chromium to a supported Puppeteer version. Treat this as version-specific guidance, not a guarantee about every Alpine build.
For a distribution-specific launch issue, verify the browser build, Puppeteer version, installed libraries, and official troubleshooting notes for the exact image. A dependency list for Debian or Ubuntu may not apply to Alpine.
6. Diagnose navigation and selector timeouts
Current Puppeteer references list 30,000 ms as the default launch timeout and the default wait timeout. A longer timeout may help a genuinely slow operation, but it will not make an impossible condition true. First identify which operation timed out and what it was waiting for. See LaunchOptions and WaitForOptions.
Navigation waits
The navigation wait’s waitUntil option controls which lifecycle event resolves the wait; the documented default is load. Choosing a different event changes when the wait completes. It does not prove that a particular application widget or API-driven view is ready.
await page.goto('https://example.com', {
waitUntil: 'domcontentloaded',
timeout: 30_000,
});
Use the lifecycle condition that fits the page, then wait for an application-specific selector if the content you need appears later. Avoid treating networkidle as a universal definition of readiness: pages that poll or keep connections open may not become idle when expected.
Element waits and interactions
Puppeteer’s current interactions guide recommends locators because they wait for the element and relevant action preconditions. Prefer a locator for a typical interaction:
await page.locator('button[type="submit"]').click();
Use waitForSelector when you need its lower-level behavior. Check that the selector is valid in the current page or frame, and that the element can reach the requested visibility state. Dispose of a returned handle when you no longer need it.
const handle = await page.waitForSelector('.report-ready', {
visible: true,
timeout: 15_000,
});
try {
if (!handle) throw new Error('Report element did not appear');
console.log(await handle.evaluate(el => el.textContent));
} finally {
await handle?.dispose();
}
For an interaction timeout, check whether the element appears after an asynchronous update, is inside an iframe, is hidden, disabled, covered by another element, or never appears because the application failed. Increasing the timeout only helps when the condition eventually becomes true.
7. Use launch diagnostics and timeouts deliberately
The LaunchOptions reference documents a 30-second default launch timeout, a configurable timeout, and dumpio, which forwards browser stdout and stderr to the process output. Enable output while diagnosing a launch problem so you can inspect Chrome’s own error before changing several options.
const browser = await puppeteer.launch({
dumpio: true,
timeout: 45_000,
});
Set a longer timeout only when the environment’s startup time justifies it. Keep separate values for launch, navigation, and element waits so a failure identifies the slow or stuck stage.
8. Capture a page with a diagnostic script
This complete example launches Puppeteer’s bundled browser, reports browser and page errors, waits for a page-specific selector, saves a screenshot, and closes the browser even if capture fails.
const puppeteer = require('puppeteer');
async function main() {
const browser = await puppeteer.launch({
headless: true,
dumpio: true,
timeout: 30_000,
});
try {
const page = await browser.newPage();
page.on('console', message => console.log('PAGE:', message.type(), message.text()));
page.on('pageerror', error => console.error('PAGE ERROR:', error));
page.on('requestfailed', request => {
console.error('REQUEST FAILED:', request.url(), request.failure()?.errorText);
});
await page.goto('https://example.com', {
waitUntil: 'domcontentloaded',
timeout: 30_000,
});
await page.locator('h1').wait();
await page.screenshot({ path: 'page.png', fullPage: true });
} finally {
await browser.close();
}
}
main().catch(error => {
console.error(error);
process.exitCode = 1;
});
Replace https://example.com and h1 with the target URL and a selector that means the content you need is ready. If navigation fails, the request and page logs help separate a network or page failure from a browser startup failure.
9. Deploy on cloud runtimes
Puppeteer’s official troubleshooting page has platform-specific examples for App Engine, Cloud Functions, Cloud Run, Heroku, and AWS Lambda. Treat those examples as starting points and confirm current provider settings before deployment.
- Cloud Run: the guide says the default Node.js runtime does not include the system packages needed for Headless Chrome, so deployment needs an appropriate Dockerfile and dependencies. It also notes that CPU allocation after an HTTP response can affect background work.
- App Engine and Cloud Functions: inspect browser cache placement and executable discovery; the guide describes cases where a cache within
node_modulescan help. - Docker: if Chrome processes remain as zombies, the troubleshooting material suggests checking whether an init process such as
dumb-initis appropriate. This is an operational tip, not a universal requirement. - AWS Lambda, Heroku, and other managed runtimes: verify platform-specific filesystem, library, process, and execution-time constraints against current provider documentation.
Background screenshot work must fit the platform’s process and CPU lifecycle. If the runtime suspends or limits CPU after sending a response, finish the work before responding or use a supported background-work mechanism.
10. Troubleshooting reference
| Symptom | Likely cause | What to check or change |
|---|---|---|
| Browser executable not found | Browser download was skipped, cache moved, or build and runtime differ | Print puppeteer.executablePath(), inspect the cache, and ensure installation and runtime use the intended path. |
| Chrome exits before connecting | Missing libraries, permissions, incompatible executable, or unwritable paths | Check executable permissions, inspect Linux shared libraries, and make profile and cache locations writable. |
No usable sandbox! |
Host sandbox or user-namespace configuration | Investigate the host-specific sandbox setup. Avoid disabling sandboxing as a routine fix. |
chrome_crashpad_handler: --database is required |
Chrome cannot write required profile or crash data | Provide writable configuration, cache, and user-data directories for the browser process. |
| Navigation timeout | Slow or stalled navigation, or unsuitable lifecycle condition | Check the target’s network behavior and choose the lifecycle event that matches the task; then set a justified timeout. |
| Selector or click timeout | Wrong selector or frame, late content, or an unreachable visibility/action condition | Confirm the selector and frame, inspect page errors, and wait for the actual application-ready condition. |
| Works locally but not in cloud | Different libraries, cache, filesystem permissions, CPU lifecycle, or runtime image | Compare versions and image details, then follow the platform’s current deployment guidance. |
| Alpine-only launch or timeout issue | Distribution dependencies or a browser and Puppeteer version mismatch | Check Alpine-specific compatibility and versions; do not reuse another distribution’s package list. |
11. Performance, reliability, and cost
For a reliable capture service, reuse a browser process when your application model permits it, create and close pages deliberately, and always close the browser during shutdown. Avoid starting multiple Chrome processes per request without measuring the resource and startup impact. Keep navigation and selector waits tied to the content you need, and log failures with the environment and browser versions.
Browser rendering consumes CPU and memory, and slow pages or long waits increase the time a worker stays occupied. Set bounded launch, navigation, and element timeouts; retry only failures that may be transient, and avoid retrying deterministic errors such as a missing executable or invalid selector. On hosted platforms, account for their execution, CPU, and concurrency limits. Costs depend on your runtime and traffic; Puppeteer itself does not make a cloud host free or set a universal per-capture cost.
12. Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server from Yorker Media. If you want a screenshot without installing and maintaining Chrome, send a GET request with the URL. The API documentation describes the options and response headers.
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}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await require('node:fs/promises').writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));
Cookie banners are accepted like a visitor and removed before the shot, along with known newsletter popups and chat widgets; each cleanup step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits are never billed, and response headers report the page verdict and billing status. The MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 shots. All features are on every plan. Sign up free for 1,000 screenshots a month, with no card.
13. Frequently asked questions
Which Puppeteer and Chrome versions should I use?
Start with Puppeteer’s bundled browser, which is the combination its maintainers guarantee. If you supply another executable, verify that exact browser and Puppeteer version pairing in your target environment.
Should I use waitUntil: 'networkidle' for every page?
No. Select the lifecycle event that matches the navigation you need, then wait for a page-specific condition when application content loads later. Long-lived requests can make network-idle conditions unsuitable.
Does a successful navigation mean the screenshot is ready?
Not always. Navigation lifecycle events describe document loading; client-rendered content may appear later. Wait for a selector or another condition that represents the content your task needs.
Can I use Puppeteer on Alpine?
It may require distribution-specific dependencies and a compatible browser and Puppeteer pairing. Check the current official guidance for your precise Alpine and browser versions.


