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

If Puppeteer stalls while several Node.js processes are running, first find which awaited operation stopped progressing. Then check whether those processes are competing for the same Chrome profile, whether browser ownership and cleanup are clear, whether concurrency exceeds the host’s capacity, and whether the runtime has the dependencies Chrome needs. A longer timeout can expose a slow startup, but it does not fix any of those underlying causes.

First, identify exactly where the process stalls

A process that stops at puppeteer.launch() is a different problem from one that launches Chrome successfully and then stalls during navigation or a protocol call. Add timestamped logs immediately before and after each important await. This shows both the last operation started and how long it took.

import puppeteer from 'puppeteer';

const log = (message) => console.log(new Date().toISOString(), message);

log('before launch');
const browser = await puppeteer.launch({ headless: true });
log('after launch');

try {
  log('before newPage');
  const page = await browser.newPage();
  log('after newPage');

  log('before goto');
  await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
  log('after goto');
} finally {
  await browser.close();
  log('browser closed');
}

Adapt the example to your installed Puppeteer version and module system. Put the same before-and-after markers around other awaited calls in your actual workflow, such as context creation, selector waits, page evaluation, or screenshot capture. If the final line is “before launch,” investigate browser startup. If it is “before goto,” launch completed and the next investigation belongs to navigation, network activity, or page behavior—not launch.

Check for concurrent reuse of a Chrome profile

Search every launch option, command-line argument, and environment-derived configuration for userDataDir or --user-data-dir. If two concurrently launched Chrome processes point to the same profile directory, Chrome’s ProcessSingleton mechanism can prevent one from starting. Puppeteer’s launcher detects a ProcessSingleton failure and reports that the profile is already in use; it also checks whether the directory is writable. These are separate issues to check, not interchangeable diagnoses. See the Puppeteer launcher implementation.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  • Give simultaneously launched browser processes distinct, writable profile directories when they need separate processes.
  • Check that the process user can create and modify the configured directory, including in containers where mounted-volume ownership may differ.
  • If the design is meant to share a browser, do not start another launch against its active profile. Connect to the existing browser through Puppeteer’s supported connection workflow instead.
  • Do not delete or forcibly alter a profile lock while a browser may still be using that profile. First determine which process owns the browser and shut it down cleanly if appropriate.

A repeated profile path is a strong lead only when your configuration actually reuses one. The title alone does not establish that this is your cause.

Choose a process model that matches the work

There is no single architecture that is best for every workload. Decide how much failure isolation you need, how many browsers the assigned host can support, whether sessions must have separate cookies and local storage, and which process is responsible for shutting down each browser.

Approach Isolation and state Operational consideration
Separate browser process per worker Separate processes provide process-level separation; use distinct profile directories when profiles are configured. Each worker must fit within the host’s CPU, memory, and process capacity. Define which worker owns and closes each browser.
One browser with separate browser contexts Contexts isolate cookies and local storage from one another within the same browser. Reduces the need to start a separate browser for every small task, but tasks share the browser process. See the Puppeteer browser management guide.
Workers connect to a managed browser Clients attach to a running browser using its browser WebSocket endpoint; browser lifecycle is managed separately. Each worker must know whether it only owns its connection or also owns the browser process. See the Puppeteer browser management guide.

For one browser with isolated sessions, contexts are useful because they do not share cookies or local storage. For a browser managed independently of the workers, puppeteer.connect() attaches a client to the running browser. These options do not guarantee that a hang will disappear; they change how browsers and sessions are allocated and owned.

Set an explicit concurrency limit

Starting a browser for every small job can create unnecessary process and memory pressure. Base your worker count on the CPU, memory, and process limits actually assigned to the host or container, then increase it only while those resources remain available and the workload remains stable.

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

Puppeteer’s troubleshooting documentation describes a CircleCI example where Jest detected 36 workers although the environment allowed only 2; the excess worker count led to spawn ENOMEM. In a similar CI setup, set an explicit worker limit suited to the environment rather than relying on automatic worker detection. The example is environment-specific, not a universal recommended worker count. See Puppeteer troubleshooting.

When comparing architectures, consider four trade-offs together:

  • Failure containment: separate processes can isolate some failures, while tasks sharing a browser process share that process’s fate.
  • Resource overhead: account for the actual CPU, memory, and process capacity assigned to the deployment.
  • Session isolation: use separate contexts when cookies and local storage must not be shared.
  • Lifecycle and profile ownership: make clear who starts the browser, controls any profile directory, and eventually closes the process.

Make browser ownership and cleanup explicit

Every task should have a clear owner for the browser it uses and a cleanup path that runs when the task succeeds or throws. Puppeteer’s browser management guide states, “To gracefully close the browser, you use the browser.close() method:” browser.close() closes the browser. By contrast, browser.disconnect() detaches a client without closing the running browser or its pages.

const browser = await puppeteer.launch({ headless: true });

try {
  const page = await browser.newPage();
  await page.goto('https://example.com');
  // Do the work this task owns.
} finally {
  await browser.close();
}

Use this cleanup pattern only when the task owns the launched browser. A worker attached with puppeteer.connect() should disconnect when finished if it does not own the process; a separate browser owner must eventually close that browser. Avoid cleanup code in one process that kills a browser another process is still using.

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

Capture startup and protocol evidence before changing timeouts

Puppeteer’s launch() option timeout defaults to 30,000 milliseconds; setting timeout: 0 disables that startup timeout. The option bounds how long Puppeteer waits for startup. Raising it may be appropriate when a known, legitimate startup takes longer than the current limit, but disabling it removes that bound—it does not repair profile contention, resource exhaustion, missing dependencies, or a later stalled protocol operation. See the LaunchOptions reference.

Capture browser-process output with dumpio: true when investigating startup:

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

If an operation appears stuck after startup, inspect browser.debugInfo.pendingProtocolErrors for unresolved asynchronous protocol calls. Puppeteer cautions that protocol logs can contain sensitive information, so redact them before sharing. The Puppeteer configuration reference documents diagnostic options and information.

Keep a minimal record of Puppeteer and browser versions, operating system or container, launch options, the exact final log line, and whether progress stopped at launch, navigation, or another await. Avoid publishing credentials, cookies, authorization headers, or unredacted protocol data.

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

Check the deployment environment

A script can be correct and still fail to start Chrome in a particular host or container. The official troubleshooting guide covers Linux sandbox conditions, missing system dependencies, and differences among cloud runtimes. Check the instructions for the deployment environment you actually use rather than applying a workaround copied from a different one.

For example, Puppeteer’s Cloud Run notes say CPU allocation behavior can make background Puppeteer work appear very slow after an HTTP response, and that the default Cloud Run Node.js runtime lacks Chrome’s required system packages. Those are Cloud Run-specific cautions, not evidence that every slow Puppeteer job has either cause.

Do not add --no-sandbox automatically. First diagnose the actual launch error and understand the security and deployment consequences for your environment. A sandbox workaround that is appropriate for one constrained deployment may be inappropriate elsewhere.

Troubleshoot by symptom

Symptom What to check Next step
Stalls at puppeteer.launch() when another script is running Shared userDataDir or --user-data-dir; profile permissions; browser output. Use distinct writable profile directories for separate launches, or connect to the intentionally shared running browser. Enable dumpio to capture browser output.
Fails with a profile-already-in-use or ProcessSingleton message Whether another Chrome process currently owns the same profile path. Identify the owner and use a separate profile for another process, or stop the existing browser cleanly before reusing the profile.
Fails with a directory permission or writability error Ownership and write permissions for the configured profile directory in the actual runtime. Choose a directory writable by the process user and verify mounted-volume permissions.
Fails with spawn ENOMEM under CI or many workers Worker count compared with the environment’s process and memory limits. Set an explicit worker limit appropriate to that host or container; Puppeteer’s CircleCI example concerns 36 detected workers versus 2 allowed, not a general sizing rule.
Launch succeeds but navigation or another await stalls The timestamped logs and exact last awaited operation; pending protocol diagnostics where relevant. Investigate that operation specifically. Do not treat a post-launch stall as proof of a launch timeout problem.
Launch fails in a container or cloud runtime OS-specific sandbox requirements, Chrome dependencies, and runtime-specific behavior. Follow the official troubleshooting guidance for that deployment and diagnose the actual error before changing sandbox settings.
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 task is simply to obtain a website screenshot, ScreenshotNeo provides a screenshot API and MCP server instead of requiring you to manage Puppeteer and Chrome processes. A GET request takes a URL and returns an image or PDF. For example, using cURL:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

See the ScreenshotNeo API documentation for request details. Cookie/consent banners, newsletter popups, and chat widgets can be removed before capture; bot checks, blank pages, failed loads, and cache hits are not billed. Its MCP server lets AI agents use screenshot tools. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots.

Sign up free for 1,000 screenshots a month, with no card required.

Frequently Asked Questions

Does running two Node.js scripts automatically cause Puppeteer to hang?

No. Concurrent execution alone does not identify a cause; locate the stalled await and check configuration and host capacity.

What information should I include when asking for help with a Puppeteer hang?

Include Puppeteer and browser versions, OS or container, launch options, the exact last log line, and whether progress stopped at launch, navigation, or another awaited call. Redact credentials and sensitive protocol data.

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

Can I share a browser between Node.js workers?

Puppeteer supports connecting clients to a running browser. Decide which process owns and closes that browser, and whether tasks need separate browser contexts.

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.