Xvfb: Virtual Displays for Headed Browsers on Servers

Xvfb is an X11 display server that draws into memory. When a headed browser on a server still needs it, when headless mode replaces it, and how to run it.

By Simon Chiu · Updated

Xvfb (X virtual framebuffer) is an X11 display server that keeps the whole screen in memory instead of sending it to a monitor, so programs that need a graphical display, such as a headed browser, can run on a Linux server that has no screen.

Before browsers had a real headless mode, Xvfb was how everyone ran Chrome and Firefox in CI and on servers. Today headless Chrome needs no display at all, so Xvfb is a tool for the cases where you deliberately run the browser with a window. This entry is part of the screenshot glossary.

What it means in plain terms

On Linux, a program with a window does not draw on the screen by itself. It connects to a display server (X11 or Wayland) through the DISPLAY environment variable, for example DISPLAY=:99, and asks it to draw. Xvfb is a display server that does everything a normal X server does, except that its "screen" is a block of memory. Nobody sees it, but the program cannot tell the difference.

flowchart LR
  N["Your script<br/>(Puppeteer, Playwright, Selenium)"] -- "CDP / WebDriver" --> B["Browser, headed mode"]
  B -- "X11 protocol via DISPLAY=:99" --> X["Xvfb<br/>screen in memory"]
  B -- "page.screenshot() pixels" --> N

Note the last arrow. A Puppeteer or Playwright screenshot comes from the browser's own renderer through the automation protocol, not from Xvfb's memory. Xvfb only gives the browser a place to open its window.

When you still need Xvfb

Situation Need Xvfb? Why
Screenshots or PDFs with Puppeteer or Playwright No Use headless mode. It is the same Chrome without a window.
Debugging a page that behaves differently in headless mode Sometimes Run headed under Xvfb on the same server to compare.
Playwright or Selenium tests that must run headed in Linux CI Yes Headed mode on Linux requires a display.
Tools that capture the whole X screen (ffmpeg -f x11grab, xwd) Yes They record the display, not the page.
Desktop apps such as Electron apps in CI Usually Many cannot start without a display.
Production screenshot service No Every Xvfb process is one more thing to start, monitor and kill.

The "behaves differently" row is real but shrinking. Chrome's new headless mode runs the full browser, so most differences people used to blame on headless mode, such as fonts or missing features, now come from the server environment instead.

How to set it up

These commands are for Debian or Ubuntu images. We did not run them for this page: our test machine is macOS, which has no Xvfb, and our Docker host was not available on the test date.

Install it next to the browser's system libraries:

FROM node:22-bookworm-slim
RUN apt-get update \
 && apt-get install -y --no-install-recommends xvfb xauth \
 && rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY package*.json ./
RUN npm ci && npx puppeteer browsers install chrome --install-deps
COPY . .
CMD ["xvfb-run", "-a", "--server-args=-screen 0 1920x1080x24", "node", "shot.mjs"]

xvfb-run starts Xvfb, sets DISPLAY and XAUTHORITY for the child command, and stops the server when the command exits. -a picks a free display number, which matters when several jobs run on one machine. -screen 0 1920x1080x24 sets the virtual screen size and a 24-bit colour depth.

Puppeteer, headed

// shot.mjs — run with: xvfb-run -a node shot.mjs
import puppeteer from 'puppeteer';

if (!process.env.DISPLAY) throw new Error('No DISPLAY: start this script under xvfb-run');

const browser = await puppeteer.launch({ headless: false, args: ['--window-size=1400,1000'] });
try {
  const page = await browser.newPage();
  await page.setViewport({ width: 1280, height: 800 });
  await page.goto('https://example.com', { waitUntil: 'load' });
  await page.screenshot({ path: 'headed.png' });
} finally {
  await browser.close();
}

Playwright, headed

# Playwright's own CLI flag; xvfb-run supplies the display
xvfb-run -a npx playwright test --headed

The same works for a plain script that calls chromium.launch({ headless: false }).

Manual Xvfb, for long-running workers

Xvfb :99 -screen 0 1920x1080x24 -nolisten tcp &
export DISPLAY=:99
node worker.mjs

-nolisten tcp keeps the display off the network. If you start Xvfb this way, your process manager must also stop it; otherwise it outlives the worker, like an orphaned browser (see zombie processes).

RenderScreenshot

There is no display to configure. Captures run in a managed headless Chromium on Cloudflare's browser rendering service, and the API takes the viewport size directly:

curl "https://api.renderscreenshot.com/v1/screenshot?url=https%3A%2F%2Fexample.com&width=1280&height=800" \
  -H "Authorization: Bearer $RS_API_KEY" -o example.png

This request was not run for this page.

Common mistakes

  • Adding Xvfb to fix a headless problem. If a capture is wrong in headless mode, check fonts, waits and the user agent first. A virtual display rarely changes the pixels.
  • A fixed display number with parallel jobs. Two Xvfb :99 servers cannot share the number. Use xvfb-run -a.
  • A screen smaller than the window. Make the virtual screen at least as large as --window-size.
  • Leaving Xvfb running. Use xvfb-run, or stop the server in the same supervisor that starts the worker.
  • Forgetting xauth. xvfb-run needs it, and slim images do not include it.

For full container setups, read Puppeteer in Docker or Playwright in Docker. To capture pages without a browser or display to run, create a free account.

Frequently asked questions

Do I need Xvfb for headless Chrome?
No. Headless Chrome draws without any display server. You need Xvfb only when you run a browser in headed mode (headless: false) on a Linux machine with no screen.
What does 'Missing X server or $DISPLAY' mean?
A headed Chrome on Linux tried to open a window and found no X display. Start the command under xvfb-run, set DISPLAY to a running Xvfb server, or switch the browser to headless mode.
Does the Xvfb screen size change my screenshot size?
Not directly. Puppeteer and Playwright take screenshots from the browser's own renderer, sized by the viewport you set. The virtual screen only needs to be large enough for the browser window.

Related in this topic

Try RenderScreenshot free

150 free credits every month, no credit card required.