Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

Use Chrome’s explicit headless flag for your version, select an ANGLE renderer, and give the container enough shared memory. For Chrome 109 and later, start with --headless=new; for Chrome 96–108, use --headless=chrome. In a container without a usable GPU, the most portable setup is SwiftShader:

--use-gl=angle
--use-angle=swiftshader-webgl
--enable-unsafe-swiftshader

The last switch reduces security guarantees and is appropriate only for controlled test workloads. If your container exposes a working Vulkan driver, use the Vulkan configuration instead of SwiftShader. WebGL can still fail to create a context, so verify it in the page and provide a fallback.

Choose the rendering path before changing flags

Headless Chrome does not automatically mean hardware acceleration. Chromium’s headless path normally forces SwiftShader; --enable-gpu stops that forcing and lets Chrome attempt normal driver selection, but it does not create GPU access that the container does not have.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Environment Renderer and flags Prerequisites and trade-offs
No usable GPU in the container --use-gl=angle --use-angle=swiftshader-webgl --enable-unsafe-swiftshader Works as a software fallback; the unsafe switch lowers security and should be limited to trusted test pages.
SwiftShader without the unsafe WebGL mode --use-gl=angle --use-angle=swiftshader Prefer this when it initializes your workload; compatibility can differ by Chrome build and page.
Container with an exposed Vulkan stack --use-angle=vulkan --enable-features=Vulkan --disable-vulkan-surface Requires usable Vulkan libraries, driver access and device exposure. It may provide hardware acceleration, but Chrome still can fall back or block the driver.

Compare approaches on five dimensions: renderer, security posture, performance, container prerequisites and Chrome-version portability. There is no authoritative general performance number: rendering speed depends on the page, Chrome build, CPU, GPU and container limits.

Use the correct headless flag

Chrome 96–108

Use --headless=chrome. Chrome introduced the new headless implementation in version 96, but this explicit spelling was used through Chrome 108.

Chrome 109 and later

Use --headless=new. Do not silently assume that a flag copied from an older image still selects the same implementation; print the browser version in CI and keep the flag aligned with it.

Inject flags through Selenium Docker

The docker-selenium images accept SE_BROWSER_ARGS_* environment variables. Each value is passed as a browser argument in standalone and node containers. Allocate two gigabytes of shared memory as recommended for browser containers; otherwise Chrome can crash or lose tabs under load.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Chrome 109+ with SwiftShader

docker run -d --name selenium-webgl --shm-size=2g 
  -e SE_BROWSER_ARGS_HEADLESS=--headless=new 
  -e SE_BROWSER_ARGS_GL=--use-gl=angle 
  -e SE_BROWSER_ARGS_ANGLE=--use-angle=swiftshader-webgl 
  -e SE_BROWSER_ARGS_SWIFTSHADER=--enable-unsafe-swiftshader 
  selenium/standalone-chrome:latest

The suffixes after SE_BROWSER_ARGS_ are labels; the value is the complete switch. Avoid putting two switches in one variable unless the image documentation for your pinned version explicitly supports that format. Pin an image tag in production rather than relying on latest, and record the Chrome version with each test run.

Chrome 96–108

Replace only the headless variable:

-e SE_BROWSER_ARGS_HEADLESS=--headless=chrome

Keep the ANGLE and SwiftShader variables unchanged. If you use a Selenium Grid, apply the same variables to the node that launches Chrome, not only to the distributor or hub.

Vulkan-capable container

When the container genuinely has a working Vulkan path, use:

docker run -d --name selenium-vulkan --shm-size=2g 
  -e SE_BROWSER_ARGS_HEADLESS=--headless=new 
  -e SE_BROWSER_ARGS_ANGLE=--use-angle=vulkan 
  -e SE_BROWSER_ARGS_VULKAN=--enable-features=Vulkan 
  -e SE_BROWSER_ARGS_SURFACE=--disable-vulkan-surface 
  selenium/standalone-chrome:latest

Exposing a device node alone is not enough: the image also needs compatible Vulkan libraries and permissions. If ANGLE cannot initialize Vulkan, remove these switches and return to SwiftShader while you diagnose the driver.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Configure Selenium directly in Python

This is equivalent to the Docker environment variables and is useful when you control the driver process yourself:

from selenium import webdriver
from selenium.webdriver.chrome.options import Options

options = Options()
# Chrome 109+; use --headless=chrome for Chrome 96–108.
options.add_argument("--headless=new")
options.add_argument("--use-gl=angle")
options.add_argument("--use-angle=swiftshader-webgl")
options.add_argument("--enable-unsafe-swiftshader")
# Needed only when your container runs Chrome as root.
options.add_argument("--no-sandbox")
# A fallback for small /dev/shm; prefer Docker's --shm-size=2g.
options.add_argument("--disable-dev-shm-usage")

driver = webdriver.Chrome(options=options)
try:
    driver.get("https://example.com")
    print(driver.title)
finally:
    driver.quit()

--no-sandbox and --disable-dev-shm-usage address common container constraints; neither enables WebGL. Running without Chrome’s sandbox increases risk, so use it only when the container’s user and sandbox setup require it. Prefer a two-gigabyte shared-memory mount before relying on the latter flag.

Verify that a WebGL context really exists

Run this check after navigation, from JavaScript executed in the same WebDriver session. A non-null context is the meaningful result; a renderer string is diagnostic only.

const canvas = document.createElement('canvas');
const gl = canvas.getContext('webgl') ||
           canvas.getContext('experimental-webgl');
const result = gl ? {
  webgl: true,
  renderer: gl.getExtension('WEBGL_debug_renderer_info')
    ? gl.getParameter(
        gl.getExtension('WEBGL_debug_renderer_info')
          .UNMASKED_RENDERER_WEBGL)
    : 'unreported'
} : {webgl: false};
console.log(result);

With Selenium Python:

result = driver.execute_script("""
const canvas = document.createElement('canvas');
const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
if (!gl) return {webgl: false};
const ext = gl.getExtension('WEBGL_debug_renderer_info');
return {webgl: true, renderer: ext
  ? gl.getParameter(ext.UNMASKED_RENDERER_WEBGL)
  : 'unreported'};
""")
print(result)
if not result["webgl"]:
    raise RuntimeError("WebGL context creation failed")

Applications should handle a null context with a visible fallback or an explicit test failure. Browsers do not guarantee WebGL availability, even when the flags are accepted.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Diagnose the renderer and backend

Read Chrome’s GPU diagnostics

In a headed or debug session, open chrome://gpu. It can show whether ANGLE selected SwiftShader, Vulkan or a blocked driver. A renderer string containing “SwiftShader” confirms software rendering; it does not prove that a hardware GPU is being used when the string is different.

Turn on browser logging

Add --enable-logging while investigating backend selection, then collect the Chrome and driver logs from the container. Remove verbose logging from routine runs if logs may contain page URLs or request metadata.

Check the container itself

  • Confirm that the Chrome binary and driver versions are compatible.
  • Check /dev/shm size and container memory before blaming WebGL.
  • For Vulkan, verify device visibility, library versions and user permissions inside the same container that launches Chrome.
  • Make sure the flags are attached to the browser process. A flag placed on a Grid hub does not automatically reach a node.

Common failures and fixes

“WebGL is disabled” or the context is null

First verify the headless flag for the Chrome version. Then use the ANGLE SwiftShader set in a GPU-less container. Test the page-level context check rather than relying on a user-agent or browser-version check. If it still fails, inspect chrome://gpu and enable logging.

Chrome exits immediately in Docker

Increase shared memory with --shm-size=2g. If Chrome runs as root, add --no-sandbox only when your container isolation policy permits it. --disable-dev-shm-usage can avoid a small shared-memory mount, but it may use slower disk-backed temporary storage.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

SwiftShader works but performance is poor

That is expected for CPU rendering, especially with large canvases, 3D scenes or many parallel sessions. Reduce concurrent browsers, lower the viewport or scene complexity, and wait for the specific rendering milestone instead of using a long arbitrary delay. Move to Vulkan only after validating that the container’s driver path is real and stable.

Unsafe SwiftShader is rejected by policy

Remove --enable-unsafe-swiftshader and try standard SwiftShader with --use-angle=swiftshader. If the page requires the unsafe WebGL path, run it in a dedicated, controlled test environment and treat the security reduction as an explicit deployment decision.

Vulkan flags make WebGL worse

Those switches are not a software fallback. If the container lacks a compatible Vulkan stack, ANGLE may fail or fall back unexpectedly. Return to SwiftShader, then validate Vulkan separately with the image’s libraries, device access and permissions.

The renderer is “unreported”

WEBGL_debug_renderer_info is optional. The extension may be unavailable for privacy or implementation reasons. Treat the successful context as the pass condition and use browser logs or chrome://gpu for backend diagnosis.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Make tests reliable

  • Pin the Selenium image and record Chrome, ChromeDriver and Selenium versions.
  • Use a fixed viewport and device scale factor when pixel output matters.
  • Wait for a selector, application-ready signal or rendering promise rather than sleeping for a guessed number of milliseconds.
  • Run a small WebGL smoke test before a larger suite so backend failures are reported separately from application failures.
  • Limit parallel sessions according to CPU and memory; SwiftShader consumes host CPU and can contend with other tests.
  • Keep the browser sandbox enabled whenever possible and isolate any workload that requires unsafe SwiftShader or root execution.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Or skip the browser setup

If your goal is a clean website image or PDF rather than testing WebGL inside your own browser, ScreenshotNeo provides a website screenshot API. One request can return PNG, JPEG, WebP or PDF without maintaining Selenium containers:

cURL

curl -G "https://api.screenshotneo.com/v1/shot" 
  -d access_key=YOUR_API_KEY 
  --data-urlencode url=https://stripe.com 
  -o shot.webp

See the ScreenshotNeo documentation for the complete option set. It can load lazy images, capture an element by CSS selector, set dark mode, choose device presets or any viewport, use retina scale, export PDFs, inject CSS or JavaScript, click before capture, hide selectors, wait for a selector, delay or network idle, block ads and trackers, send custom headers/cookies/user agents, set timezone or geolocation, resize images, cache with a chosen TTL, create signed links, run asynchronous jobs with signed webhooks, capture up to 100 URLs per call, and expose usage and OpenAPI endpoints. Parameter names used by other screenshot APIs also work for easier migration.

Before capture, ScreenshotNeo accepts cookie and consent banners and removes more than 60 known consent platforms, newsletter popups and chat widgets; each cleanup step can be disabled. Bot checks, blank pages, timeouts, failed loads and cache hits are not billed, and response headers identify the page verdict and billing result. Its MCP server exposes take_screenshot, get_page_info and capture_pdf to Claude, Cursor and other MCP clients.

The Free plan includes 1,000 shots per month with no card. Paid plans start at $5 for 3,000 shots; all features are included on every plan. Create a free ScreenshotNeo account.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Frequently asked questions

Does --enable-gpu guarantee hardware WebGL?

No. It only stops headless Chrome from forcing SwiftShader so normal driver selection can be attempted. The container still needs a usable GPU and compatible driver stack.

Can I use Xvfb instead?

This setup does not require Xvfb. Chrome’s headless modes run without a virtual display; Xvfb is a separate headed-browser strategy with different operational requirements.

Is a successful WebGL context proof of fast rendering?

No. It proves that the page obtained a context. SwiftShader can be functional but substantially slower than a correctly exposed hardware renderer.

Should production services enable unsafe SwiftShader?

Only when the workload is controlled and the security trade-off is accepted. For untrusted browsing, prefer a secure renderer path or fail safely rather than treating the unsafe switch as universal.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Frequently Asked Questions

Which flag should I use on Chrome 108?

Use --headless=chrome; switch to --headless=new for Chrome 109 and later.

Why does WebGL work locally but not in Selenium Docker?

The container may lack shared memory, a GPU/Vulkan stack, or the correct headless and ANGLE switches. Verify the context inside the container and inspect chrome://gpu or browser logs.

What does a SwiftShader renderer string mean?

It indicates software rendering. It confirms the selected renderer, not hardware acceleration.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.