What Is a Headless Browser?

A headless browser is a real browser engine with no window. What it is, what it's used for, and what it gives away, measured on Chrome 156.

MMotherlode Lab8 min read
Magpie title card: What Is a Headless Browser?, over a storefront's HTML source

What is a headless browser? A web browser with the user interface stripped off: a full Chrome, Firefox, or WebKit engine that loads pages, runs JavaScript, and lays out the document, driven entirely by code, with no window anywhere to show the result. Everything you do by pointing and clicking, a headless browser does because a program told it to.

That trade, giving up the screen to keep the engine, is why headless browsers run most of the automated web. Your CI test suite, the invoice-PDF generator, the scraper that handles pages built by JavaScript: underneath, several of them are Chrome with the display cut off. This page defines the term, measures what a headless browser adds over a plain HTTP fetch, and ends with the test for whether you need one.

The browser minus the browser

A browser is several programs in one package: a network stack, an HTML parser, a CSS layout engine, a JavaScript virtual machine, and a painting layer that turns the finished layout into pixels. Headless mode removes only the last part. Parsing, layout, scripting, the DOM: all of it still runs, exactly as on your screen. What’s gone is the window chrome, the tabs, and the final paint to a display.

Three engines ship headless modes:

  • Chromium (Blink + V8), the default choice, driven as Chrome, Chromium, or one of the testing builds
  • Firefox (Gecko + SpiderMonkey), headless via the -headless flag
  • WebKit (Safari’s engine), headless builds available, least common in practice

Code drives the browser over a wire protocol. Chrome speaks the Chrome DevTools Protocol (CDP); everything also speaks WebDriver, the W3C standard, increasingly via its successor WebDriver BiDi. Puppeteer and Playwright are JavaScript libraries that speak those protocols for you; Selenium does the same job across languages. The library is the hand on the wheel. The headless browser is the car.

The two headless Chromes

Headless Chrome shipped in 2017 with Chrome 59. The original implementation was a separate browser program that happened to ship inside the Chrome binary and shared no browser code with it. It was fast and small, and it drifted from real Chrome in ways that mattered.

Chrome 112, in 2023, replaced it: headless mode became the real Chrome running windowless, same code as the browser on your desktop. Since Chrome 132 the old implementation no longer ships inside Chrome at all. It lives on as a standalone binary called chrome-headless-shell, which you download from the Chrome for Testing dashboard.

Puppeteer exposes all three states:

const browser = await puppeteer.launch({
  headless: true,      // new headless: real Chrome, no window
  // headless: 'shell' // old headless: chrome-headless-shell
  // headless: false   // headful: a real visible browser
});

Which one to pick is simpler than the ecosystem makes it sound. New headless is real Chrome, so it behaves like Chrome, renders like Chrome, and (as measured below) raises fewer automation flags. Use it by default. The shell is smaller and starts faster, which still matters when you’re spawning hundreds of short-lived instances for screenshots or PDFs and the target page isn’t fighting back.

What is a headless browser used for?

Four jobs account for nearly all of it.

Automated end-to-end testing. Every Playwright and Cypress suite in every CI pipeline is a fleet of headless browsers loading the app, clicking through the checkout flow, and asserting what rendered. This is the oldest use, and by raw instance count probably still the biggest: every push to any mid-size repository starts a handful of these, and the pushes add up.

Scraping pages that JavaScript builds. When the static HTML is an empty shell and the content arrives only after scripts run, a plain HTTP fetch gets you the scaffolding. A headless browser runs the scripts and hands back the finished document. How pages end up that way is its own topic (see client-side vs server-side rendering), and the working-through-it version is in our guide to scraping dynamic websites.

PDF and screenshot generation. chrome-headless-shell --print-to-pdf turns HTML into an invoice PDF; --screenshot renders a page to PNG. A large share of “generate PDF report” features in SaaS products are exactly this, one headless instance per request.

Driving pages as an agent. Filling forms, logging in, clicking through a flow that has no API: the same primitives the test suites use, aimed at other sites. This is also where scraping with a browser becomes its own discipline rather than a flag you pass; the deep version lives in our scraping browser guide, which covers headless browser scraping end to end.

Measured: what the browser adds over curl

One page, two fetches. quotes.toscrape.com/js/ ships its quotes as a JavaScript array and renders them client-side. Fetched 2026-10-16 from a home connection, Chrome for Testing 156.0.8078.4:

Fetch Bytes Rendered quote blocks Time
curl (static HTML) 5,808 0 0.84 s
chrome-headless-shell --dump-dom 8,987 10 1.9–4.0 s

Run it yourself:

$ curl -s https://quotes.toscrape.com/js/ | grep -c 'class="quote"'
0
$ chrome-headless-shell --headless --dump-dom \
    https://quotes.toscrape.com/js/ | grep -c 'class="quote"'
10

Curl gets the container. The browser executes the script and returns the rendered result. That is the entire value proposition.

The cost side is on the same measurement. The headless run took 2 to 4 times as long as curl, and a freshly started chrome-headless-shell, parked on about:blank, held 237 MB of RSS across its process tree on this Linux box. A browser is an expensive way to read a page.

What headless gives away

The same launch, asked what it can do (both binaries at version 156.0.8078.4, --window-size=1920,1080):

Signal chrome-headless-shell Chrome, new headless
User agent HeadlessChrome/156.0.8078.4 HeadlessChrome/156.0.0.0
navigator.webdriver true false
matchMedia("(hover: hover)") false false
matchMedia("(pointer: fine)") false false

Read the first row as the introduction. On a plain launch, no flags beyond headless, both binaries name themselves HeadlessChrome in the user agent. That token is the tell: real Chrome sends Chrome. The shell raises navigator.webdriver with no WebDriver involved anywhere. And both, at a 1920×1080 viewport, claim no pointer and no hover, a combination no desktop or phone browser reports. Automation libraries can override the user-agent string; a default launch doesn’t.

Sites collect these signals and score them, along with font lists, codec support, canvas rendering, and the fact that no mouse ever moves. The practice of reading a browser’s self-description this way is browser fingerprinting. Against a determined fingerprinting setup, “undetectable headless” tools are selling a patch for yesterday’s check. The honest position: new headless Chrome leaks less than the shell, nothing leaks nothing, and a site that fights bots that hard is telling you what it thinks of scraping.

When you don’t need one

Fetch the page once with curl or your language’s HTTP client and grep for the data. If it’s there, parse it and skip the browser entirely: you’ll finish before a headless instance finishes starting, and you’ll do it in kilobytes of RAM instead of 237 MB per instance.

Escalate to a headless browser only when the data is genuinely absent from the static HTML, which, thanks to client-side rendering becoming the default for web apps, is more often than it used to be, but is still the minority of the open web. When you do need one, the realistic choice is between the two big libraries; their differences are laid out in our Puppeteer vs Playwright comparison.

Magpie works the same way, for what it’s worth: static fetch first, escalation to a real browser only when a page needs JavaScript, and bot challenges are detected and reported rather than defeated. The pipeline isn’t the subject here, so that’s all this page will say about it.

FAQ

Is using a headless browser legal? The tool itself is legal to use. What you do with it is where law shows up: scraping publicly available data is broadly lawful in the US, but terms of service, copyright, and privacy law still constrain specific uses, and jurisdictions differ. The details are in our guide to when web scraping is legal.

Can a website detect a headless browser? Often, yes. A default launch sends a HeadlessChrome user agent and, in the shell’s case, sets navigator.webdriver; missing hover and pointer media queries and absent mouse movement leak in both. New headless leaks less than the shell, but every signal is greppable by anyone who looks.

Is chrome-headless-shell the same as Chrome? No. It’s the old headless implementation, split out of the main binary since Chrome 132. It renders with the same Blink engine but doesn’t share the full browser code, and on a plain launch it reports navigator.webdriver: true, which real Chrome doesn’t.

Does a headless browser run JavaScript? Yes, and that’s the point. The engine executes scripts exactly as the visible browser would, which is why the curl fetch above found zero rendered quote blocks and the headless fetch found ten.

Run the two commands

Point them at any page you actually care about. If grep finds your data in the static HTML, parse that and close the browser; you’ve saved 237 MB and a couple of seconds per page. If grep comes back empty, you now know exactly what the headless browser is for, and which of the two Chromes to reach for.

[2026-10-16: every measurement on this page was run the day it was written, from a home connection, on Chrome for Testing 156.0.8078.4. Browser defaults change between versions; re-run before you rely on a number here.]

Stop renting credits.

One flat price, your machine, your keys.

Download free for Windows, macOS and Linux

Windows · macOS · Linux $49/mo flat, cancel anytime