chrome-headless-shell: The Old Headless Chrome Binary

chrome-headless-shell is the standalone build of Chrome's old headless mode. How it differs from new headless, which one Puppeteer and Playwright use.

By Simon Chiu · Updated

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-shell for a plain chromium.launch(). With channel: '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 inside chrome. Start the chrome-headless-shell binary instead.
  • Installing both binaries in a size-sensitive image. If you only use one mode, install only that binary.

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.

Frequently asked questions

Is chrome-headless-shell the same as headless Chrome?
It is one of the two headless modes. New headless mode runs the full chrome binary without a window. chrome-headless-shell is a separate, smaller binary that keeps the old headless implementation.
Can I use chrome-headless-shell for screenshots in production?
Yes, many people do, because it starts fast and is smaller. Test your pages in it, because it is not the same browser your visitors run, and some features behave differently.
Do I need --headless when I start chrome-headless-shell?
No. The binary is always headless. Libraries may still pass the flag, which is harmless.

Related in this topic

Try RenderScreenshot free

150 free credits every month, no credit card required.