ScreenshotNeo

BlogHow-to

How to Fix Puppeteer Chrome Launch Errors on an Ubuntu Server

Diagnose Puppeteer launch failures on Ubuntu by matching Chrome’s error output to missing libraries, browser paths, sandbox settings, headless mode, or permissions.

By the ScreenshotNeo team4 October 20269 min read

Short answer: Failed to launch browser process is a symptom, not a diagnosis. Read the full Chrome stderr first, then match the error to its cause: missing shared libraries, a missing or incorrect browser executable, a sandbox restriction, headful mode without an X display, or unwritable profile and cache directories. Prefer keeping Chrome’s sandbox enabled; --no-sandbox is a security tradeoff, not a general fix.

This guide applies to Node.js and Puppeteer on Ubuntu or Debian, including container deployments. Puppeteer’s troubleshooting and requirements pages are the primary references for version-sensitive dependency and runtime details: Puppeteer troubleshooting and Puppeteer system requirements.

1. Capture the complete launch error

Do not change launch flags based only on the first line. Capture the application’s complete exception and Chrome stderr, including the lines immediately before the process exits. Those lines distinguish a missing library from a sandbox, display, path, or permissions problem.

const puppeteer = require('puppeteer');

(async () => {
  let browser;
  try {
    browser = await puppeteer.launch({
      headless: true,
      dumpio: true
    });
    const page = await browser.newPage();
    await page.goto('https://example.com', { waitUntil: 'networkidle2' });
    await page.screenshot({ path: '/tmp/example.png', fullPage: true });
  } catch (error) {
    console.error('Puppeteer launch or capture failed:', error);
    process.exitCode = 1;
  } finally {
    if (browser) await browser.close();
  }
})();

dumpio: true forwards browser process output to the Node process. In a service, make sure stderr is collected by the service’s logging system. Remove or disable verbose output after diagnosis if it is too noisy for routine operation.

2. Match the error signature to a fix

Error clue Likely area First check
error while loading shared libraries, or a library shown as not found OS libraries missing from the host or container Run ldd against the actual Chrome executable.
spawn ... ENOENT or executable path does not exist Browser download or configured path Check install scripts, browser cache, and executablePath.
No usable sandbox! Sandbox configuration or host policy Check Ubuntu version, Chrome location, and AppArmor/user namespace policy.
Cannot open display, or X server errors Headful Chrome without an X display Use headless mode for server screenshots, or provide Xvfb if headful operation is required.
chrome_crashpad_handler: --database is required, profile creation errors, or permission denied Profile, crash database, or cache path not writable by runtime user Check the service user and writable mounts/directories.

These are diagnostic clues, not guarantees: use the surrounding stderr and the exact executable and runtime environment to confirm the cause. Puppeteer’s Linux troubleshooting guide documents the dependency, sandbox, browser-install, and writable-directory checks.

3. Check for missing shared libraries

Run the check where Chrome actually runs: on the Ubuntu host, or inside the deployed container image. First find the browser path. Puppeteer can report its executable path when using its managed browser:

node -e "console.log(require('puppeteer').executablePath())"

Then inspect unresolved dynamic libraries:

ldd /path/to/chrome | grep 'not found'

Replace /path/to/chrome with the executable path from the previous command. If output identifies a missing library, install the corresponding package in the same OS image where the Node process runs, then rebuild or redeploy that image. Ubuntu and Debian Chrome dependencies can include NSS, GTK, ATK, GBM, Pango, X11/XCB libraries, certificates, and fonts. The exact set varies with browser build and base image, so treat the upstream list and the ldd result as authoritative for your deployment rather than assuming one package command fits every release.

Do not install packages on a developer workstation and assume that fixes a container. The browser process sees the libraries in its own runtime filesystem.

4. Verify Puppeteer installed a browser and uses the right path

Puppeteer’s package and browser handling depend on how it was installed. If deployment disables package install scripts, its browser download may not have run. Install the browser explicitly as part of the build or deployment process:

npx puppeteer browsers install

For puppeteer-core, or when using a system-installed Chrome, configure the real executable path explicitly. Confirm the file exists and that the user running the service can execute it. Also ensure that user can access Puppeteer’s browser cache if Puppeteer manages the download.

const puppeteer = require('puppeteer-core');

(async () => {
  const browser = await puppeteer.launch({
    executablePath: '/usr/bin/google-chrome',
    headless: true
  });
  try {
    const page = await browser.newPage();
    await page.goto('https://example.com', { waitUntil: 'networkidle2' });
    await page.screenshot({ path: '/tmp/example.png' });
  } finally {
    await browser.close();
  }
})();

Use the path that exists in your environment; /usr/bin/google-chrome is only an example. A missing path commonly produces ENOENT.

5. Diagnose sandbox failures without weakening security by default

No usable sandbox! points to sandbox setup or host policy, rather than a missing shared library. Inspect the Ubuntu release, the Chrome binary’s location, and the host’s sandbox configuration. Puppeteer documents an Ubuntu 23.10 and later AppArmor interaction that can prevent user namespaces for Puppeteer-downloaded Chrome for Testing; the exact behavior depends on the host configuration and browser location. See the Puppeteer troubleshooting notes and the Puppeteer repository troubleshooting guide.

Keep the browser sandbox enabled where possible and resolve the host policy or deployment configuration that prevents it. Do not copy a sandbox policy change blindly: understand the host’s security requirements and the effect of that change first.

Puppeteer documents --no-sandbox as a possible workaround only when the content being opened is absolutely trusted, and strongly discourages running without the sandbox. Disabling it changes the security boundary around browser content. If a constrained, controlled environment leaves no alternative, make the risk explicit and keep untrusted URLs out of that process.

// Security-sensitive workaround only for a controlled environment
const browser = await puppeteer.launch({
  headless: true,
  args: ['--no-sandbox', '--disable-setuid-sandbox']
});

6. Use headless mode for server screenshots

A server screenshot generally does not need a desktop display. Use Puppeteer’s headless mode, which is the default in current versions, and remove any configuration that explicitly starts headful Chrome. If you do need headful operation, Linux requires an X display; Puppeteer’s troubleshooting guide describes Xvfb for providing one in a service environment.

Installing Xvfb does not fix missing libraries, a bad executable path, sandbox restrictions, or unwritable directories. Add it only when the application genuinely needs a headful browser.

7. Make browser profile and cache paths writable

Chrome writes user data and cache files during startup. In a read-only container, or one with restricted home-directory mounts, startup can fail even when the binary and libraries are correct. Check permissions as the same user that launches Node, not as an interactive administrator.

Set XDG directories to a writable location when appropriate, and provide a writable Puppeteer user-data directory. For example, if /tmp is writable in your deployment:

const puppeteer = require('puppeteer');
const path = require('path');

(async () => {
  const profileDir = path.join('/tmp', `puppeteer-profile-${process.pid}`);
  const browser = await puppeteer.launch({
    headless: true,
    userDataDir: profileDir
  });
  try {
    const page = await browser.newPage();
    await page.goto('https://example.com');
    await page.screenshot({ path: '/tmp/example.png' });
  } finally {
    await browser.close();
  }
})();

Choose a per-process or otherwise isolated profile when multiple browser processes run concurrently. Do not point concurrent launches at one profile directory. Confirm temporary storage has enough space and is available for the life of the browser process.

8. Check Node and Puppeteer compatibility

Compare the installed Node, Puppeteer, and browser versions with Puppeteer’s requirements for that release. At the time reflected in the supplied research, the requirements page showed Puppeteer 25.12.0, required Node 22.12 or later, and listed Debian/Ubuntu Linux x64 and arm64 for Chrome for Testing. These details can change; check the current system requirements and your installed package version before upgrading or pinning deployment dependencies.

node --version
npm ls puppeteer puppeteer-core

When a failure starts after a deployment or dependency update, compare the previous and current Node, Puppeteer, browser, and base image versions. Change one variable at a time so the logs still point to a cause.

9. Troubleshooting checklist

  1. Capture the complete Puppeteer exception and Chrome stderr.
  2. Record the Node and Puppeteer versions and the exact browser executable path.
  3. Run ldd /path/to/chrome | grep 'not found' in the deployed runtime.
  4. If the path is missing, check whether browser installation scripts ran; install with npx puppeteer browsers install if needed.
  5. If stderr says No usable sandbox!, inspect Ubuntu release, AppArmor, user namespaces, and Chrome location before considering any workaround.
  6. Use headless mode unless the application specifically needs a display.
  7. Check profile, cache, and temporary-directory permissions as the service user.
  8. Confirm the deployed Node/Puppeteer/browser combination against the current requirements page.
  9. After each targeted fix, restart the service and capture a minimal known page to see whether the launch error is resolved.

10. Performance, reliability, and operating cost

Chrome startup has a cost, so avoid launching a new browser for every URL when a long-running worker can safely reuse a browser and create separate pages. Always close pages and browsers you no longer need, and isolate concurrent work with separate pages and writable profiles as appropriate. A shared browser process can fail as a unit, so production workers should surface launch and page errors in logs and be able to recreate a failed browser process.

Keep browser binaries and OS libraries in the same versioned deployment image, and validate them during deployment. This makes missing dependencies and browser-path mistakes easier to find before traffic reaches the worker. In containers, include the required libraries in the final runtime image, not just a build stage.

Screenshot throughput also depends on page load behavior, network conditions, page complexity, and concurrency. A timeout is not necessarily a launch error: distinguish a Chrome process that never starts from a page that starts but does not finish loading. Set navigation and job timeouts to suit the pages you handle, and avoid unlimited concurrent Chrome instances that exhaust memory or temporary storage.

Or skip the browser setup

If you need a screenshot without maintaining Chrome on an Ubuntu server, ScreenshotNeo provides a website screenshot API and MCP server. One GET request returns a PNG, JPEG, WebP, or PDF; see the API documentation for the available parameters.

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

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://example.com"},
    timeout=90,
)
r.raise_for_status()
with open("shot.webp", "wb") as f:
    f.write(r.content)
const q = new URLSearchParams({
  access_key: 'YOUR_API_KEY',
  url: 'https://example.com'
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
const image = Buffer.from(await res.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', image));
  • Cookie banners are accepted like a visitor, and 60+ known consent platforms, newsletter popups, and chat widgets are removed before the shot; each step can be turned off.
  • Bot checks and CAPTCHAs, blank pages, failed loads, timeouts, and cache hits cost nothing. Responses identify the page verdict and billing status in headers.
  • An MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients.
  • 1,000 screenshots a month are free with no card; paid plans start at $5 for 3,000 screenshots.

Create a free ScreenshotNeo account to get 1,000 screenshots a month with no card.

FAQ

Should I add --no-sandbox to every Puppeteer launch?

No. It disables Chrome’s sandbox protections and is not a general launch fix. Identify the sandbox error and host policy first; Puppeteer strongly discourages running without the sandbox.

Do screenshots on Ubuntu require Xvfb?

Usually not. Headless mode does not need a desktop display. Xvfb is relevant when you deliberately run Chrome headful on a Linux server.

Why does Puppeteer work locally but fail after deployment?

The deployed runtime may have different shared libraries, browser downloads, executable paths, filesystem permissions, sandbox policy, or Node/browser versions. Run the checks inside the exact host or container that launches Chrome.

Can Puppeteer take a screenshot if navigation never reaches network idle?

Yes, if your capture logic uses another suitable navigation condition or an explicit wait strategy. A page-load wait timeout occurs after Chrome starts, so it should be diagnosed separately from a browser launch failure.