How Headless Chrome Works

Updated:
How Headless Chrome Works

Headless Chrome is a browser you never see, but constantly encounter. It runs autotests, generates PDFs and screenshots, and search engines use similar technology for rendering pages. In this article, I'll break down how it works internally, what distinguishes its modes, how it's controlled, and where it's worth applying. For information on how bots based on it end up in reports and how to detect them: "Suspicious traffic in GA4: how bots with headless browsers disguise themselves as humans and how to detect them".

⚡ In short: what is Headless Chrome

  • ✅ It's a real Chrome without a visible interface. Chrome documentation describes it as running a browser in an unattended, UI-less environment. It's launched with the --headless flag.
  • ✅ As of Chrome 112, it's the same browser. The mode creates platform windows but doesn't display them, and the rest of the functionality works without limitations.
  • ✅ It's controlled programmatically: via Chrome DevTools Protocol, WebDriver BiDi, or libraries like Puppeteer, Playwright, Selenium.
  • ⚠️ For a website, it's a full-fledged visitor, because it executes JavaScript. Therefore, legitimate tests and malicious bots look the same to analytics.

📚 Table of Contents

🧠 How it works internally: new headless and chrome-headless-shell

Previously, headless was a separate browser implementation that was shipped in the same binary but didn't use the Chrome code from the //chrome directory. Today, the modes are unified: in Chrome 112, headless was updated so that Chrome creates platform windows but doesn't display them. The old implementation, starting from version 132.0.6793.0, is available only as a separate binary chrome-headless-shell, which can be downloaded from the Chrome for Testing page.

What are the consequences of this separation, explained by the Chrome developers themselves:

Parameter New Headless (--headless) chrome-headless-shell
What it is A real Chrome browser without a visible UI A lightweight wrapper around the Chromium //content module
Dependencies Full set, like Chrome Significantly fewer: doesn't require X11/Wayland and D-Bus
Strength Authenticity, reliability, more features Speed and lightness in some scenarios
What it's suitable for Accurate end-to-end testing, extension testing Automated screenshots, data collection from pages

Simply put, the choice depends on a trade-off between performance and browser "authenticity." This is visible in Puppeteer's launch parameter: headless: true enables the new mode (which is the default), headless: 'shell' enables chrome-headless-shell, and headless: false enables the regular mode with a window. Both modes, at the engine level (Blink, V8), execute the same web platform, so JavaScript, CSS, and network requests work the same as in a regular Chrome.

I discuss the limitations that cause the behavior of modes and interfaces to differ from regular Chrome separately: Headless Chrome Limitations.

🔌 How it's controlled: DevTools Protocol and WebDriver BiDi

Without a window, the browser is controlled by commands. This is usually done through a protocol. When Chrome is launched with the --remote-debugging-port=9222 flag, Chrome DevTools Protocol is enabled: through it, tools send commands to the browser, and this is how debuggers in editors work. The address http://localhost:9222 in another browser shows a list of pages that can be inspected. The debugging port gives full control over the browser, so it should not be exposed externally.

  • Chrome DevTools Protocol (CDP) is Chromium's proprietary protocol. A full command reference is available in the DevTools Protocol Viewer. As noted in the Selenium BiDi description, CDP is only supported by Chromium-based browsers.
  • WebDriver BiDi is a W3C bidirectional standard, created by the Selenium team along with browser vendors. It's intended to become a stable cross-browser API: events flow from the browser to the controlling code via WebSocket. As explained in the Selenium documentation, CDP support there is temporary until BiDi is implemented. In the Chrome blog post from 2023, BiDi support in Puppeteer was called experimental, so check the current status in the documentation.
How a script controls Headless Chrome Diagram: your code (Puppeteer, Playwright, Selenium) sends commands via CDP or WebDriver BiDi protocol over WebSocket to a windowless Chrome, which returns results: DOM after JavaScript execution, screenshot, or PDF. Your code Puppeteer Playwright Selenium commands Protocol Chrome DevTools Protocol (CDP) or WebDriver BiDi WebSocket Windowless Chrome Blink · V8 · network same code as in Chrome data Result DOM after JS screenshot PDF
Diagram: how a script controls Headless Chrome

🛠️ Puppeteer, Playwright, Selenium: how they differ

Tool Languages Browsers When to choose
Puppeteer JavaScript (Node) Chrome / Chromium; check documentation for support of other browsers Fast Chrome automation: screenshots, PDFs, page crawling. A library from the Chrome team, headless is enabled by default
Playwright JavaScript/TypeScript, Python, .NET, Java Chromium, Firefox, WebKit A single API for multiple engines and its own test runner; headless is supported in all browsers and on all platforms
Selenium Java, Python, JavaScript, C#, Ruby, and others Chrome, Edge, Firefox, Safari Existing WebDriver test suites and precise control over a specific browser's driver

For Java developers, it's important that both Playwright and Selenium have official Java clients. There is no "best" tool: Puppeteer is simpler for quick tasks in Chrome, Playwright is more convenient when multiple engines are needed, and Selenium is chosen when a WebDriver infrastructure already exists. Before committing to a choice in a project, check the current versions and supported browsers in the documentation for each tool.

🚀 What You Can Do from the Command Line

For simple tasks, scripts are not needed. According to the Chrome Flags documentation, the following commands are available in headless mode and in chrome-headless-shell (replace chrome with the path to Chrome on your system):

Serialized DOM

chrome --headless --dump-dom https://example.com/

This is not the same as downloading HTML via curl. Chrome first parses the HTML into a DOM, executes scripts that might modify it, and then serializes the DOM back into a string. So you see the page after JavaScript has run.

Screenshot

chrome --headless --screenshot --window-size=412,892 https://example.com/

The file screenshot.png will appear in the current directory, and --window-size sets the window size (e.g., for mobile).

PDF

chrome --headless --print-to-pdf --no-pdf-header-footer https://example.com/

Saves the page as output.pdf; the --no-pdf-header-footer flag removes headers and footers with dates and addresses.

Waiting

  • --timeout=5000 — maximum wait time (ms) after which content is captured, even if the page is still loading.
  • --virtual-time-budget=42000 — "rewinds" time for the page's code (setTimeout, setInterval): the browser executes it as quickly as possible, and the page believes that time has actually passed.

🧪 What It's Used For Legally

  • Automated Tests. Chrome developers explicitly call headless a tool for automated testing and server environments where an interface is not needed: you can run tests on a real page, create its PDF, or check how the browser renders it.
  • Generating PDFs and Images: invoices, reports, social media previews from HTML templates.
  • Monitoring: regularly opening key pages and checking if they are actually displayed after JavaScript execution.
  • SEO Audits: comparing raw HTML with the page after rendering (see the next section).
  • Collecting Public Data, if site rules allow (see the ethics section).

📈 Headless and SEO: How Search Engines Render JavaScript

For SEO specialists, a headless browser is also important because search engines use similar technology. Google explains that Google Search executes JavaScript using an up-to-date version of Chromium. Googlebot takes a URL from the crawl queue, reads robots.txt, and queues pages for rendering. When resources allow, headless Chromium renders the page and executes JavaScript. If the page or its scripts are blocked in robots.txt, Google will not execute that JavaScript.

This leads to practical conclusions:

  • Do not block JS and CSS files needed for page rendering in robots.txt. I wrote about what robots.txt does and doesn't do in the article "Robots.txt: A Complete Guide for SEO and Site Optimization".
  • Rendering strategy affects performance and reliability. The web.dev article states that server-side rendering with hydration can improve speed and user experience, and pages should be regularly tested to ensure their content is accessible to Googlebot.
  • Quick local check. The --dump-dom command shows the DOM after script execution. This brings you closer to what the renderer sees, but does not replace checking in Google Search Console.

🐳 Running in Docker and CI

On servers and in CI pipelines, headless Chrome runs without a graphical environment. According to the Chrome documentation FAQ, a virtual display like Xvfb is not needed because there is no window. It also states that the --no-sandbox flag is not needed if the user in the container is configured correctly. Do not disable the sandbox unless necessary: it reduces security, especially when the browser opens untrusted pages. (The FAQ page is old, so check the recommendations for your Chrome version.)

To avoid downloading the full browser, you can download a lightweight version from Chrome for Testing using the Puppeteer utility:

npx @puppeteer/browsers install chrome-headless-shell@stable

👤 How Headless Chrome Appears to a Website

From a website's perspective, headless Chrome is a client that loads a page, executes its scripts, and runs everything on it, including analytics tags. That's why GA4 doesn't distinguish it from a human by default: it only filters out known bots from a list. I've detailed how this looks in reports, how to confirm suspicions in Cloudflare, and what to do about it in the article "Suspicious Traffic in GA4: How Bots with Headless Browsers Masquerade as Humans and How to Detect Them". You can also read about the differences between a bot and a real user based on logs here: "How to Distinguish a Bot from a Real User: A Complete Guide with Log Examples".

A practical detail for website owners: your own autotests and monitoring that access production with a GA4 tag will also be included in the statistics. Exclude them (e.g., through internal traffic or developer traffic) to avoid polluting the data.

⚖️ Ethics and Usage Rules

  • robots.txt is not permission. Standard RFC 9309 explicitly states that these rules are not a form of authorization. The file asks bots to behave politely, but technically prohibits nothing. More details: guide to robots.txt.
  • Do not overload other people's sites. Limit request frequency, do not run hundreds of tabs in parallel.
  • Read the site's terms of use and consider your country's data collection laws. This is not legal advice, but a reminder to check the rules before you start.
  • Do not use headless browsers for artificial inflation of traffic, clicks, or ad views. This violates advertising network rules and can lead to account blocking.

⚠️ Common Problems

  • Dynamic content did not load in time. The snapshot was taken before loading was complete. On the command line, --timeout and --virtual-time-budget help; in libraries, wait for specific elements or events.
  • Different results in headless vs. windowed mode. Differences can be due to window size, fonts, settings, and launch mode. Therefore, set the window size explicitly (--window-size) and reproduce the problem in the same mode where it occurred.
  • Unstable tests. A common cause is reliance on timing. Instead of "sleeping" for a certain amount of time, wait for an explicit condition.
  • Server resources. Each browser instance consumes memory and processor, so close the browser after finishing work and limit parallelism.

Read more about the limitations of headless mode and its differences from regular Chrome in a separate article: Headless Chrome Limitations: Common Production Issues.

❓ Frequently Asked Questions (FAQ)

How does Headless Chrome differ from regular Chrome?

Starting with Chrome 112, it's the same browser: it creates, but doesn't display windows, and all other functions are available. The difference is primarily that there is no visible interface, and control is programmatic. I will describe the details and behavioral differences in the article "Headless Chrome Limitations".

Is Xvfb needed for headless Chrome?

No. According to the Chrome documentation FAQ, there is no window, so a virtual display is not needed.

What to choose: Puppeteer or Playwright?

Puppeteer is convenient for quick Chrome automation in Node. Playwright is worth choosing when you need Chromium, Firefox, and WebKit in a single API or clients for Python, .NET, or Java.

Can I use headless Chrome with Java?

Yes: Playwright has an official Java client, and Selenium supports Java among its primary languages.

Is there a difference between chrome-headless-shell and the --headless mode?

Yes. --headless launches the real Chrome without a window, while chrome-headless-shell is a separate lightweight binary with an older implementation that has fewer dependencies.

✅ Conclusions

  • 🔹 Headless Chrome is a real browser without a visible interface. Since Chrome 112, its mode has been unified with regular Chrome.
  • 🔹 The old version is now available as a separate chrome-headless-shell: lighter, but less "authentic".
  • 🔹 It is controlled via CDP, WebDriver BiDi, or libraries like Puppeteer, Playwright, and Selenium. The choice depends on the language, browsers, and existing infrastructure.
  • 🔹 For quick tasks, the command line is sufficient: --dump-dom, --screenshot, --print-to-pdf.
  • 🔹 For SEO, it's important to understand that Google also renders pages through headless Chromium, so don't block resources needed for display.
  • 🔹 For a website, a headless browser looks like a visitor, so also refer to the article about bots in GA4 and use the tool responsibly.

📎 Sources

Important: Chrome versions, flags, and browser support in Puppeteer, Playwright, and Selenium change. Check the current documentation before use. The material is current as of October 2026.