Blog

AI Agent on the Web: a Real GPU-Backed Chrome for Claude

Run a Claude agent (Playwright MCP, computer use) in a GPU-backed Chrome on a server: Xvfb vs Xorg dummy, Selenium, Cloudflare bot detection, benchmarks, setup.

AI agent on the web: a real GPU-backed Chrome for Claude

An AI agent that acts on the web (filling in a form, comparing offers, monitoring a dashboard) needs a real browser running continuously, somewhere other than your laptop. Two mistakes get it flagged as a robot from the very first page:

  1. running it in headless mode: Chrome then announces itself as HeadlessChrome in its User-Agent;
  2. running it without a GPU: the graphics card becomes SwiftShader (an emulated GPU), or WebGL disappears entirely.

A desktop PC never has these characteristics. So the rule is simple: a real Chrome, in a window, on a virtual display, with a real GPU.

This article shows how to run Chrome with the GPU for a Claude agent, compares Xvfb and Xorg “dummy”, covers Selenium, and explains what the GPU changes (or doesn’t) against Cloudflare’s bot detection. All measurements are reproducible: the script is public.

The architecture

UML deployment diagram: the Claude agent drives Chromium through Playwright MCP; Chromium displays on Xvfb and renders on the Intel UHD 770 GPU; x11vnc lets you watch the screen through an SSH tunnel

  • The agent (Claude Code or the Claude Agent SDK) decides on the actions;
  • Playwright MCP exposes the browser to the agent as tools (browser_navigate, browser_click, browser_type, browser_snapshot…) and drives Chromium over CDP (Chrome DevTools Protocol);
  • Chromium runs in windowed mode on an Xvfb virtual display, and renders on the GPU via ANGLE/Vulkan;
  • x11vnc lets you watch the screen and take over (for two-factor authentication, for example).

How an action unfolds:

UML sequence diagram: browser_navigate, Page.navigate over CDP, request to the site, possible Cloudflare checks, GPU rendering, snapshot returned to the agent

The measurements

Conditions

  • Linux GPU test server, VM configured like the Perf 2 plan (4 vCPU), Intel UHD 770 GPU shared between several VMs (the VM has its own GPU device, /dev/dri, with the standard Linux driver), Debian 13;
  • Chromium 156, Playwright MCP 0.0.83, Selenium 4.31 + chromedriver 156, Xvfb (Xorg 21) and xserver-xorg-video-dummy 0.4.0;
  • the GPU was not used by other VMs during the measurements;
  • measured on 2026-10-29.

How it’s computed

The benchmark is a WebGL 2 page (webgl-bench.html):

  1. a 1920×1080 canvas;
  2. a fragment shader that computes 64 sines/cosines per pixel, about 265 million sines/cosines per image;
  3. ?draws=N draws N full-screen images per frame (requestAnimationFrame);
  4. after each frame, readPixels reads one pixel: this forces the browser to finish rendering, otherwise it could queue the commands and report a flattering but wrong score;
  5. after 5 s: images/s = number of images ÷ duration. We also check that the program linked correctly and that the pixel read has the right color.

Chrome is launched with --disable-gpu-vsync --disable-frame-rate-limit so it isn’t capped at 60 images/s. CPU load is the difference of the /proc/stat counters over the test duration only, expressed as busy vCPUs.

chromium --headless=new --use-angle=vulkan --ignore-gpu-blocklist \
  --disable-gpu-vsync --disable-frame-rate-limit \
  --enable-logging=stderr --v=0 "file:///root/webgl-bench.html?draws=10" \
  2>&1 | grep -m1 RESULT

Result 1: the GPU makes the difference (×20)

--headless=new, 10 images per frame:

Rendering1080p images/sBusy CPU
SwiftShader (default, CPU)133.9 of 4 vCPU
UHD 770, --use-angle=vulkan~2600.6 vCPU
UHD 770, --use-angle=gl-egl~230—

With the GPU, rendering is 20 times faster and the processor stays free. For an agent, this is the key point: without a GPU, a heavy page (map, dashboard, video) saturates all 4 vCPUs, and the agent, Playwright and the virtual display fight over what’s left.

Result 2: Xvfb or Xorg dummy, it doesn’t matter

You often hear that Xvfb should be replaced by Xorg with the dummy driver to “look more real”. Measured, in windowed mode, 1 image per frame:

Virtual displayNo flagsSwiftShader forcedGPU (--use-angle=vulkan)
XvfbWebGL disabled9.2 images/s67.6 images/s
Xorg + dummy driverWebGL disabled8.7 images/s58.8 images/s, then WebGL context lost
  • Neither uses the GPU by itself: glxinfo reports llvmpipe (software rendering) on both;
  • without flags, Chrome disables WebGL on both;
  • with the dummy driver, our GPU run lost the WebGL context after a few images (eglCreateWindowSurface failed with error EGL_BAD_MATCH). Xvfb stayed stable.

The choice of virtual display is therefore secondary: the GPU is what matters for performance, most of the time. Stick with Xvfb, which is simpler, and add the two GPU flags to Chrome.

Result 3: which renderer the site sees

This is what a detection script reads (WEBGL_debug_renderer_info):

ConfigurationReported WebGL renderer
--headless=new without flagsSwiftShader (emulated CPU)
Xvfb or dummy, without flagsno WebGL
Selenium + Xvfb, without flagsno WebGL
Selenium + Xvfb, GPU flagsIntel(R) UHD Graphics 770
Playwright MCP, windowed on Xvfb, GPU flagsIntel(R) UHD Graphics 770
headless, GPU flagsIntel(R) UHD Graphics 770

Result 4: headless = spotted immediately

What a JavaScript script reads in one line on the page, in the four configurations (Chromium 156):

Headless without GPUHeadless + GPUXvfb window without GPUXvfb window + GPU
navigator.userAgent❌ HeadlessChrome/156…❌ HeadlessChrome/156…✅ Chrome/156…✅ Chrome/156…
Graphics card (WebGL)❌ SwiftShader✅ Intel UHD Graphics 770❌ none (WebGL off)✅ Intel UHD Graphics 770
screen❌ 800×600❌ 800×600✅ 1920×1080✅ 1920×1080
Window size780×580780×580945×10601919×1079
  • Headless mode gives itself away: the word HeadlessChrome is written in the User-Agent, sent with every request. It is the simplest signal to block, and every anti-bot system does. The 800×600 screen confirms the diagnosis;
  • without a GPU, even in a window, WebGL disappears: no modern desktop browser is in that situation;
  • only the last column looks like a real workstation: a windowed Chrome on a 1080p screen, with a real Intel graphics card.

Patching the User-Agent by hand is not enough: headless mode has other differences (screen size, rendering behavior, missing APIs), and a User-Agent that doesn’t match the rest is itself suspicious. The most reliable option is not to use headless mode at all: on a server, that is exactly what Xvfb is for.

Result 5: in a window, the GPU takes a huge load off the CPU

Windowed mode has a cost: Chrome has to draw the whole page on screen. Without a GPU, the processor does everything. Same 1080p WebGL animation, Chrome in a window on Xvfb, normal frame rate (60 images/s maximum), CPU load measured across the whole VM during the test:

Chrome in a window on XvfbImages/sBusy CPU (4 vCPU VM)
Without GPU (SwiftShader)9.83.4 vCPU: the VM is saturated
Without GPU, 3× heavier scene4.03.7 vCPU
With the UHD 770 GPU60 (smooth)0.3 vCPU
With the GPU, 3× heavier scene600.3 vCPU

With the GPU, Chrome displays 6 times more images while using more than 10 times less CPU, i.e. about 70 times less CPU per image. Without a GPU, the VM is saturated and doesn’t even reach 10 images/s: the agent, Playwright and the rest of the system fight over the crumbs, every action takes longer, and heavy pages become unusable. With the GPU, 3.7 vCPUs remain free for useful work.

That’s the winning combination: windowed mode avoids getting spotted, and the GPU makes it free for the processor.

What the GPU changes against Cloudflare

Cloudflare (Bot Management, Turnstile) combines many signals: TLS and HTTP/2 fingerprint, IP address reputation, in-page JavaScript checks, behavior (pace, movements), statistical models. A browser without a GPU quickly gives itself away in the JavaScript checks: SwiftShader as the graphics card, or no WebGL at all, is a typical signature of an automated browser in a datacenter.

With the GPU, Chrome reports a real Intel card and renders the page like a desktop PC. In practice, this reduces false positives: CAPTCHAs or blocks on sites your agent is perfectly entitled to visit.

But let’s be clear: CDP + driver + good IP does NOT make you undetectable.

  • CDP automation itself leaves detectable traces;
  • datacenter addresses are known as such;
  • an agent’s behavior (speed, sequence of actions) can be spotted;
  • the rules change constantly: what passes today will be blocked tomorrow.

The right approach: get recognized

For a legitimate agent, the sustainable path is to be identified rather than hidden:

  • Web Bot Auth (“signed agents” at Cloudflare): the agent signs its HTTP requests with an Ed25519 key (HTTP Message Signatures, RFC 9421) and publishes its public key in a directory; sites can then explicitly allow it;
  • an honest User-Agent, respect for robots.txt and terms of service, a reasonable request rate;
  • on your own sites behind Cloudflare (E2E tests, monitoring), a WAF rule that lets your server’s IP through.

Setup

1. Packages

apt install chromium xvfb x11vnc nodejs npm

2. Virtual display and VNC (systemd)

# /etc/systemd/system/xvfb.service
[Unit]
Description=Xvfb :99
[Service]
ExecStart=/usr/bin/Xvfb :99 -screen 0 1920x1080x24 -nolisten tcp
Restart=always
[Install]
WantedBy=multi-user.target
# /etc/systemd/system/x11vnc.service
[Unit]
Description=x11vnc on :99 (localhost only)
After=xvfb.service
Requires=xvfb.service
[Service]
ExecStart=/usr/bin/x11vnc -display :99 -localhost -forever -shared -nopw
Restart=always
[Install]
WantedBy=multi-user.target
systemctl enable --now xvfb x11vnc
# from your computer:
ssh -L 5900:localhost:5900 root@your-server   # then a VNC client on localhost:5900

VNC only listens on localhost: it is reachable only through the SSH tunnel.

3. Playwright MCP with the GPU

File /root/mcp.json:

{
  "browser": {
    "browserName": "chromium",
    "launchOptions": {
      "executablePath": "/usr/bin/chromium",
      "headless": false,
      "args": ["--use-angle=vulkan", "--ignore-gpu-blocklist", "--no-sandbox"]
    }
  }
}

With Claude Code installed on the server:

claude mcp add playwright --env DISPLAY=:99 -- \
  npx @playwright/mcp@latest --config /root/mcp.json

Or as an HTTP server, if the agent runs elsewhere (SSH tunnel to the port):

DISPLAY=:99 npx @playwright/mcp@latest --config /root/mcp.json --port 8931
# MCP endpoint: http://localhost:8931/mcp

To check, ask the agent to open a page that shows the WebGL renderer: it should report Intel(R) UHD Graphics 770.

4. Selenium + Xvfb

apt install python3-selenium chromium-driver
from selenium import webdriver
from selenium.webdriver.chrome.service import Service

o = webdriver.ChromeOptions()
o.binary_location = "/usr/bin/chromium"
for a in ["--use-angle=vulkan", "--ignore-gpu-blocklist",
          "--no-sandbox", "--window-size=1920,1080"]:
    o.add_argument(a)
driver = webdriver.Chrome(options=o, service=Service("/usr/bin/chromedriver"))
xvfb-run -a -s "-screen 0 1920x1080x24" python3 my_script.py

With Debian’s Selenium 4.31, specify chromedriver explicitly via Service(...): otherwise it can’t find it (NoSuchDriverException).

FAQ

Headless or windowed on Xvfb? Windowed, without hesitation. Headless announces itself as HeadlessChrome and gets blocked on most protected sites. Windowed mode also lets you follow the agent over VNC and take over. Keep headless for your own tests, on your own sites.

What about Claude’s computer use (screenshots + clicks)? It needs a real display: Xvfb + a windowed browser, exactly the setup above. The GPU renders each screenshot faster and leaves the CPU for everything else.

Is a Perf 1 plan enough? For an agent browsing ordinary sites, yes. The measurements above were made with 4 vCPU (Perf 2), useful if the agent opens several tabs or heavy pages.

Try it

Linux GPU server with an Intel UHD 770 inside the VM, Mesa and Vulkan already installed. From €14.99 excl. VAT/month, root access, dedicated IPv6. See also: Chrome with the GPU on a Linux server and Xvfb: a practical guide.