October DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsPC HealthRecommendedCrashes, freezes, slowdowns? Check your PC nowSpot repairable issues before they interrupt work.Check PCOctober DealsAmazon USDeal season is back - check today's better picksAmazon US: current deals, useful picks and tech finds.See Picks×
Skip to content
browser automation

How to Prevent Puppeteer Headless Browser Out-of-Memory Crashes

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

Puppeteer crashes for different memory reasons, and the correct fix depends on which limit failed. A Node.js heap exhaustion, a Chrome renderer crash, a container cgroup kill, and a Docker /dev/shm failure can look similar from the outside. First identify the failing process and measure its memory; then cap concurrency, close every browser resource, size Docker correctly, and only change runtime limits when the measurements justify it.

Start by identifying the failed memory budget

Do not begin with --max-old-space-size or Chrome safety flags. Record the exact error and the process that emitted it. The phrase in the log usually narrows the search:

Symptom Likely budget What to measure
JavaScript heap out of memory Node/V8 heap Node heap usage, RSS and garbage-collection trend
ENOMEM while creating workers or processes Host or container process/memory allowance Active workers, browser processes, cgroup events and RSS
Renderer crash, “page crashed” or browser disconnect Chrome renderer/process memory, shared memory or a killed child Chrome parent and renderer RSS, /dev/shm, process exits
Container exits with an OOM-kill event Container cgroup limit memory.current, memory.events and the container limit
Browser starts, then fails during screenshots/PDFs Peak allocation or retained buffers Concurrent pages, image/PDF buffer sizes and application references

Capture a baseline during a reproducible failure. Log the number of browsers, contexts, pages and queued jobs; Node RSS and heap; Chrome parent and renderer RSS; container memory values; and /dev/shm usage. Puppeteer’s troubleshooting guidance specifically warns that automatic test-worker detection can exceed a container’s allowance and produce ENOMEM.

Bound every kind of concurrency

Limit test workers

Jest may detect CPUs on the whole machine rather than the smaller container allocation. Run fewer workers explicitly:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
#1 Best Overall
Sale
CORSAIR Vengeance LPX DDR4 RAM 32GB (2x16GB) Up to 3200MHz CL16-20-20-38 1.35V Intel XMP AMD EXPO Computer Memory – Black (CMK32GX4M2E3200C16)
  • Disclaimer: Maximum Speed requires overclocking/PC BIOS adjustments. Maximum speed and performance depend on system components, including motherboard and CPU
  • Hand-sorted memory chips ensure high performance with generous overclocking headroom
  • VENGEANCE LPX is optimized for wide compatibility with the latest Intel and AMD DDR4 motherboards
  • A low-profile height of just 34mm ensures that VENGEANCE LPX even fits in most small-form-factor builds
  • A solid aluminum heatspreader efficiently dissipates heat from each module so that they consistently run at high clock speeds
npx jest --maxWorkers=2

Choose a number that fits measured peak memory, not the host CPU count. If two workers each launch several pages, the real concurrency is the product of those levels.

Queue application jobs

Use a semaphore or queue so an incoming burst cannot create unlimited pages. Count jobs, pages, contexts and browsers separately. A simple in-process limiter looks like this:

import puppeteer from 'puppeteer';

const limit = 2;
let active = 0;
const waiting = [];

function acquire() {
  if (active < limit) { active++; return Promise.resolve(); }
  return new Promise(resolve => waiting.push(resolve)).then(() => { active++; });
}
function release() {
  active--;
  const next = waiting.shift();
  if (next) next();
}

async function runJob(url) {
  await acquire();
  let browser;
  try {
    browser = await puppeteer.launch({headless: true});
    const page = await browser.newPage();
    await page.goto(url, {waitUntil: 'networkidle2', timeout: 60000});
    return await page.screenshot({type: 'png'});
  } finally {
    if (browser) await browser.close().catch(() => {});
    release();
  }
}

For higher throughput, keep a small browser pool, but still cap pages per browser and total jobs. A queue prevents a traffic spike from becoming a memory spike.

Close pages, contexts and browsers on every path

Leaks commonly come from an exception path that skips cleanup. Put each lifecycle in try/finally, set navigation and operation timeouts, and close a page before dropping its reference. If you use contexts, close them before the browser.

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.
async function capture(browser, url) {
  const context = await browser.createBrowserContext();
  const page = await context.newPage();
  try {
    await page.goto(url, {waitUntil: 'domcontentloaded', timeout: 45000});
    return await page.pdf({format: 'A4', printBackground: true});
  } finally {
    await page.close().catch(() => {});
    await context.close().catch(() => {});
  }
}

Do not retain page objects, response bodies, screenshots or PDFs in global arrays. Stream or write large artifacts instead of keeping many binary buffers alive. Remove event listeners and request-interception handlers when a job ends.

Recycle wedged workers

Some workloads fragment or retain memory inside Chrome even after normal navigation. Recycle a browser after a defined number of jobs, after repeated renderer crashes, or when RSS remains above your ceiling after all pages close. Treat recycling as containment, not a substitute for finding a leak. A supervisor should terminate and replace a browser that stops responding, while the queue records the failed job for retry.

Measure Node and Chrome separately

Node metrics

setInterval(() => {
  const m = process.memoryUsage();
  console.log({rss: m.rss, heapUsed: m.heapUsed, heapTotal: m.heapTotal, external: m.external});
}, 10000);

A rising heapUsed after jobs finish suggests JavaScript retention. A high RSS with a stable heap can indicate native buffers, libraries, or Chrome launched as a child process; inspect Chrome independently.

Chrome process metrics

In a Linux container, sample the browser parent and renderer processes with your process monitor and record RSS over the same timeline as Node. Track the maximum, not just the idle value. Screenshots, PDFs, large pages, fonts, video and many simultaneous tabs create short-lived peaks that average metrics hide.

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

Container metrics

cat /sys/fs/cgroup/memory.current
cat /sys/fs/cgroup/memory.events
df -h /dev/shm
ps -eo pid,ppid,rss,cmd --sort=-rss | head

On cgroup v1 systems, the equivalent files may be under /sys/fs/cgroup/memory/. An increasing oom or oom_kill counter confirms the container, not V8, enforced the failure.

Give Docker enough memory and shared memory

Use a suitable image and process init

Use the official Puppeteer image, or install all dependencies required by the Chrome for Testing build you run. Start the container with an init process so child processes are reaped correctly:

docker run --init --shm-size=1g --memory=2g my-puppeteer-image

The exact values must come from your measured peak workload. --init addresses process management; it does not increase memory.

Rank #2
Corsair Vengeance RGB RS DDR5 16GB (2 x 8GB) Up to 6000MHz AMD Intel RAM
  • Disclaimer: Maximum Speed requires overclocking/PC BIOS adjustments. Maximum speed and performance depend on system components, including motherboard and CPU
  • AMD EXPO & Intel XMP 3.0 Compatible Only: Dual memory profiles allow you to easily select optimized settings for your platform, whether you’re running an AMD or Intel processor
  • Dynamic RGB Lighting: Individually addressable RGB lighting delivers vibrant effects through a sleek, understated panoramic diffuser
  • Onboard Voltage Regulation: Onboard voltage regulation for reliable power at high frequencies
  • Maximum Bandwidth and Tight Response Times: Optimized for peak performance on the latest AMD and Intel DDR5 motherboards

Size /dev/shm

Docker commonly provides a small shared-memory mount. Heavy pages can exhaust it even when the cgroup still has free RAM. Increase it with --shm-size, then verify usage inside the container. If your deployment cannot enlarge shared memory, use a documented Chrome configuration that avoids relying on the default mount, understanding that disabling safety mechanisms can reduce isolation and should not be your first response.

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

Check sandbox and writable paths

Sandboxed Chrome needs the required container capability. Prefer a correctly configured sandbox over running with --no-sandbox; the latter removes a security boundary and can conceal an image or permission problem. Ensure the profile, cache and temporary directories are writable and have enough space. Read-only or full filesystems can surface as browser failures that are mistaken for OOM.

Keep Puppeteer and Chrome for Testing aligned

Puppeteer downloads a compatible Chrome for Testing build by default. If you set executablePath to a system Chrome or Chromium, you take responsibility for pinning and testing that pair. Do not silently mix a Puppeteer release with an unrelated browser binary. Record both versions in CI logs and reproduce with the same pair locally.

The Puppeteer installation documentation lists approximate Chrome for Testing download sizes of 170 MB on macOS, 282 MB on Linux and 280 MB on Windows. Those are download footprints, not the runtime RAM requirement; reserve disk and image space separately from the memory budget.

Choose an operating model deliberately

Model Strength Cost or risk
One long-lived browser Lowest startup overhead Leaks and fragmentation can accumulate; requires recycling policy
Recycled browsers Contains gradual growth and wedged processes More startup time and browser downloads/cache activity
Many pages in one browser Efficient sharing of one browser process Less fault isolation; renderer peaks can coincide
Separate contexts or browsers Better isolation between jobs Higher baseline memory and process count
Host execution Usually more available memory and simpler diagnostics Less reproducible than a pinned container
Docker/CI Repeatable limits and environments Must budget cgroups, /dev/shm, init and permissions

Start with the smallest concurrency that meets your latency target. Increase one dimension at a time while watching peak RSS and cgroup events.

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

Reduce peak work before raising limits

  • Block unnecessary ads, trackers, fonts or media when they are not part of the test.
  • Capture one element instead of a full page when the requirement allows it.
  • Avoid loading many full-page screenshots or PDFs into memory simultaneously; write each result before starting the next.
  • Use realistic navigation and selector timeouts so failed sites do not occupy workers indefinitely.
  • Disable extensions and request interception rules that retain response bodies unless they are required.
  • Investigate pages with infinite scrolling, canvas-heavy dashboards, large images or WebAssembly as separate workload classes.

Only after reducing avoidable peaks should you consider increasing the container memory or Node heap. A larger heap can postpone a crash while allowing an unbounded queue to consume the host.

When increasing Node’s heap is appropriate

--max-old-space-size controls V8’s old-generation heap; it does not enlarge Chrome’s renderer budget, Docker shared memory or the cgroup limit. Use it only when Node metrics show a legitimate JavaScript heap requirement and the container has matching headroom:

node --max-old-space-size=2048 worker.js

After changing it, repeat the same workload and verify that RSS, Chrome processes and cgroup usage remain within limits. If the error is a renderer crash or container OOM kill, this flag is the wrong fix.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Systematic troubleshooting

“JavaScript heap out of memory”

Find retained arrays, page data, response bodies and screenshot/PDF buffers. Add heap snapshots or allocation sampling around one job, close resources in finally, and lower concurrency. Raise the heap only when the retained data is intentional and bounded.

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

ENOMEM when Jest starts

Automatic worker detection may see all host CPUs while the container allows only a few processes. Set --maxWorkers explicitly, then reduce application-level browser concurrency as well.

Renderer crash or browser disconnect

Check renderer RSS, /dev/shm, cgroup events and the browser/Chrome pair. Increase shared memory or container memory based on measurements, split unusually large pages, and recycle the browser after repeated failures.

Rank #3
Crucial 32GB DDR5 RAM Kit (2x16GB), 5600MHz (or 5200MHz or 4800MHz) Laptop Memory 262-Pin SODIMM, Compatible with Intel Core and AMD Ryzen 7000, Black - CT2K16G56C46S5
  • Boosts System Performance: 32GB DDR5 RAM laptop memory kit (2x16GB) that operates at 5600MHz, 5200MHz, or 4800MHz to improve multitasking and system responsiveness for smoother performance
  • Accelerated gaming performance: Every millisecond gained in fast-paced gameplay counts—power through heavy workloads and benefit from versatile downclocking and higher frame rates
  • Optimized DDR5 compatibility: Best for 12th Gen Intel Core and AMD Ryzen 7000 Series processors — Intel XMP 3.0 and AMD EXPO also supported on the same RAM module
  • Trusted Micron Quality: Backed by 42 years of memory expertise, this DDR5 RAM is rigorously tested at both component and module levels, ensuring top performance and reliability
  • ECC Type = Non-ECC, Form Factor = SODIMM, Pin Count = 262-Pin, PC Speed = PC5-44800, Voltage = 1.1V, Rank And Configuration = 1Rx8

Container is killed without a Node exception

Inspect memory.events and the orchestrator’s termination reason. Lower concurrent jobs or increase the cgroup limit; a Node heap flag cannot override a cgroup kill.

Works locally but fails in CI

Compare memory limits, shared-memory size, filesystem permissions, sandbox capability, init process, CPU count and browser versions. Pin the image and browser, and log these values at startup.

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

Pages close but memory never falls

Determine whether Node RSS, Chrome RSS or file cache is retaining it. Run one URL at a time, remove extensions and interception, test without screenshots/PDFs, and bisect the page workload. If Chrome remains high after cleanup, recycle it and isolate the triggering site or feature.

Or skip the browser setup

For simple website screenshots, ScreenshotNeo provides a single HTTP request instead of maintaining Puppeteer, Chrome processes and Docker limits. Before capture it accepts cookie/consent banners and removes more than 60 known consent platforms, newsletter popups and chat widgets; each cleanup 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 whether it was billed. It also offers an MCP server for Claude, Cursor and other MCP clients, with take_screenshot, get_page_info and capture_pdf tools.

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 the full option set. The service supports full-page captures with lazy images loaded, CSS-selector element shots, dark mode, 12 device presets or custom viewports, retina scale, PDF paper size/margins/orientation/page ranges, HTML/CSS-to-image, custom JavaScript and CSS, clicks, hidden selectors, selector/delay/network-idle waits, request/resource blocking, headers, cookies, user agents, Authorization, timezone, geolocation, transparent backgrounds, resizing, chosen-TTL caching, signed image links, asynchronous jobs with signed webhooks, bulk capture of up to 100 URLs per call, usage data and an OpenAPI specification. Common screenshot-API parameter names also work, easing migrations.

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

Plans include 1,000 screenshots per month free with no card; paid plans start at $5 for 3,000 screenshots, and every feature is available on every plan. Create a free ScreenshotNeo account to try it.

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

FAQ

How do I know whether a crash is memory-related?

Correlate the timestamp with the exact error, process exit reason, RSS, cgroup events and /dev/shm usage. A browser disconnect alone is not proof of a Node heap failure.

Should I run one page per browser?

Not by default. Pages in one browser use fewer baseline resources, while separate browsers improve fault isolation. Measure both models with your actual pages and choose the smallest configuration that remains stable.

Does closing a page guarantee Chrome memory returns immediately?

No. Allocators, caches and fragmentation can keep RSS elevated. Track the trend after cleanup and use controlled browser recycling when memory does not return or a renderer repeatedly wedges.

Frequently Asked Questions

How do I know whether a crash is memory-related?

Correlate the timestamp with the exact error, process exit reason, RSS, cgroup events and /dev/shm usage. A browser disconnect alone is not proof of a Node heap failure.

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

Should I run one page per browser?

Not by default. Pages in one browser use fewer baseline resources, while separate browsers improve fault isolation. Measure both models with your actual pages and choose the smallest configuration that remains stable.

Does closing a page guarantee Chrome memory returns immediately?

No. Allocators, caches and fragmentation can keep RSS elevated. Track the trend after cleanup and use controlled browser recycling when memory does not return or a renderer repeatedly wedges.

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.

Leave a Reply

Your email address will not be published. Required fields are marked *

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

Read next

Recommended PC Tool
Recommended PC Tool
Crashes, No Sound, or Screen Glitches?Free driver scan
Windows Errors? Fix Them Before They SpreadFree repair scan

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.