Run a Headless X Display with Xvfb and Check It Safely
A GUI test suite running on a server with no screen is normal work for Xvfb, an X server that draws into memory instead of glass. You will start a disposable X display with no physical monitor, point a test or batch job at it, verify the connection, and stop it cleanly afterwards. That covers software that needs X11 but is running on a server, in a build job, or over an ordinary SSH session.
The route
Jump straight to the step you need, or tick off Done means at the end.
The examples use Xvfb from Ubuntu package xvfb version 2:21.1.12-1ubuntu1.6. Its installed manual describes the X server as xorg-server 21.1.11, so treat the local command and manual as the final authority if your package version differs.
- Time: about fifteen minutes.
- You need: a shell, the
xvfbpackage, and a client such asxdpyinfofor the verification step. The server normally runs as your user, so none of this needssudo.
1. Check the local installation
Confirm which executable is being used and note the package version. Both checks are read-only:
$ command -v Xvfb
/usr/bin/Xvfb
$ dpkg-query -W -f='${Package} ${Version}\n' xvfb
xvfb 2:21.1.12-1ubuntu1.6
Do not use Xvfb -version as a version check: this build treats it as an unknown server option and just prints the normal X server usage text. The package query and the manual's header are the useful sources here.
2. Choose an unused display number
An X display name such as :99 is a local server number, not a network address. Xvfb listens on that display, and clients find it through the DISPLAY environment variable. Pick a number that is not already claimed by another X server:
$ DISPLAY_NUM=:99
$ if DISPLAY="$DISPLAY_NUM" xdpyinfo >/dev/null 2>&1; then
> echo "display already answers: $DISPLAY_NUM"
> exit 1
> fi
$ echo "using $DISPLAY_NUM"
using :99
Checkpoint
If the command reports the display already answers, choose another number. Do not kill an unknown X server just to make this example fit; it may belong to another user or service entirely.
3. Start Xvfb with an explicit screen
Start screen 0 at 1280 by 1024 pixels and 24-bit depth. Xvfb's own default is already 1280x1024x24, but writing it explicitly makes test runs easier to review later:
$ LOG_FILE="/tmp/xvfb${DISPLAY_NUM#:}.log"
$ Xvfb "$DISPLAY_NUM" -screen 0 1280x1024x24 >"$LOG_FILE" 2>&1 &
$ XVFB_PID=$!
$ sleep 1
$ if ! kill -0 "$XVFB_PID" 2>/dev/null; then
> echo "Xvfb stopped; log follows:"
> sed -n '1,80p' "$LOG_FILE"
> exit 1
> fi
$ echo "Xvfb PID: $XVFB_PID"
Xvfb PID: 12345
The PID is an example and will differ every time. If startup fails, read the log before retrying. A common cause is a display-number collision; another is a malformed -screen value, which must follow the WxHxD format with width, height and depth as integers.
Keep the PID in the same shell session as the work using the display. Close the shell without arranging cleanup, and the server can keep running until something else stops it.
4. Point a client at the display
Export DISPLAY for the test or command that needs X11, then ask xdpyinfo to make a harmless connection:
$ export DISPLAY="$DISPLAY_NUM"
$ xdpyinfo | sed -n '1,12p'
name of display: :99
version number: 11.0
vendor string: The X.Org Foundation
vendor release number: 12101011
X.Org version: 21.1.11
maximum request size: 16777212 bytes
motion buffer size: 256
Your release or formatting may differ. What matters is that the command exits cleanly, names the display you chose, and reports an X11 server. If xdpyinfo says it cannot open the display, check DISPLAY, the PID, and the startup log in that order.
Now run the real client in the same environment. For a script, a narrowly scoped variable is usually preferable:
$ DISPLAY="$DISPLAY_NUM" /path/to/your-test --headless-x11
Swap the placeholder for a real program and its documented arguments. Xvfb provides an X server and virtual framebuffer; it does not make a graphical program automatically safe, fast, or compatible with every hardware-accelerated feature it might expect.
5. Stop the temporary server
Once the client has finished, stop the process you started and confirm it has actually gone:
$ kill "$XVFB_PID"
$ for attempt in 1 2 3 4 5; do
> if ! kill -0 "$XVFB_PID" 2>/dev/null; then break; fi
> sleep 1
> done
$ if kill -0 "$XVFB_PID" 2>/dev/null; then
> echo "Xvfb did not stop; inspect before using force"
> exit 1
> fi
$ echo "stopped $XVFB_PID"
stopped 12345
That is the undo for the whole example. If the process ignores the normal termination signal, inspect it with ps -p "$XVFB_PID" -o pid,stat,cmd before reaching for anything forceful. Never kill -9 a PID you have not checked. The log file in /tmp is ordinary temporary data and can go once you no longer need it.
Useful framebuffer options
-screen can create another screen too, for example -screen 0 1600x1200x24. Specify screen 1 as well, and screen 0 still keeps its default configuration unless you set it explicitly. Most clients expect screen 0, so configure that one first.
Reach for -pixdepths only when a client or test genuinely needs additional pixmap depths. Its argument is a space-separated list of integers from 1 to 32, such as -pixdepths 3 27. Do not confuse these extra pixmap depths with the screen depth already set in -screen.
-fbdir DIRECTORY changes the storage model entirely. Xvfb creates one memory-mapped XWD framebuffer file per screen, named like Xvfb_screen0. That supports a file copy for a full-screen snapshot. Choose a directory you control, and check its free space and permissions before starting. These files are state produced by the running server, not a substitute for a proper screenshot tool.
-shmem instead puts the framebuffer in System V shared memory and prints a shared-memory ID for each screen. Treat that output as diagnostic data for whatever tool consumes it, not as a secret or a stable identifier. Neither -fbdir nor -shmem is needed for the ordinary client-testing workflow above.
Done means
- Installation confirmed: the package and executable were identified.
- Display checked, not assumed: the number was free before you started anything.
- Started deliberately: Xvfb ran with an explicit screen size and depth.
- Connection verified:
xdpyinfoconnected through the intendedDISPLAY. - Real client ran: the test or batch job completed in that environment.
- Cleanup confirmed: the recorded Xvfb PID was stopped and checked afterwards.