Run Chrome Headless (New) for Automated Testing and Rendering
Launch Chrome 112+ without a UI, connect through WebDriver, and verify screenshots or PDF output before running your test suite.
21 Mar 2026, 08:25 UTC

The problem and the useful takeaway
You need to run browser tests or generate PDFs on a server with no attached display. A normal Chrome window refuses to start without a graphical session, and launching Chrome without the correct flags can leave orphan processes or exhaust shared memory in CI. The useful takeaway is to start Chrome 112 or later with --headless=new, open the DevTools debugging port, connect through WebDriver, and verify the debugging endpoint before executing your suite.
Prerequisites
- Chrome 112+ or Chromium installed. Confirm with
google-chrome --version. - ChromeDriver whose major version matches Chrome exactly. Confirm with
chromedriver --version. - A scripting environment such as Node.js, Python, or Java with a WebDriver client library.
- Terminal access on Linux or macOS. Windows paths and binary names differ.
Launch Chrome in new headless mode
Run this command in a dedicated terminal session. It starts Chrome without a visible window and exposes the DevTools Protocol on port 9222.
google-chrome \
--headless=new \
--disable-gpu \
--no-sandbox \
--disable-dev-shm-usage \
--remote-debugging-port=9222 \
--user-data-dir=/tmp/chrome-headless-profile
Where to run: a Linux or macOS terminal. Permissions: a standard user is preferred. Use --no-sandbox only when running as root or inside an unprivileged container; it weakens process isolation. Placeholders: replace /tmp/chrome-headless-profile with a temporary directory you can delete afterward; replace 9222 if another service already uses that port.
Flag meanings:
| Flag | Purpose |
|---|---|
--headless=new | Uses Chrome's modern headless engine (Chrome 112+), which supports more web platform features than the legacy mode. |
--disable-gpu | Disables GPU acceleration, which is usually unnecessary in headless environments. |
--no-sandbox | Allows Chrome to run as root or in containers without user namespace support. |
--disable-dev-shm-usage | Writes shared memory files to /tmp instead of /dev/shm, helping avoid out-of-memory kills on small /dev/shm partitions. |
--remote-debugging-port=9222 | Opens the DevTools Protocol endpoint so clients and health checks can inspect the browser. |
Expected check: Chrome starts silently and returns control to the shell. No browser window appears.
Risk: --disable-dev-shm-usage can consume disk space in /tmp under heavy use. --no-sandbox reduces security, so do not use it for untrusted web content on a multi-user host.
Verify the debugging endpoint
From another terminal, query the DevTools JSON endpoint:
curl -s http://localhost:9222/json/version | head -c 400; echo
Expected result: JSON output containing a Browser field with the Chrome version. If the command fails with connection refused, Chrome did not start, crashed, or is listening on a different port. Check running processes with pgrep -af chrome and inspect the port with ss -ltnp | grep 9222.
Run a minimal WebDriver script
This Node.js example uses Selenium WebDriver to launch Chrome, open a page, and print the title. It assumes selenium-webdriver and a matching ChromeDriver are installed.
const {Builder, By, until} = require('selenium-webdriver');
const chrome = require('selenium-webdriver/chrome');
(async () => {
const options = new chrome.Options()
.addArguments('--headless=new')
.addArguments('--disable-gpu')
.addArguments('--no-sandbox')
.addArguments('--disable-dev-shm-usage');
const driver = new Builder()
.forBrowser('chrome')
.setChromeOptions(options)
.build();
try {
await driver.get('https://example.com');
const title = await driver.getTitle();
console.log('Page title:', title);
} finally {
await driver.quit();
}
})();
Expected result: The script prints Page title: Example Domain and exits without leaving Chrome processes behind. If session creation fails, confirm that ChromeDriver's major version matches Chrome's major version exactly.
Diagnostic decision: if the script starts but page navigation hangs, try --headless=old instead of --headless=new. The newer engine supports more features but can render some pages differently than a headed browser.
Capture output and clean up
To save a screenshot in the same script, add this before driver.quit():
const screenshot = await driver.takeScreenshot();
require('fs').writeFileSync('screenshot.png', screenshot, 'base64');
After the run, confirm no orphaned Chrome processes remain:
pgrep -f "chrome --headless=new"
Expected result: no matching PIDs. If processes remain, note the PID and run kill <PID>. Avoid pkill -f chrome on a shared desktop because it can terminate an interactive browser session. Remove the temporary user data directory when you are done:
rm -rf /tmp/chrome-headless-profile
Expected checks summary
| Check | Command or action | Expected result |
|---|---|---|
| Chrome version | google-chrome --version | Chrome 112 or later |
| ChromeDriver alignment | chromedriver --version | Same major version as Chrome |
| Debugging endpoint | curl http://localhost:9222/json/version | JSON with a Browser field |
| Navigation | WebDriver script opens example.com | Page title contains Example Domain |
| Process cleanup | pgrep -f "chrome --headless=new" | No matching PIDs after the run |
Limitations and fallback options
- Rendering differences:
--headless=newbehaves differently from legacy headless. Compare a headless screenshot against a headed baseline if visual fidelity matters. - Version mismatch: ChromeDriver must match Chrome's major version exactly; mismatches cause session creation failures.
- Containers and root:
--no-sandboxis often required in containers, but running as a dedicated non-root user is safer. - Memory limits: CI environments with small
/dev/shmcan trigger OOM kills. Use--disable-dev-shm-usageor increase shared memory, and monitor with--enable-precise-memory-infoif needed. - Limited APIs: File system access and some WebGL features remain restricted in headless mode. Test critical paths explicitly.
- Fallback: if the new headless engine crashes or fails a compatibility test, retry with
--headless=old. As a last resort, run Chrome with Xvfb as a virtual display.
Practical verification
Before adopting this in a pipeline, run the version checks, launch Chrome with the flags above, curl the /json/version endpoint, execute the minimal WebDriver script, and confirm pgrep returns no leftover processes. If any step fails, treat the configuration as unverified for your environment and adjust flags or versions accordingly.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.