Headless Chrome - New Headless vs Headless Shell

Headless Chrome is Chrome run with no window. Learn its two modes, what each one reveals to pages, and how to choose one in Puppeteer and Playwright.

By Simon Chiu · Updated

Headless Chrome is Google Chrome, or Chromium, running without a visible window under the control of a program, used to render pages for screenshots, PDFs, tests and scraping.

Chrome added a headless mode in version 59, in 2017, and it replaced PhantomJS as the default engine for screenshot tools within a year or two. Today "headless Chrome" can mean two different programs, and the difference shows up in your screenshots. This entry is part of the screenshot and rendering glossary. For the general idea, start with headless browser.

A plain explanation

There are two ways to run Chrome headless:

  • New headless (--headless). The full Chrome binary, with the window turned off. It shares its code with the Chrome on your desktop, so pages behave the same. Chrome introduced it in version 112 and made it the meaning of --headless later.
  • Headless shell (chrome-headless-shell). A separate, slimmer binary that keeps the original headless implementation. Since Chrome 132 the old mode is no longer in the main Chrome binary and ships only as this shell.

Neither one is "better". New headless is closer to what visitors see. The shell is smaller and starts fast, which is why some test tools default to it.

What a page can see in each mode

We started both modes from Puppeteer 24.43.1 with Chrome 148 and read a few values from inside a page. We also checked Playwright 1.64.1's two Chromium options.

Setup Version string the browser reports navigator.userAgent token navigator.webdriver navigator.plugins.length
Puppeteer headless: true (new headless) Chrome/148.0.7778.97 HeadlessChrome/148.0.0.0 true 5
Puppeteer headless: 'shell' HeadlessChrome/148.0.7778.97 HeadlessChrome/148.0.7778.97 true 0
Playwright default chromium.launch() 156.0.8078.4 HeadlessChrome/156.0.8078.4 n/a n/a
Playwright channel: 'chromium' 156.0.8078.4 HeadlessChrome/156.0.0.0 n/a n/a

New headless reports a reduced version (.0.0.0) in the user agent, like desktop Chrome does, and exposes the usual PDF viewer plugins. The shell reports the full build number and no plugins. Both keep the HeadlessChrome token, and both set navigator.webdriver under automation, so neither mode is invisible to bot detection.

One first-hand note from writing this page: on our test Mac, Page.captureScreenshot timed out in new headless with Puppeteer while the shell captured the same page in 51 ms. We have not isolated the cause. If screenshots hang only in one mode, try the other before you change anything else.

How it works technically

Headless Chrome runs the normal multi-process architecture: a browser process and one renderer process per site, plus GPU and network services. Without a window, frames are composited into an offscreen surface. Automation libraries connect over the Chrome DevTools Protocol, either through a WebSocket (--remote-debugging-port) or a pipe, and send commands such as Page.navigate and Page.captureScreenshot.

How to choose and use it in Puppeteer, Playwright and RenderScreenshot

Puppeteer. headless: true is new headless. headless: 'shell' uses chrome-headless-shell, which npx puppeteer browsers install chrome-headless-shell downloads. This script prints the table rows above.

import puppeteer from 'puppeteer';

for (const headless of [true, 'shell']) {
  const browser = await puppeteer.launch({ headless });
  try {
    const page = await browser.newPage();
    await page.goto('data:text/html,<title>t</title>');
    const seen = await page.evaluate(() => ({
      userAgent: navigator.userAgent,
      webdriver: navigator.webdriver,
      plugins: navigator.plugins.length,
    }));
    console.log({ headless, binary: await browser.version(), ...seen });
  } finally {
    await browser.close();
  }
}

Playwright. Headless Chromium uses the shell by default. Pass channel: 'chromium' to get new headless.

import { chromium } from 'playwright';

for (const options of [{}, { channel: 'chromium' }]) {
  const browser = await chromium.launch(options);
  try {
    const page = await browser.newPage();
    const ua = await page.evaluate(() => navigator.userAgent);
    console.log(JSON.stringify(options), '->', ua.match(/HeadlessChrome\/[\d.]+|Chrome\/[\d.]+/)[0]);
  } finally {
    await browser.close();
  }
}

Command line. No library needed for a one-off capture. With the shell binary:

chrome-headless-shell --screenshot=page.png --window-size=1280,800 --hide-scrollbars https://example.com

With the full Chrome binary, add --headless. On macOS the binary is inside the app bundle, for example "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome".

chrome --headless --screenshot=page.png --window-size=1280,800 --hide-scrollbars https://example.com

The command-line screenshot guide covers the other flags.

RenderScreenshot. We run headless Chromium on Cloudflare's Browser Rendering platform, so you do not choose a mode, download binaries or track Chrome releases. You choose the result: viewport, wait condition, colour scheme and output format.

curl --get https://api.renderscreenshot.com/v1/screenshot \
  -H "Authorization: Bearer $RS_API_KEY" \
  --data-urlencode 'url=https://example.com' \
  -d width=1280 -d height=800 -d dark_mode=false \
  --output page.png

Tested on 2026-10-15 with Node 22.18, Puppeteer 24.43.1 (Chrome and chrome-headless-shell 148.0.7778.97) and Playwright 1.64.1 (Chromium 156.0.8078.4). The chrome-headless-shell command wrote a 79,353-byte PNG. The full-Chrome --headless command and the curl request were not run for this page.

Common mistakes

  • Mixing modes between environments. A baseline from the shell and a check from new headless differ in fonts, colour scheme and plugins. Pin the mode with the version.
  • Inheriting the host's dark mode. New headless can follow the operating system's appearance. Set the colour scheme explicitly.
  • Using old flags. --headless=old no longer exists in recent Chrome. Install chrome-headless-shell instead.
  • Running as root without a sandbox plan. In containers, prefer a non-root user to adding --no-sandbox.

For the measured difference between headless and a real window, read headless vs headful Chrome. To use headless Chrome without running it, get a free API key.

Frequently asked questions

What is the difference between new headless and headless shell?
New headless is the normal Chrome binary started with --headless, so it behaves like desktop Chrome. Headless shell is a separate, smaller binary called chrome-headless-shell, based on the old headless implementation. It starts quickly but differs from real Chrome in some features and fingerprints.
Which headless mode do Puppeteer and Playwright use by default?
Puppeteer v22 and later use new headless for headless true, and headless 'shell' for the shell. Playwright uses the headless shell for headless Chromium by default, and new headless when you pass channel 'chromium'.
Can I take a screenshot with headless Chrome from the command line?
Yes. Run chrome --headless --screenshot=out.png --window-size=1280,800 followed by the URL. The same flags work with chrome-headless-shell, without --headless.

Related in this topic

Try RenderScreenshot free

150 free credits every month, no credit card required.