Back to Blog
Deployment

Headless Browser Setup on Ubuntu: Complete Server Guide

How to set up headless browser automation on Ubuntu with Xvfb, system dependencies, systemd services, and production configuration.

Introduction

Running BotBrowser on a headless Ubuntu server is the foundation of most production deployments. Servers do not have physical displays, GPU drivers vary from desktop systems, and Chrome has specific library dependencies that are not installed by default on minimal server images. Getting these details right is the difference between a stable production environment and intermittent crashes that are difficult to diagnose.

This guide covers everything from installing system dependencies to configuring Xvfb (X Virtual Frame Buffer), setting up systemd services for persistence, and running BotBrowser with Playwright or Puppeteer in headless mode. By the end, you will have a reliable Ubuntu server that runs BotBrowser instances unattended.

Why Headless Server Setup Matters

Desktop environments handle display management, GPU initialization, and font rendering automatically. Server environments strip all of this away. A minimal Ubuntu Server installation lacks the shared libraries that Chrome needs for rendering, the X11 display server that even headless Chrome expects to be present, and the font packages that affect text rendering.

Without proper setup, you encounter errors that range from obvious ("cannot open display") to subtle (segfaults during WebGL rendering, blank Canvas output, or missing font families in fingerprint checks). Each of these issues can silently degrade your fingerprint protection without any clear error message.

The DISPLAY environment variable is particularly important. Even when running Chrome in headless mode, Chrome still initializes some display-related subsystems. On Ubuntu, DISPLAY=:10.0 must be set and Xvfb must be running on that display. Skipping this step leads to crashes or degraded rendering that affects fingerprint consistency.

Technical Background

Xvfb (X Virtual Frame Buffer)

Xvfb provides a virtual display server that implements the X11 protocol without requiring physical display hardware. Chrome connects to Xvfb as if it were a real display, completing its rendering initialization correctly.

Key configuration parameters:

  • Display number (:10): An arbitrary identifier. Using :10 avoids conflicts with :0 which may be used by desktop installations.
  • Screen specification (1920x1080x24): Width, height, and color depth. 24-bit color depth is required for accurate rendering. Lower depths cause color banding in screenshots and incorrect Canvas output.
  • DISPLAY environment variable (DISPLAY=:10.0): Must be set for every process that launches Chrome, including Node.js scripts and shell scripts.

Chrome System Dependencies

Chrome depends on dozens of shared libraries for rendering, audio, networking, and accessibility. On a desktop Ubuntu installation, most of these are present. On a server installation, you need to install them explicitly.

The critical categories are:

  • Graphics: libdrm2, libgbm1, libxcomposite1, libxdamage1, libxrandr2 for display compositing
  • UI toolkit: libgtk-3-0, libatk-bridge2.0-0, libatk1.0-0 for accessibility and widget rendering
  • Security: libnss3, libnspr4 for TLS and certificate handling
  • Audio: libasound2 for audio subsystem initialization (even when not playing audio)
  • Fonts: fonts-liberation for basic font availability
  • Desktop integration: xdg-utils for MIME type handling

<svg viewBox="0 0 700 300" xmlns="http://www.w3.org/2000/svg" style={{maxWidth: '100%', height: 'auto'}}> Headless Server Architecture BotBrowser Chrome + Profile Headless Mode Xvfb :10 Virtual Display 1920x1080x24 System Libs GTK, NSS, GBM Fonts, Audio Ubuntu 22.04 LTS (Headless) DISPLAY=:10.0 set in environment

Common Approaches and Limitations

Running Without Xvfb

Some guides suggest running Chrome headless without Xvfb. While Chrome's --headless flag does not strictly require an X display for basic page loading, certain rendering operations, GPU initialization paths, and font enumeration routines behave differently without a display server. The result is inconsistent rendering that affects fingerprint stability.

BotBrowser profiles define specific display properties. Without Xvfb, the browser may fall back to software rendering paths that produce different Canvas and WebGL output than what the profile specifies. Always run Xvfb.

Missing Font Packages

Ubuntu Server ships with minimal font support. Chrome can render text without additional fonts, but the available font list and rendering metrics will not match what a real desktop user would have. This creates a fingerprint inconsistency. BotBrowser's cross-platform font engine mitigates this by embedding fonts from the profile, but having system fonts available as a fallback improves rendering consistency.

Incorrect Display Depth

Using Xvfb :10 -screen 0 1920x1080x16 (16-bit color) instead of 24-bit causes color depth issues. Canvas rendering uses fewer color channels, producing different pixel data and different fingerprint hashes. Always use 24-bit depth.

Running as Root Without Sandbox Flags

Chrome's sandbox requires specific kernel capabilities. In Docker containers or when running as root, the sandbox fails silently or crashes. Use --disable-setuid-sandbox in these environments.

BotBrowser's Approach

BotBrowser is designed for headless server deployments. Its cross-platform profile system ensures that a Windows profile loaded on a Linux server produces the same fingerprint as on an actual Windows machine. The embedded font engine renders text using the profile's font data regardless of what fonts are installed on the server. GPU fingerprint values come from the profile, not from the server's actual GPU.

This means the server's hardware does not leak into the fingerprint. A $5 cloud VPS and a $500 dedicated server produce identical output when using the same profile.

However, the server must still provide the rendering infrastructure that Chrome expects. BotBrowser cannot initialize correctly without the system libraries listed above and a running display server.

Configuration and Usage

Step 1: Install System Dependencies

sudo apt-get update && sudo apt-get install -y \
  wget ca-certificates fonts-liberation \
  libasound2 libatk-bridge2.0-0 libatk1.0-0 \
  libcups2 libdbus-1-3 libdrm2 libgbm1 \
  libgtk-3-0 libnspr4 libnss3 \
  libxcomposite1 libxdamage1 libxrandr2 \
  xdg-utils xvfb

For Ubuntu 24.04, some package names have changed. If you encounter errors, run:

sudo apt-get install -y \
  libasound2t64 libatk-bridge2.0-0 libatk1.0-0 \
  libcups2t64 libgbm1 libgtk-3-0t64 \
  libnss3 libxcomposite1 libxdamage1 \
  libxrandr2 xvfb fonts-liberation xdg-utils

Step 2: Start Xvfb

For immediate testing:

Xvfb :10 -screen 0 1920x1080x24 &
export DISPLAY=:10.0

For production, create a systemd service:

# /etc/systemd/system/xvfb.service
[Unit]
Description=X Virtual Frame Buffer
After=network.target

[Service]
Type=simple
ExecStart=/usr/bin/Xvfb :10 -screen 0 1920x1080x24
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

Enable and start the service:

sudo systemctl daemon-reload
sudo systemctl enable xvfb
sudo systemctl start xvfb

Step 3: Install BotBrowser

# Download the latest release
wget -O botbrowser.tar.gz \
  https://github.com/botswin/BotBrowser/releases/latest/download/botbrowser-linux-x64.tar.gz

# Extract to /opt
sudo mkdir -p /opt/botbrowser
sudo tar -xzf botbrowser.tar.gz -C /opt/botbrowser/
sudo chmod +x /opt/botbrowser/chrome

# Verify installation
DISPLAY=:10.0 /opt/botbrowser/chrome --version

Step 4: Download Profiles

sudo mkdir -p /opt/profiles
git clone https://github.com/botswin/BotBrowser-Profiles.git /opt/profiles

Step 5: Test Launch

DISPLAY=:10.0 /opt/botbrowser/chrome \
  --bot-profile="/opt/profiles/windows-chrome-131.enc" \
  --headless \
  --remote-debugging-port=9222 &

Verify the browser is running:

curl -s http://localhost:9222/json/version | python3 -m json.tool

Step 6: Playwright Integration

npm install playwright-core
const { chromium } = require('playwright-core');

(async () => {
  const browser = await chromium.launch({
    executablePath: '/opt/botbrowser/chrome',
    args: [
      '--disable-setuid-sandbox',
      '--bot-profile=/opt/profiles/windows-chrome-131.enc',
      '--proxy-server=socks5://user:pass@proxy.example.com:1080',
    ],
    headless: true,
  });

  const context = await browser.newContext();
  const page = await context.newPage();
  await page.goto('https://example.com');
  console.log('Title:', await page.title());
  await browser.close();
})();

Run with the display variable:

DISPLAY=:10.0 node script.js

Step 7: Systemd Service for Automation

For persistent automation workers:

# /etc/systemd/system/botbrowser-worker.service
[Unit]
Description=BotBrowser Automation Worker
After=xvfb.service
Requires=xvfb.service

[Service]
Type=simple
Environment=DISPLAY=:10.0
WorkingDirectory=/opt/scripts
ExecStart=/usr/bin/node /opt/scripts/worker.js
Restart=always
RestartSec=10
User=botbrowser
Group=botbrowser

[Install]
WantedBy=multi-user.target

Multiple Instances

Each concurrent instance needs its own --user-data-dir:

for i in $(seq 1 5); do
  DISPLAY=:10.0 /opt/botbrowser/chrome \
    --bot-profile="/opt/profiles/profile-${i}.enc" \
    --user-data-dir="/tmp/bb-session-${i}" \
    --remote-debugging-port=$((9222 + i)) \
    --headless &
done

Process Management with PM2

npm install -g pm2

DISPLAY=:10.0 pm2 start worker.js --name "bb-worker-1" \
  -- --profile="/opt/profiles/profile-1.enc"

DISPLAY=:10.0 pm2 start worker.js --name "bb-worker-2" \
  -- --profile="/opt/profiles/profile-2.enc"

pm2 save
pm2 startup  # Generate systemd auto-start

Verification

After completing the setup, verify everything works:

# Check Xvfb is running
systemctl status xvfb

# Check display is accessible
DISPLAY=:10.0 xdpyinfo | head -5

# Check Chrome dependencies are satisfied
ldd /opt/botbrowser/chrome | grep "not found"

# Launch and verify fingerprint
DISPLAY=:10.0 node -e "
const { chromium } = require('playwright-core');
(async () => {
  const b = await chromium.launch({
    executablePath: '/opt/botbrowser/chrome',
    args: ['--bot-profile=/opt/profiles/profile.enc'],
    headless: true,
  });
  const p = await (await b.newContext()).newPage();
  const ua = await p.evaluate(() => navigator.userAgent);
  console.log('User-Agent:', ua);
  const wd = await p.evaluate(() => navigator.webdriver);
  console.log('webdriver:', wd);
  await b.close();
})();
"

The webdriver value should be false, and the User-Agent should match the loaded profile.

Best Practices

Always set DISPLAY=:10.0. Add it to /etc/environment or your service files so it is never forgotten. Even headless Chrome needs it on Linux.

Use 24-bit color depth for Xvfb. Lower depths produce incorrect rendering output.

Monitor disk usage. Chrome writes crash dumps and cache data to --user-data-dir. Set up log rotation or periodic cleanup to prevent disk exhaustion.

Keep dependencies updated. Run apt-get upgrade periodically. Library version mismatches can cause subtle rendering issues.

Set resource limits. Use systemd's MemoryLimit and CPUQuota to prevent runaway instances from consuming all server resources.

Frequently Asked Questions

Does BotBrowser work on Ubuntu 24.04?

Yes. Some package names have changed (e.g., libasound2 became libasound2t64). The alternative install command in Step 1 covers these changes.

Can I use a higher Xvfb resolution?

Yes. You can set Xvfb :10 -screen 0 2560x1440x24 or any resolution you need. Match it to the profile's screen resolution for best results.

Do I need a GPU on the server?

No. BotBrowser uses the profile's GPU values for fingerprint reporting. The server's actual GPU (or lack thereof) does not affect the fingerprint output.

Why not just use --headless=new without Xvfb?

Chrome's new headless mode still initializes display subsystems. Without Xvfb, some rendering paths fall back to software-only mode, which can produce different Canvas and WebGL output. For consistent fingerprint results, always run Xvfb.

How many instances can I run per server?

It depends on server resources. Each Chrome instance uses 200-500 MB of RAM. A server with 16 GB RAM can comfortably run 20-30 instances with room for the OS and supporting services. Monitor memory usage and reduce concurrency if you see swap activity.

How do I check missing libraries?

Run ldd /opt/botbrowser/chrome | grep "not found". Any libraries listed as "not found" need to be installed. Use apt-file search libname.so to find the package that provides a specific library.

Can I run BotBrowser on ARM-based Ubuntu servers?

BotBrowser provides x86_64 Linux builds. ARM support depends on the specific release. Check the GitHub releases page for available architectures.

How do I update BotBrowser?

Download the latest release and extract it over the existing installation. No configuration changes are needed. Update profiles from the BotBrowser Profiles repository at the same time to match the new browser version.

Summary

Setting up BotBrowser on a headless Ubuntu server requires system dependency installation, Xvfb configuration, and proper environment variable management. Once configured, the server provides a stable foundation for running any number of BotBrowser instances with consistent fingerprint protection.

For containerized deployments, see Docker Deployment Guide. For performance tuning at scale, see Optimizing BotBrowser Performance in Production. For CLI flag combinations, see CLI Recipes.

#headless#ubuntu#server#deployment#linux