Puppeteer Headless Mode: How to Run Chrome Without a UI
Run Puppeteer without a visible Chrome window. Learn the difference between headless modes, set up a working script, and troubleshoot common launch failures.
Puppeteer runs Chrome without a visible browser window by default. To make that choice explicit, launch it with headless: true. Use headless: 'shell' for the separate chrome-headless-shell binary, or headless: false when you need to see and debug the browser.
1. Install Puppeteer and launch headless Chrome
The examples below use JavaScript modules and Puppeteer’s bundled browser. Install the package in a project with Node.js:
npm install puppeteer
Create screenshot.mjs:
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch({ headless: true });
try {
const page = await browser.newPage();
await page.goto('https://example.com', { waitUntil: 'networkidle2' });
await page.screenshot({ path: 'page.png', fullPage: true });
} finally {
await browser.close();
}
Run it with node screenshot.mjs. The script launches Chrome without displaying its UI, navigates to the page, saves a full-page PNG, then closes the browser even if navigation or capture throws an error.
Puppeteer is a JavaScript library for controlling Chrome or Firefox over the DevTools Protocol or WebDriver BiDi. Headless describes the lack of a visible UI; Chrome still runs as a browser process. See the official Puppeteer overview.
2. Choose the right headless mode
| Setting | What it launches | Use it when | Keep in mind |
|---|---|---|---|
headless: true |
New headless Chrome | You want the normal headless browser for general automation. This is the documented default. | It is not the same implementation as the shell binary. |
headless: 'shell' |
Separate chrome-headless-shell |
Your automation does not need the complete Chrome feature set and this implementation suits the workload. | Its behavior does not completely match regular Chrome. Do not assume it is always faster. |
headless: false |
Visible, headful Chrome | You need to inspect the page or debug interactions visually. | This is not headless. Enabling devtools: true also forces visible mode. |
For most scripts, start with true. Try shell mode only when its feature set and behavior fit your automation. Puppeteer’s headless guide describes shell as currently more performant for automation tasks that do not need the full Chrome feature set, but it supplies no universal benchmark; measure your own workload if performance matters. Read Puppeteer’s headless mode guide and the LaunchOptions reference.
const browser = await puppeteer.launch({ headless: 'shell' });
// Or, for a visible debugging window:
const debugBrowser = await puppeteer.launch({ headless: false });
3. Browser installation and version matching
The puppeteer package normally downloads a compatible Chrome for Testing browser and the headless shell during installation. Prefer that bundled browser unless you have a specific reason to manage the browser separately: Puppeteer guarantees compatibility with the browser it downloads, not arbitrary Chrome versions.
Package managers or deployment environments may block install scripts, leaving the library present but the browser missing. Install the browser explicitly with Puppeteer’s documented command:
npx puppeteer browsers install
puppeteer-core is the library-only package. It does not download Chrome, so use it when a remote browser or externally managed installation is intentional. Provide an executable path or channel; Puppeteer requires one with puppeteer-core:
import puppeteer from 'puppeteer-core';
const browser = await puppeteer.launch({
executablePath: '/path/to/chrome',
headless: true,
});
Replace the path with the actual Chrome executable available in your environment. You can instead configure a supported Chrome channel where appropriate. Check the installation guide and launch API for the current options.
Browser versions move with Puppeteer releases. For context, Puppeteer’s v25.12.0 support page maps to Chrome for Testing 154.0.8037.57; treat that as a version-specific mapping, not a permanent pin. Check the supported browsers table for the version you install.
4. Configure launch and capture behavior
The headless option selects visibility and implementation. Other launch options solve separate problems. For example, slowMo spaces out browser operations while debugging, and devtools opens DevTools and forces a visible browser.
const browser = await puppeteer.launch({
headless: true,
// executablePath: '/path/to/chrome', // only for a managed browser
// channel: 'chrome', // select an installed Chrome channel
// slowMo: 100, // useful while debugging
});
Set navigation readiness on the page rather than treating headless mode as a wait strategy. Common page.goto() wait choices include load, domcontentloaded, networkidle0, and networkidle2. Network-idle conditions can be a poor fit for pages with long polling or persistent connections; choose the condition that matches the page, and add a specific selector wait when the content you need is known.
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
await page.waitForSelector('main');
await page.screenshot({ path: 'main.png' });
For screenshots, Puppeteer can capture a viewport or a full page with fullPage: true. A reliable script should close the browser in a finally block. If you create many pages, close each page when finished; if you run multiple jobs, limit concurrency to the memory and CPU available to the machine.
5. Troubleshooting common launch problems
| Symptom | Likely cause | What to do |
|---|---|---|
| “Could not find Chrome” or browser launch fails immediately | The browser download was skipped or install scripts were blocked. | Run npx puppeteer browsers install. Confirm the deployment build includes the downloaded browser. |
puppeteer-core cannot launch |
This package does not bundle a browser. | Pass a valid executablePath or channel, and confirm the file exists in the runtime environment. |
| Linux reports missing shared libraries | Required system dependencies are absent from the host or container. | Install the dependencies for your distribution using Puppeteer’s troubleshooting guide, then retry. |
| Chrome fails to start in a container or restricted host | The host may prevent Chrome from creating its normal sandbox. | Fix the host/container sandbox configuration and run with the expected permissions. Chrome’s sandbox protects the host from untrusted web content. Puppeteer strongly discourages --no-sandbox; do not use it as a routine workaround. |
| Screenshot is blank or content is missing | The page may not have finished rendering, or the chosen readiness condition may not match its behavior. | Wait for a meaningful selector or a suitable navigation condition. Inspect the page in visible mode to see what rendered. |
| Shell mode lacks expected browser behavior | chrome-headless-shell does not completely match regular Chrome. |
Switch to headless: true when the workflow needs full Chrome behavior. For shell-specific GPU acceleration, Puppeteer’s troubleshooting documentation says to use --enable-gpu. |
When visual inspection helps, launch with headless: false; the debugging guide also documents slowMo to slow operations down. Once the cause is clear, return to the mode appropriate for the job.
6. Performance, reliability, and cost
Headless mode removes the visible UI; it does not remove the cost of starting Chrome, rendering pages, or holding browser memory. Reuse a browser process for a batch of related pages when appropriate, close pages and browsers when done, and cap concurrent work to avoid exhausting CPU or memory. Shell mode may suit tasks that do not need all of Chrome, but behavior differs and there is no single speed result that applies to every workload.
For reliable automation, pin compatible Puppeteer and browser versions through your normal dependency and deployment process, ensure installation scripts and browser files make it into the runtime image, wait for page-specific content, and preserve cleanup in error paths. For untrusted pages, keep Chrome’s sandbox enabled and follow the host’s security guidance. Running locally shifts browser installation and compute costs to your own machine or server; a managed browser service can be an option when you do not want to operate browser infrastructure, but is not required for local Puppeteer headless mode.
7. Or skip the browser setup
If you only need a website screenshot, ScreenshotNeo is a website screenshot API and MCP server for developers. One GET request returns a PNG, JPEG, WebP, or PDF. It handles browser setup and offers options including full-page captures, element selection, device presets, custom waits, and PDF output. See the ScreenshotNeo API documentation for request options.
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}`);
const fs = await import('node:fs/promises');
await fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));
- Cookie and consent banners, newsletter popups, and chat widgets are removed before capture; each cleanup step can be turned off.
- Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed. Response headers report the page verdict and billing status.
- An MCP server provides
take_screenshot,get_page_info, andcapture_pdftools for Claude, Cursor, and other MCP clients. - 1,000 screenshots per month are free with no card; paid plans start at $5 for 3,000 screenshots. Every feature is available on every plan.
Sign up for ScreenshotNeo’s free plan to get 1,000 screenshots a month with no card.
8. Frequently asked questions
Does headless mean Puppeteer does not start Chrome?
No. Chrome still runs as a process; headless means it has no visible browser UI.
Is headless: 'shell' the same as headless: true?
No. Shell selects a separate headless-shell binary with behavior that does not completely match regular Chrome.
Can I use a system-installed Chrome?
Yes. Configure its executable path or channel, but Puppeteer only guarantees compatibility with its bundled browser.
Should I add --no-sandbox if launch fails?
No. First resolve missing dependencies or host sandbox restrictions. Puppeteer strongly discourages disabling Chrome’s sandbox.


