What Is Chrome Headless Shell and How Do Developers Use It?
Chrome Headless Shell is the standalone legacy Headless binary. Learn when to use it, install it, automate captures, and choose modern Headless.
Chrome Headless Shell is the standalone binary for Chrome’s legacy Headless implementation. It runs without a visible browser window and is useful for command-line screenshots, PDF generation, DOM extraction, and scraping when you do not need the full Chrome browser. Modern Headless runs the regular Chrome browser without showing its UI and is the better fit for high-fidelity end-to-end tests or extension testing.
The old implementation used to ship inside the Chrome binary. Since Chrome 132.0.6793.0, it has been distributed separately as chrome-headless-shell through Chrome for Testing. In Puppeteer, choose it with headless: 'shell'; choose modern Headless with headless: true, or a visible browser with headless: false.
1. What Headless Shell is
Headless Shell is a lightweight wrapper around Chromium’s //content module. Chrome documents it as having substantially fewer dependencies, including no X11/Wayland or D-Bus requirement. That can simplify a server or container image.
| Question | Headless Shell | Modern Chrome Headless |
|---|---|---|
| Implementation | Standalone legacy shell binary | Regular Chrome browser without a visible UI |
| Puppeteer value | headless: 'shell' |
headless: true |
| Dependencies | Smaller set; no X11/Wayland or D-Bus requirement | Full browser dependencies |
| Best fit | Screenshots, PDFs, scraping, and simple rendering | High-accuracy web-app tests and extension testing |
There is no universal speed winner. The shell may be more performant in some circumstances because it has fewer dependencies, but page behavior, fonts, network conditions, and the container determine actual results. Pin the Chrome for Testing build when reproducibility matters.
2. Install a reproducible binary
Using Puppeteer’s installer
The Puppeteer installation guide says installing puppeteer downloads Chrome for Testing and a compatible Headless Shell.
npm init -y
npm install puppeteer
npx @puppeteer/browsers install chrome-headless-shell@stable
Pin a version
npx @puppeteer/browsers install chrome-headless-shell@120.0.6098.0
The version above is an example from the documentation, not a current recommendation. Use a current release channel or deliberately pinned build. Chrome for Testing publishes JSON endpoints and an availability dashboard for discovering builds.
Verify installation
chrome-headless-shell --version
which chrome-headless-shell
If the command is not on PATH, pass Puppeteer an explicit executablePath or use the path printed by your package manager.
3. Run Headless Shell from the command line
Dump the live DOM
chrome-headless-shell --dump-dom https://example.com/
--dump-dom prints the serialized DOM after Chrome parses the response and runs scripts that modify it. This differs from downloading the original HTML with curl.
Capture a screenshot
chrome-headless-shell --screenshot --window-size=412,892 https://example.com/
--window-size controls the viewport. Set your working directory explicitly in CI and preserve the generated file as an artifact.
Print a PDF
chrome-headless-shell --print-to-pdf https://example.com/
Control waiting
chrome-headless-shell --timeout=15000 --screenshot https://example.com/
chrome-headless-shell --virtual-time-budget=5000 --dump-dom https://example.com/
--timeout limits how long capture operations wait. --virtual-time-budget advances timer-driven page code so delayed content can appear. Neither flag guarantees that every single-page application has finished rendering; use an application-specific readiness check with Puppeteer when that matters.
4. Use Headless Shell with Puppeteer
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch({ headless: 'shell' });
try {
const page = await browser.newPage();
await page.setViewport({ width: 1440, height: 900, deviceScaleFactor: 1 });
await page.goto('https://example.com/', { waitUntil: 'networkidle2', timeout: 30000 });
await page.screenshot({ path: 'example.png', fullPage: true });
await page.pdf({ path: 'example.pdf', format: 'A4', printBackground: true });
} finally {
await browser.close();
}
})();
Install with npm install puppeteer. Puppeteer controls Chrome through the Chrome DevTools Protocol and WebDriver BiDi, exposing navigation, screenshots, PDFs, network interception, and UI testing APIs. See the Puppeteer overview.
Select a browser mode deliberately
await puppeteer.launch({ headless: 'shell' }); // standalone Shell
await puppeteer.launch({ headless: true }); // modern Chrome Headless
await puppeteer.launch({ headless: false }); // visible Chrome
Wait for application readiness
await page.goto(url, { waitUntil: 'domcontentloaded' });
await page.waitForSelector('[data-ready="true"]', { timeout: 20000 });
await page.screenshot({ path: 'ready.png' });
Prefer a site-specific readiness marker to an arbitrary delay. For pages with open connections, networkidle2 may not represent finished rendering.
Capture an element and hide page noise
await page.locator('.invoice').screenshot({ path: 'invoice.png' });
await page.addStyleTag({ content: '.cookie-banner, .chat-widget { display: none !important; }' });
Set headers, cookies, and authentication
await page.setExtraHTTPHeaders({ Authorization: `Bearer ${process.env.TOKEN}` });
await page.setCookie({ name: 'session', value: process.env.SESSION, domain: 'example.com' });
await page.setUserAgent('capture-bot/1.0');
Keep secrets in environment variables and never log authorization headers or session cookies.
5. Virtual screens and advanced display testing
Headless browsers can use virtual screens that are independent of physical monitors. Chrome’s virtual-screen guide describes configuring size, origin, scale factor, orientation, and work area with --screen-info. Chrome DevTools Protocol can add or remove screens while the browser runs.
These capabilities help test fullscreen behavior, multiscreen layouts, high-DPI settings, and popup placement. For ordinary screenshots, --window-size and Puppeteer’s setViewport are simpler. Screen-info syntax is version-sensitive, so check the documentation for the exact build you deploy.
6. Choosing Shell or modern Headless
- Need extension tests or browser fidelity? Choose modern Headless. It is the actual Chrome implementation and is intended for high-accuracy end-to-end behavior and extension testing.
- Only need rendering, screenshots, PDFs, or scraping? Evaluate Shell when its reduced dependency footprint helps your server or container.
- Need reproducible output? Pin Chrome for Testing, record the Puppeteer version, install fonts explicitly, and use a fixed OS image, viewport, locale, and timezone.
- Debugging a flaky page? Reproduce with
headless: false, inspect the page, then switch back to the selected headless mode.
7. Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server. One GET request returns a PNG, JPEG, WebP, or PDF. It accepts cookie and consent banners like a visitor, then removes more than 60 known consent platforms plus newsletter popups and chat widgets before capture; each cleanup step can be disabled.
Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed. Responses identify the result with X-Page-Verdict and X-Billed headers. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients.
Read the ScreenshotNeo API documentation for 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}`);
Features include full-page and element capture, dark mode, device presets or custom viewports, retina scale, PDF paper and page controls, HTML/CSS rendering, custom JavaScript, clicks, waits, request blocking, custom headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, TTL caching, signed links, asynchronous jobs with signed webhooks, bulk capture of up to 100 URLs per call, a usage API, and an OpenAPI specification. Parameter names used by other screenshot APIs also work, which can simplify migration.
The Free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 shots; yearly billing gives two months free. Create a free ScreenshotNeo account.
8. Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
command not found |
Binary is not on PATH. |
Install it with npx @puppeteer/browsers install chrome-headless-shell@stable, use the returned path, or set executablePath. |
| No browser found in Puppeteer | Install script was skipped or cache is empty. | Run the browser install command explicitly and keep package and browser versions compatible. |
| Blank screenshot | Capture happened before rendering, or a bot check or failed load blocked the page. | Wait for a readiness selector, inspect the DOM and console, and test visible Chrome. For ScreenshotNeo, inspect X-Page-Verdict. |
| Different fonts or layout in CI | Missing fonts, different scale, OS, or Chrome build. | Install the same fonts, set viewport and scale explicitly, pin Chrome for Testing, and use one container image. |
| Navigation timeout | Slow resources, an open connection, or an application that never reaches network idle. | Use a bounded timeout, wait for a selector, block unnecessary requests, and capture after the page’s ready signal. |
| PDF lacks backgrounds | Print backgrounds are disabled. | Use printBackground: true and verify print CSS. |
| Extension does not load | Shell lacks a feature required by the extension test. | Use modern Headless or headful Chrome with the extension’s supported flags. |
9. Performance, reliability, and cost
- Startup: Reuse one browser process for multiple pages when isolation allows. Starting a process per URL adds overhead.
- Concurrency: Limit parallel pages to the host’s CPU, memory, and network capacity. Excess concurrency causes timeouts and unstable rendering.
- Reproducibility: Pin Chrome for Testing, Puppeteer, fonts, viewport, timezone, locale, and relevant page data.
- Reliability: Use bounded navigation and selector timeouts, retry transient network failures with backoff, close pages in
finallyblocks, and collect console and request-failure logs. - Cost: Self-hosting requires compute, browser maintenance, fonts, concurrency controls, and queueing. ScreenshotNeo bills only clean shots; failed loads, bot checks, blank pages, timeouts, and cache hits cost nothing.
10. FAQ
Is Headless Shell the same as Chromium?
It is a standalone binary built from Chromium’s legacy Headless implementation and wrapped around the //content module. It is not the complete Chrome browser.
Did Chrome remove Headless mode?
No. Modern Headless remains part of Chrome. The old implementation moved to the separately distributed chrome-headless-shell binary starting with Chrome 132.0.6793.0.
Can Shell run JavaScript?
Yes. DOM dumping and capture occur after page parsing and script execution. Use a readiness condition when an application renders asynchronously.
Should every scraper use Shell?
No. Choose based on required browser fidelity, site behavior, dependencies, and features. Validate target pages with the exact build and flags you will deploy.
Where is the current flag reference?
Use Chrome’s Headless command-line documentation and the Headless Shell guide for current flags and version details.


