Chrome Headless Screenshot: Every Flag, Tested
chrome --headless --screenshot and --print-to-pdf flag by flag: window size, scale, WebP, PDF paper, old vs new headless, and the Linux sandbox error.
Headless Chrome can take a screenshot with no script at all:
chrome --headless --window-size=1280,800 --screenshot=page.png https://example.com
It loads the page, writes a 1280×800 PNG and exits. Swap --screenshot for --print-to-pdf=page.pdf and you get a PDF. This page is the flag reference we wanted while building our own renderer: what each screenshot and PDF flag does in current Chrome, what the defaults are, how the new headless mode differs from the old one, and what breaks on Linux. Each claim below comes from a run, with the output size we got. For a comparison of Chrome with other CLI tools such as shot-scraper and capture-website, see website screenshots from the command line; for the wider choice of tools, see the guide to taking website screenshots with code.
Tested on 2026-10-18. macOS 27.0.1 (Apple M1 Pro) with Google Chrome 155.0.8059.80 and Chrome for Testing
chrome-headless-shell156.0.8078.4. Linux: Debian 12 (bookworm) in Docker with the distribution's Chromium 155.0.8059.79. Target:https://example.comand small local HTML files. Windows was not tested; the Windows commands below are given as paths only.
Where the binary is
| Platform | Binary |
|---|---|
| macOS | "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" |
| Debian, Ubuntu (distribution package) | chromium (Debian) or chromium-browser |
| Linux (Google's package) | google-chrome or google-chrome-stable |
| Windows (not tested) | "C:\Program Files\Google\Chrome\Application\chrome.exe" |
| Any OS, separate download | chrome-headless-shell (no --headless needed) |
On macOS, put the path in a variable once. All commands below use $CHROME:
CHROME="/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" "$CHROME" --version # Google Chrome 155.0.8059.80
Screenshot flags
| Flag | Effect | What we got |
|---|---|---|
--headless |
Run without a window. Same as --headless=new |
Required for the full Chrome binary |
--screenshot |
Write screenshot.png in the current folder |
756×469 PNG with no size set |
--screenshot=page.png |
Choose the file name | Folder must exist |
--screenshot=page.jpg |
JPEG, picked by extension | 59,166 bytes for example.com at 1280×800 |
--screenshot=page.webp |
WebP, picked by extension | 36,008 bytes, a RIFF....WEBPVP8X file |
--window-size=1280,800 |
Viewport size in CSS pixels | Exactly 1280×800 |
--force-device-scale-factor=2 |
Retina output | 2560×1600 from a 1280×800 window |
--hide-scrollbars |
No scrollbar in the image | Matters on tall pages and Linux |
--window-size=1280,2400 |
A tall "full page" by hand | 1280×2400, white below short pages |
--blink-settings=preferredColorScheme=1 |
Force light theme (0 forces dark) |
Needed on a Mac set to Dark |
--timeout=15000 |
Capture after 15 s even if loading continues | Caps hung requests |
--virtual-time-budget=3000 |
Let page timers run 3 s of virtual time first | For content added by setTimeout |
--dump-dom |
Print the rendered DOM to stdout instead | Useful to check what loaded |
Three of these deserve more than a row.
The extension sets the format, with no quality control. Chrome wrote a real WebP for .webp and a real JPEG for .jpg. There is no flag for quality. If you need a quality setting, convert the PNG afterwards or use a library such as Puppeteer, which has a quality option.
--window-size is the viewport here. Through the command line, --window-size=1280,800 gave exactly 1280×800. Through Selenium, the same flag gave a 1280×657 image, because Selenium drives a window that reserves room for hidden browser UI. If you compare CLI output with Selenium output, that 143 px difference is the reason. We measured it in the Selenium screenshots guide.
The default size is not stable. Without --window-size, Chrome 155 wrote 756×469 and chrome-headless-shell wrote 800×600. Always pass the size.
There is no full-page flag. A tall window is the only command-line approach, and it leaves white space under short pages. If you need the real page height, use the DevTools method in full page screenshots in Chrome or a library.
PDF flags
"$CHROME" --headless --no-pdf-header-footer --print-to-pdf=page.pdf https://example.com
| Flag | Effect | What we got |
|---|---|---|
--print-to-pdf |
Write output.pdf in the current folder |
203,687 bytes, with header and footer |
--print-to-pdf=page.pdf |
Choose the file name | |
--no-pdf-header-footer |
Remove the date, title, URL and page number lines | 191,877 bytes |
--print-to-pdf-no-header |
Older flag name, still in many guides | No effect in Chrome 155: 203,687 bytes, header still there |
We converted the default PDF to an image to see the header and footer. The top line held the date and time and the page title; the bottom line held the URL and 1/1. Most people want them gone, so add --no-pdf-header-footer by default.
Paper size comes from CSS. There is no --paper flag. Our default PDF was US Letter, 612×792 points, on a Mac set to a US region. A page with @page { size: A4; margin: 1cm } printed as A4 (595×842 points), and @page { size: A4 landscape } printed as 842×595. If you do not control the page's CSS, you cannot change the paper from the command line. Use the DevTools Page.printToPDF call through a library, which takes paperWidth and paperHeight.
Old headless vs new headless
Until 2023, --headless started a separate, lighter browser built for automation. Chrome 112 added a new headless mode that is the real Chrome browser without a window, and later Chrome versions made it the default and then removed the old mode from the Chrome binary. The old code now ships on its own as chrome-headless-shell.
What we saw in Chrome 155:
--headless / --headless=new |
--headless=old |
chrome-headless-shell |
|
|---|---|---|---|
| Accepted | Yes | Yes, but runs the new mode | Always headless, no flag |
| Output for example.com, 1280×800 | 81,121 bytes | 81,121 bytes, identical | 79,353 bytes |
Default size, no --window-size |
756×469 | 756×469 | 800×600 |
| Follows the OS dark theme (Mac set to Dark) | Yes | Yes | No |
| Median time per capture (6 runs) | 5.67 s | n/a | 0.78 s |
The time column is wall-clock time for the whole command, including browser start and exit, on example.com. The full Chrome binary spent most of that time starting and shutting down its full browser profile. The range for full Chrome was 4.1 to 9.4 s, and for the headless shell 0.55 to 0.91 s.
Use the full binary when you want the screenshot to match what a user's Chrome shows. Use the shell for scripts that capture many pages. Install a pinned shell with:
npx @puppeteer/browsers install chrome-headless-shell@stable --path ./browsers
The command prints the binary path. The chrome-headless-shell glossary entry covers what the shell leaves out.
Exit codes and failures
Chrome's command line reports a failed load in its log, not in its exit code:
$ "$CHROME" --headless --screenshot=bad.png https://nonexistent.invalid ERROR:components/headless/command_handler/headless_command_handler.cc:403] Page load failed: net::ERR_NAME_NOT_RESOLVED $ echo $? 0
No file was written and the exit status was 0. An HTTP 404 page is captured and also exits 0. In a script, delete the output file first and check that it exists and is not empty afterwards. A complete wrapper that does this is in the command-line tools comparison.
On macOS, Chrome 155 also logged CVDisplayLinkCreateWithCGDisplay failed and, for --headless=old, Teardown watchdog expired. Both appeared on successful runs. They are noise, not the cause of a failure.
Linux and Docker
We installed Debian's chromium package in a debian:bookworm-slim container and ran the same commands.
As root, Chromium refuses to start:
$ chromium --headless --screenshot=root.png --window-size=1280,800 https://example.com ERROR:content/browser/zygote_host/zygote_host_impl_linux.cc:103] Running as root without --no-sandbox is not supported. See https://crbug.com/638180. $ echo $? 1
Unlike a failed page load, a sandbox failure does set a non-zero exit code. Adding --no-sandbox made it work: a 1280×800 PNG in 1.31 s wall time. As a non-root user, the container still failed with exit code 1 and No usable sandbox!. The message itself names the fixes: install Debian's chromium-sandbox package, or, on hosts that restrict unprivileged user namespaces, follow Chromium's AppArmor notes. Your options:
- Run as a non-root user and give the sandbox what it needs (Debian's
chromium-sandboxpackage, or a container security profile that allows it). We did not test this path. - Add
--no-sandboxand treat the container as the security boundary: a non-root user, no secrets in the image, and only URLs you trust or have validated.
Read Chromium's sandbox before you pick the second option for user-supplied URLs.
Fonts are missing. The slim image had three font families (DejaVu Sans, Sans Mono and Serif). A test page with Chinese and Japanese text and three emoji rendered the CJK characters as empty boxes and two of the three emoji as boxes. After apt-get install fonts-noto-cjk fonts-noto-color-emoji, everything rendered, and fc-list went from 6 to 37 entries. One emoji, 😀, still came out as a black-and-white DejaVu glyph, because DejaVu has that character and Chrome picked it before the colour font.
FROM debian:bookworm-slim RUN apt-get update \ && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ chromium ca-certificates fonts-noto-cjk fonts-noto-color-emoji \ && rm -rf /var/lib/apt/lists/* RUN useradd --create-home shooter USER shooter WORKDIR /home/shooter
The production failures guide reproduces the other Docker problems: zombie processes, /dev/shm and hung pages.
Windows
We did not run Chrome on Windows for this page. The flags are the same; only the path and the shell quoting differ. In PowerShell the binary path needs the call operator:
& "C:\Program Files\Google\Chrome\Application\chrome.exe" --headless --window-size=1280,800 --screenshot="$PWD\page.png" https://example.com
Use an absolute path for the output file. Check the file exists afterwards, for the exit-code reason above.
When the command line is not enough
The command line has no wait-for-selector, no cookies or headers, no element capture, no full page, no quality setting and no reliable error signal. When you need one of those, you move to Puppeteer, Playwright or Selenium, or to an API. With RenderScreenshot the flags above become parameters of one request, and errors come back as HTTP status codes with a JSON body:
curl -sS --fail-with-body --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 scale=2 \ -d format=webp -d quality=80 -d dark_mode=false \ -o page.webp
The PDF equivalent takes format=pdf with pdf_paper=a4 and pdf_landscape=true, so the paper size no longer depends on the page's CSS. All PDF options are in the PDF parameters. Rendering runs on our servers, so there is no Chrome, no sandbox and no font install on your machine. Create a free account to try it with 150 free credits every month.
Related in this topic
- How to Take Website Screenshots with Code: 6 Ways Take a screenshot of a website programmatically: DevTools, the Chrome CLI, Puppeteer, Playwright, Selenium or an API. Timed runs and code for each.
- Selenium Screenshots in Python, Java and C# (Selenium 4) Take a screenshot with Selenium 4 in Python, Java and C#: page, element, exact viewport and PDF. Same test in three languages, timed, with the fixes.
- Playwright Screenshots: Tested Recipes and Pitfalls Take screenshots with Playwright: page.screenshot options, deviceScaleFactor, locators, masks, measured format sizes, auth, Linux fonts and production setup.
- Puppeteer Screenshots: Tested Recipes for Puppeteer 25 Every Puppeteer screenshot recipe re-run on Puppeteer 25.14 and Chrome 155: viewport, full page, element, clip, waits, formats, auth, blocking and fonts.
- Website Screenshots in Node.js: Puppeteer, Playwright, API Take website screenshots in Node.js 22 with TypeScript and ESM: Puppeteer, Playwright or one fetch call to an API, with measured install cost and tested code.
- Website Screenshots in Python: Selenium, Playwright, API Capture a website as an image in Python with Selenium, Playwright or an API: measured setup and per-shot time, async batches, and fixes for common failures.