A headless browser is a web browser that runs without a visible window and is controlled by a program, so it can load pages, run their JavaScript and produce screenshots, PDFs or page data with no person at the screen.
The engine inside is the same one desktop users run: Blink in Chromium, Gecko in Firefox, WebKit in Safari. Only the window is missing. That makes a headless browser the standard tool for screenshot services, end-to-end tests, crawlers that need rendered HTML, and AI agents that browse. This entry is part of the screenshot and rendering glossary.
A plain explanation
A normal browser has two jobs: compute the page, and show it to you. A headless browser does only the first job. It still downloads the HTML, applies the CSS, runs the scripts, lays out every element and paints pixels into memory. Your code then asks for the result: "give me a PNG of the first 800 pixels", "print this to PDF", "what text is in this element?".
How it works technically
Your script talks to the browser over a debugging protocol. For Chromium that is the Chrome DevTools Protocol (CDP), a JSON message channel over a WebSocket or a pipe. WebDriver BiDi is the newer cross-browser standard, which Firefox and recent Chrome support. A command such as "capture a screenshot" makes the renderer paint the current frame into an offscreen buffer, encode it, and send it back as base64.
Because nothing is shown, a headless browser needs no display server and fits in a container or a serverless function. It still needs the same CPU, memory and fonts as a desktop browser, which is where most production trouble starts.
How fast is a headless capture?
We launched each of Playwright's three bundled engines, loaded a small local test page and took a 1280 x 800 screenshot, three runs each. The machine was an Apple silicon Mac that was running other jobs at the same time, so treat the numbers as relative, not absolute.
| Engine (Playwright 1.64.1) | Version | Launch, median ms | Load + capture, median ms | PNG bytes |
|---|---|---|---|---|
| Chromium (headless shell) | 156.0.8078.4 | 82 | 82 | 69,503 |
| WebKit | 27.2 | 202 | 369 | 86,959 |
| Firefox | 157.0 | 504 | 774 | 80,947 |
Two lessons came out of the run. First, the browser launch is a large share of a single capture, which is why services keep browsers warm and reuse them. Second, the three engines produced three different files from the same page, because each one rasterises text and anti-aliasing its own way. If you compare screenshots over time, pin one engine and version.
How to run one in Puppeteer, Playwright and RenderScreenshot
Puppeteer launches Chrome headless by default.
import puppeteer from 'puppeteer'; const browser = await puppeteer.launch(); try { const page = await browser.newPage(); await page.goto(process.argv[2] ?? 'https://example.com'); console.log(await page.title(), '|', await page.evaluate(() => navigator.userAgent)); await page.screenshot({ path: 'headless.png' }); } finally { await browser.close(); }
It printed Example Domain and a user agent that contains HeadlessChrome/148.0.7778.97, the token many sites use to spot automation.
Playwright drives three engines with one API. Each launch is headless unless you pass headless: false.
import { chromium, firefox, webkit } from 'playwright'; const url = process.argv[2] ?? 'https://example.com'; for (const engine of [chromium, firefox, webkit]) { const browser = await engine.launch(); // headless is the default try { const page = await browser.newPage({ viewport: { width: 1280, height: 800 } }); await page.goto(url); await page.screenshot({ path: `${engine.name()}.png` }); console.log(engine.name(), browser.version(), 'ok'); } finally { await browser.close(); } }
RenderScreenshot runs the headless browser for you. We render with headless Chromium on Cloudflare's Browser Rendering platform, so a capture is one HTTP request and there is no browser process on your servers to launch, patch or restart.
curl --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 \ --output page.png
Tested on 2026-10-15 with Node 22.18, Puppeteer 24.43.1 (run in headless shell mode) and Playwright 1.64.1. The Playwright loop was run against a local test page. The curl request follows the GET endpoint docs and was not run for this page.
Common mistakes
- Launching a browser per request. Launch time dominates small captures. Reuse one browser and open a new context per job.
- Forgetting
browser.close(). Leaked browser processes are the classic cause of memory growth on screenshot servers. Close infinally. - Assuming a desktop look. Headless defaults can differ from your laptop: fonts, colour scheme, viewport. Set them explicitly.
- Missing fonts on Linux. A slim container often lacks emoji and CJK fonts, so text renders as boxes.
Related terms
- Headless Chrome: Chrome's two headless modes and how to pick one.
- Playwright: the library that drives all three engines above.
- Screenshot API: a hosted headless browser behind an HTTP endpoint.
- Model Context Protocol: how AI agents get a headless browser as a tool.
For the visible differences between headless and windowed Chrome, see headless vs headful Chrome. For hosted options, see the headless browser API roundup. To skip running browsers yourself, create a free account.