How to Install and Run Chromium in Headless Mode
Install Chromium for headless use, run command-line captures, automate with Puppeteer or Selenium, and troubleshoot common setup errors.
Short answer: install Chromium or Chrome for Testing for your operating system, then launch the browser executable with --headless. Add --dump-dom to inspect the rendered DOM or --screenshot to save an image. For repeatable automation, use Puppeteer or Selenium.
The exact installation command depends on your operating system and distribution. Identify the browser executable first; names and paths differ between Windows, macOS, Linux distributions, containers and CI runners. The examples below assume Chromium or Chrome is already installed and available as chromium, chromium-browser or google-chrome.
1. Choose the headless implementation
Current Chrome uses a unified implementation: headless and headful modes share the normal Chrome browser code. Since Chrome 132, the former implementation is distributed separately as chrome-headless-shell; --headless=old no longer selects it in the regular Chrome binary. See the Chrome Headless documentation and the Chromium Headless README.
| Choice | Use it when | Puppeteer setting |
|---|---|---|
| Unified Chrome Headless | You need the regular browser feature set and behavior | headless: true |
chrome-headless-shell |
You specifically need the standalone shell and accept differences from full Chrome | headless: 'shell' |
2. Verify the executable
Run the version command for the binary installed on your machine:
chromium --version
chromium-browser --version
google-chrome --version
Use the command that succeeds in the remaining examples. If none succeeds, install Chromium or Chrome for Testing using your operating system’s current official instructions, then rerun the version check. Avoid copying a package command from another distribution: package names and repository policies vary.
3. Run a direct headless smoke test
The Chromium project documents launching headless Chrome with remote debugging enabled:
chromium --headless --remote-debugging-port=9222 https://example.com
Replace chromium with your executable name. The browser runs without opening a visible window and listens for DevTools connections on port 9222. You can then control it through the Chrome DevTools Protocol. Keep the debugging port private unless you have deliberately secured it.
4. Inspect the rendered DOM
Use --dump-dom to print the serialized DOM after the page has loaded and scripts have executed:
chromium --headless --dump-dom https://example.com
This output is not the original HTTP response. It is the DOM Chrome produced after parsing and running page scripts, which makes it useful for checking client-rendered content.
5. Capture a screenshot from the command line
Use --screenshot to save an image in the current working directory:
chromium --headless --screenshot https://example.com
Set a viewport with --window-size when the default dimensions are not appropriate:
chromium --headless --window-size=1280,800 --screenshot=example.png https://example.com
Check the flags supported by your installed browser version before relying on a particular output filename or additional command-line option. A page that needs time to fetch data, render fonts or load images may require automation code that waits for a condition before taking the screenshot.
6. Install and run Puppeteer
Puppeteer is the simplest Node.js route when you want browser control rather than one-off command-line output. The puppeteer package normally downloads a compatible Chrome for Testing and a chrome-headless-shell binary during installation. The documented approximate download sizes are 170 MB on macOS, 282 MB on Linux and 280 MB on Windows. See the Puppeteer installation guide.
mkdir headless-demo
cd headless-demo
npm init -y
npm install puppeteer
Create capture.mjs:
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch({
headless: true
});
try {
const page = await browser.newPage();
await page.setViewport({ width: 1280, height: 800, deviceScaleFactor: 1 });
await page.goto('https://example.com', { waitUntil: 'networkidle2' });
await page.screenshot({ path: 'example.png', fullPage: true });
} finally {
await browser.close();
}
Run it with:
node capture.mjs
Use the shell binary explicitly
const browser = await puppeteer.launch({ headless: 'shell' });
Choose headless: true for unified Chrome. Choose headless: 'shell' only when the standalone shell is the deliberate target.
Use an existing browser with puppeteer-core
puppeteer-core does not download Chrome. It is appropriate when your deployment manages the browser version, uses a system package, or connects to a remote browser.
npm install puppeteer-core
import puppeteer from 'puppeteer-core';
const browser = await puppeteer.launch({
headless: true,
executablePath: '/path/to/your/chrome'
});
try {
const page = await browser.newPage();
await page.goto('https://example.com', { waitUntil: 'networkidle2' });
await page.screenshot({ path: 'example.png' });
} finally {
await browser.close();
}
Replace the path with the executable location on your machine. If package installation scripts are blocked, install Puppeteer’s browsers separately with npx puppeteer browsers install, as described in the installation documentation.
7. Run Chromium with Selenium
Selenium can pass the headless flag through Chrome options. The browser driver and browser must be installed and compatible according to your Selenium environment.
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
options = Options()
options.add_argument('--headless')
options.add_argument('--window-size=1280,800')
driver = webdriver.Chrome(options=options)
try:
driver.get('https://example.com')
driver.save_screenshot('example.png')
finally:
driver.quit()
8. Wait for real page state
Headless mode changes visibility, not the page’s loading behavior. Single-page applications, lazy images, web fonts and API calls may finish after the initial response. Use a browser automation wait that matches your page:
- Wait for a selector that proves the main content exists.
- Use a bounded delay only when the page has no reliable selector.
- Use a network-idle condition when background requests eventually settle.
- Set an explicit viewport so responsive layouts are reproducible.
Avoid waiting forever. Give navigation, selector waits and the overall job a timeout, then record the URL and failure reason.
9. Common errors and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
command not found |
The executable is not installed or is not on PATH |
Install Chromium/Chrome for your OS, locate the binary and use its full path. |
| Puppeteer cannot find a browser | You installed puppeteer-core without managing a browser |
Use puppeteer, run npx puppeteer browsers install, or set executablePath. |
| Installation has no browser download | Package-manager install scripts are disabled | Fetch the browser explicitly with Puppeteer’s browser-install command and configure the cache/path. |
| Blank or incomplete screenshot | Capture occurred before client rendering or lazy loading completed | Wait for a content selector, network idle or a bounded delay; verify the viewport. |
| Fonts or images differ in CI | The runtime lacks the same browser resources as development | Use a pinned browser build and install the fonts/resources your page requires in that environment. |
| Browser exits immediately | Unhandled startup error, incompatible executable or restricted runtime | Run the executable directly with a version check, capture stderr, then test the same binary under the automation library. |
| Remote debugging is unreachable | Port binding or network access is blocked | Bind and expose the port deliberately, keep it private, and confirm the process is still running. |
| Old headless flag fails | --headless=old was removed from the regular Chrome binary |
Use unified --headless or install and select chrome-headless-shell. |
10. Performance, reliability and cost considerations
- Startup: launching a new browser for every URL is expensive. Reuse one browser process and create or close pages per job.
- Concurrency: limit simultaneous pages to the CPU and memory available. Unbounded parallelism causes timeouts and crashes.
- Determinism: pin the browser version, viewport, timezone and user agent when screenshots are compared over time.
- Reliability: record navigation errors, timeout reasons, final URLs and browser stderr. Retry transient navigation failures with a bounded policy.
- Storage: screenshots and Puppeteer’s downloaded browsers consume disk space; cache the browser between CI runs when your environment permits.
- Cost: self-hosting consumes your compute, storage and maintenance budget. A managed API can move browser operations out of your application.
11. Or skip the browser setup
ScreenshotNeo provides a website screenshot API and MCP server. One GET request returns PNG, JPEG, WebP or PDF, while the service handles browser setup and capture options. Read the ScreenshotNeo API documentation for the complete parameter list.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
Cookie and consent banners, newsletter popups and chat widgets are removed before the shot. Bot checks, blank pages and failed loads are not billed, and response headers identify the page verdict and billing status. ScreenshotNeo also supports an MCP server for AI agents, including Claude and Cursor. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 screenshots. Create a free ScreenshotNeo account.
12. Frequently asked questions
Is Chromium headless the same as incognito?
No. Headless controls whether a visible browser window is shown. Incognito is a separate browsing context and privacy mode.
Does --dump-dom return the original HTML?
No. It prints the serialized DOM after Chrome parses the page and runs scripts.
Should I use Puppeteer or Selenium?
Use Puppeteer for a Node-first workflow with convenient browser downloads and Chrome DevTools Protocol access. Use Selenium when your project already follows the WebDriver model or uses its language bindings.
When should I use puppeteer-core?
Use it when your team controls the browser installation or connects to a remote browser. It deliberately does not download Chrome.
Can headless Chrome render JavaScript applications?
Yes. It runs page scripts, but your automation must wait for the application’s content before reading the DOM or taking a screenshot.


