chrome-headless-shell is a separate Chrome binary that contains only Chrome's original ("old") headless mode: a lighter browser with no window or browser UI code, used for automation, testing and screenshots.
It exists because Chrome now has two headless modes, and Google moved the old one out of the main binary. This entry is part of the screenshot glossary.
What it means in plain terms
For years, chrome --headless started a separate, slimmed-down implementation of Chrome that shared the rendering engine but not the browser around it. In Chrome 112, Google introduced a new headless mode (--headless=new) that runs the real, full Chrome with no window. In Chrome 132, the old implementation was removed from the main chrome binary. It lives on as chrome-headless-shell, published with Chrome for Testing.
So today:
| New headless | chrome-headless-shell | |
|---|---|---|
| Binary | chrome |
chrome-headless-shell |
| How you start it | chrome --headless |
chrome-headless-shell (always headless) |
| Browser code | The full browser | Rendering engine plus a thin shell |
| Behaviour vs a visitor's Chrome | Very close | Can differ in some features |
| Size on disk (Chrome 148, macOS arm64, our cache) | 346 MB | 190 MB |
Which one your library runs
flowchart LR P["puppeteer.launch()"] --> N["chrome --headless=new"] PS["puppeteer.launch({ headless: 'shell' })"] --> S[chrome-headless-shell] W["chromium.launch()"] --> S WC["chromium.launch({ channel: 'chromium' })"] --> N2["Chrome for Testing --headless (new mode)"]
We confirmed this by printing the executable each library started:
- Puppeteer 24 uses new headless by default.
headless: 'shell'switches to chrome-headless-shell. Both binaries are downloaded on install. - Playwright 1.64 starts
chromium_headless_shell-1248/.../chrome-headless-shellfor a plainchromium.launch(). Withchannel: 'chromium'it starts the full Chrome for Testing binary in new headless mode.
How to choose it in Puppeteer, Playwright and RenderScreenshot
Puppeteer:
import puppeteer from 'puppeteer'; const browser = await puppeteer.launch({ headless: 'shell' }); // or true for new headless try { const page = await browser.newPage(); await page.goto('https://example.com'); await page.screenshot({ path: 'shell.png' }); console.log(await browser.version()); // HeadlessChrome/148.0.7778.97 } finally { await browser.close(); }
Install only the shell, for example in a slim container: npx @puppeteer/browsers install chrome-headless-shell@stable.
Playwright:
import { chromium } from 'playwright'; // Default: chrome-headless-shell. Pass { channel: 'chromium' } for new headless mode. const browser = await chromium.launch(); try { const page = await browser.newPage(); await page.goto('https://example.com'); await page.screenshot({ path: 'pw-shell.png' }); } finally { await browser.close(); }
npx playwright install --only-shell chromium downloads just the shell. Leave --only-shell off if you also need channel: 'chromium'.
With RenderScreenshot there is no binary to choose or install. Captures run in a managed headless Chrome on Cloudflare's browser rendering platform, one fresh session per request:
curl -G https://api.renderscreenshot.com/v1/screenshot \ -H "Authorization: Bearer $RS_API_KEY" \ --data-urlencode "url=https://example.com" \ --output example.png
The rendering performance guide explains where the time in a capture goes.
What we saw when we compared them
We ran the same job (launch, load https://example.com, PNG screenshot, close) five times per mode in Puppeteer on a Mac. All five shell runs finished. In new headless mode, the first run stalled on Page.captureScreenshot until Puppeteer's protocol timeout fired, and the script stopped there. We saw the same stall in other scripts on this machine, so all Puppeteer code in this glossary was re-run with headless: 'shell' there. Treat it as a local macOS issue to watch for, not a general result, and check your own platform. We do not publish timing numbers from a single laptop.
Tested on 2026-10-15 on macOS 27 (arm64) with Node 22.18.0, [email protected] (Chrome 148.0.7778.97) and [email protected] (chrome-headless-shell 156.0.8078.4). The curl request was not run for this page.
Common mistakes
- Testing in one mode and shipping the other. Your local tests use new headless, your container uses the shell (or the reverse), and captures differ. Pin the mode in code.
- Expecting full-browser features in the shell. It is built for automation, not for being a browser. If a page behaves oddly only in your captures, try new headless mode before you debug the page.
- Passing
--headless=old. Current Chrome no longer has the old mode insidechrome. Start thechrome-headless-shellbinary instead. - Installing both binaries in a size-sensitive image. If you only use one mode, install only that binary.
Related terms
To compare the two libraries in depth, read Puppeteer vs Playwright for screenshots. To capture without picking a binary at all, get a free API key.