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.

When Puppeteer’s headless Chrome stops working, do not start by adding --no-sandbox or replacing random packages. First capture the exact failure and runtime details, then isolate the layer involved: browser installation and discovery, Node/Puppeteer compatibility, Linux libraries, sandbox policy, container permissions, headless mode, or page code. The sequence below moves from the cheapest checks to the most environment-specific fixes.

1. Record a reproducible baseline

Save the complete error and identify where it occurs:

  • Launch: puppeteer.launch() never returns.
  • Connection: Chrome starts but Puppeteer cannot connect.
  • Navigation: the browser opens, but goto() times out or fails.
  • Interaction: selectors, clicks, scripts, PDFs, or screenshots stall later.

Also record the OS and CPU architecture, container image, Node version, Puppeteer version, browser name and version, installation command, launch arguments, and any custom executablePath, cache directory, or userDataDir. Run the smallest possible script with browser output enabled:

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch({
    headless: true,
    dumpio: true,
    timeout: 30_000
  });
  const page = await browser.newPage();
  await page.goto('https://example.com', {waitUntil: 'domcontentloaded', timeout: 30_000});
  console.log(await page.title());
  await browser.close();
})().catch(err => {
  console.error(err);
  process.exitCode = 1;
});

dumpio: true forwards Chrome’s stdout and stderr to Node, often revealing a missing library, permission error, or crash that the top-level exception hides.

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

2. Confirm the installed browser and cache

“Could not find expected browser locally”

Since Puppeteer v19, downloaded browsers are stored under ~/.cache/puppeteer by default. If the runtime has a different home directory, an ephemeral filesystem, or a restricted service account, set a cache location that exists in the same environment that runs Node:

export PUPPETEER_CACHE_DIR=/var/cache/puppeteer
npx puppeteer browsers install

The manual install command is particularly important when a package manager disabled install scripts. Verify that the directory is readable by the account launching Chrome and that the browser binary exists inside the container or host—not only on your development machine.

When using executablePath

Check the path from the same runtime:

node -e "const p=require('puppeteer'); console.log(p.executablePath())"
ls -l /path/to/chrome
/path/to/chrome --version

Puppeteer’s API warns that an alternate executable is not guaranteed to work; the bundled browser is the supported baseline. Compare the OS-installed Chrome version with the Puppeteer release and remove the custom path temporarily to test the bundled browser.

3. Check Node, Puppeteer, and platform compatibility

Identify the installed version before applying version-sensitive advice:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
node --version
npm list puppeteer puppeteer-core
npx puppeteer --version

The Puppeteer system-requirements page currently displays Puppeteer 25.12.0 and requires Node 22.12 or newer. These are page-version requirements, not timeless guarantees; an older project may have different support. The documented Chrome for Testing platforms include Windows x64, macOS x64/arm64, Debian/Ubuntu Linux x64/arm64, and openSUSE/Fedora Linux x64/arm64. Upgrade or downgrade deliberately, and keep the Puppeteer package and browser installation in sync rather than mixing a new client with an old system binary.

4. Diagnose Linux shared-library failures

A browser file can be present yet fail immediately because dynamic libraries are absent. On Linux, run:

ldd /path/to/chrome | grep not

Install the missing libraries using your distribution’s packages and architecture. Use Chromium’s current dependency manifest referenced by Puppeteer’s troubleshooting documentation instead of copying an old Ubuntu package list into Debian, Fedora, Alpine, or a minimal image. Re-run ldd until no unresolved entries remain.

Alpine Linux

Chrome does not work on Alpine out of the box. It needs compatible dependencies and a configuration appropriate to the Alpine release. Timeout notes tied to a particular Alpine version should not be treated as universal; reproduce the problem on the exact image and consult the current Puppeteer guidance.

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.

5. Treat sandbox errors as host security problems

For No usable sandbox!, investigate the host security policy before changing launch flags. Chrome uses layered sandboxing, including Linux user namespaces. Check whether user namespaces are available to the active account and whether AppArmor, seccomp, or a container policy blocks them. Ubuntu 23.10 and later can apply AppArmor profiles that prevent Puppeteer-downloaded Chrome for Testing binaries from using user namespaces; follow the Chromium workaround for that host policy.

Puppeteer’s documentation states: “Running without a sandbox is strongly discouraged. Consider configuring a sandbox instead.” Do not make --no-sandbox the routine fix. If you use it temporarily to confirm that sandboxing is the failing layer, document the security trade-off and replace it with a correctly configured sandbox before production.

6. Fix Docker and read-only-container startup

Chrome writes profile, configuration, crash-reporting, and cache data during startup. A read-only root filesystem or a directory owned by another user can produce errors such as chrome_crashpad_handler: --database is required, immediate exits, or connection timeouts.

Make state writable

Provide writable XDG locations and a writable profile owned by the Chrome process user:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
export XDG_CONFIG_HOME=/tmp/chrome-config
export XDG_CACHE_HOME=/tmp/chrome-cache
mkdir -p "$XDG_CONFIG_HOME" "$XDG_CACHE_HOME" /tmp/chrome-profile
chown -R app:app /tmp/chrome-config /tmp/chrome-cache /tmp/chrome-profile

Then launch with userDataDir: '/tmp/chrome-profile', or mount persistent writable volumes at equivalent paths. Avoid sharing one profile between concurrent browser processes.

Use the maintained image correctly

Puppeteer’s maintained Docker image bundles Chrome for Testing and its dependencies. Its guide says the image runs sandboxed, needs the SYS_ADMIN capability, and recommends an init process to reap child processes:

docker run --init --cap-add=SYS_ADMIN your-puppeteer-image

Do not add that capability blindly to an unrelated custom image. First match the actual error to sandbox, filesystem, user, or process-lifecycle causes. An init process (Docker’s --init or an equivalent entrypoint) prevents orphaned Chrome children from accumulating across jobs.

7. Determine whether headless mode changed

Headless behavior has changed across Puppeteer releases. Modern headless Chrome is now the default. Before v22, the old headless implementation was the default; it is now distributed separately as chrome-headless-shell.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Mode Use it to Trade-off
headless: false See a real browser window while diagnosing display, page, and interaction problems. Requires an environment capable of displaying a window.
headless: true Use current Chrome headless behavior and broad feature coverage. Visual failures are less obvious without logging or screenshots.
headless: 'shell' Use the separate old headless implementation for automation that does not need the complete Chrome feature set. It does not match regular Chrome completely; Puppeteer describes it as potentially more performant, without a numeric benchmark.

After an upgrade, compare the mode used before and after the change with a minimal reproduction. If headless: false works while headless fails, the problem is likely mode-, display-, sandbox-, or resource-specific rather than a basic page URL issue. slowMo can make actions observable:

const browser = await puppeteer.launch({headless: false, slowMo: 100, dumpio: true});

8. Separate browser startup from page behavior

Capture page console and errors

Page JavaScript logs do not automatically appear in Node. Forward them explicitly:

page.on('console', msg => console.log('[page]', msg.type(), msg.text()));
page.on('pageerror', err => console.error('[pageerror]', err));
page.on('requestfailed', req => console.error('[requestfailed]', req.url(), req.failure()));

Use a deterministic navigation timeout and wait condition. A page that never reaches networkidle because of analytics or long polling is not necessarily a broken browser; try domcontentloaded and wait for the selector your task actually needs.

Inspect protocol stalls

If an asynchronous Puppeteer call hangs, inspect browser.debugInfo.pendingProtocolErrors. For suspected DevTools Protocol traffic problems, set NODE_DEBUG="puppeteer:*" before running. These logs can contain URLs, headers, cookies, or other sensitive data; redact them before sharing.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Best Value
The SQL Programming Language: .
  • Used Book in Good Condition
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

9. Common symptoms and targeted fixes

Symptom Likely layer Next check
Expected browser not found Install script, cache, or runtime path Run npx puppeteer browsers install; verify PUPPETEER_CACHE_DIR and permissions.
Browser process exits immediately Missing libraries, sandbox, or unwritable state Use dumpio, run ldd ... | grep not, and inspect writable profile paths.
No usable sandbox! Host namespace or security policy Check user namespaces/AppArmor; configure the sandbox rather than permanently disabling it.
Works locally, fails in Docker Image dependencies, user, filesystem, or child-process handling Compare images, mount writable paths, use an init process, and verify the container’s actual security policy.
goto() times out Navigation condition, network, or page code Test headful, capture request failures, and try a narrower wait condition.
Clicks or selectors hang Page state or selector logic Log page console errors, wait for a specific selector, and capture a diagnostic screenshot.

10. Make the fix reliable in production

  • Pin Node, Puppeteer, and browser versions in your build; upgrade them together and test headless modes explicitly.
  • Install browsers during image construction, not on every job, while ensuring the runtime cache path is preserved.
  • Run as a non-root user with ownership of the profile and cache directories.
  • Give each concurrent job its own userDataDir; never reuse a live profile across processes.
  • Set explicit launch and navigation timeouts, close pages and browsers in finally blocks, and cap concurrency so the host does not exhaust memory or process limits.
  • Keep dumpio, page event logging, and protocol debugging behind a diagnostic flag; redact logs because they may contain sensitive data.
  • Retest after base-image, kernel, AppArmor, or Chrome updates. A browser launch problem can be an environment change even when application code is untouched.

Or skip the browser setup

If your goal is simply to obtain a clean website screenshot, ScreenshotNeo provides a hosted API and MCP server instead of requiring you to maintain Chrome, libraries, sandbox policy, and container processes. It accepts cookie and consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be disabled. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing result.

One request is enough (see the ScreenshotNeo API documentation):

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

Equivalent clients:

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

ScreenshotNeo also offers 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. Create a free ScreenshotNeo account.

FAQ

Should I always add --no-sandbox?

No. Use it only as a controlled diagnostic and configure a supported sandbox for production.

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

Why does a screenshot task fail after Chrome launches?

Launch success does not prove navigation or page code works. Capture page console, request-failure events, and protocol diagnostics to isolate the later stage.

Is headless: 'shell' the same as normal Chrome?

No. It selects the separate chrome-headless-shell implementation, which can be faster for some automation but does not provide identical behavior or feature coverage.

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.