DOCS

Browser sessions

Drive a real cloud browser over CDP with Playwright or Puppeteer, and watch it live in your dashboard. A Browserbase-style primitive backed by a qbox sandbox.

A browser session is a sandbox running headful Chromium that you drive from the outside over the Chrome DevTools Protocol (CDP) — with Playwright, Puppeteer, or any CDP client — and watch live in the dashboard.

wss://<browser_id>.browser.<your-domain>/cdp     # connect your automation here

Automation (CDP) flows straight through the edge proxy to Chromium in the guest; the control plane only handles routing and access, never the bytes.

Create a session

import qbox

browser = qbox.Browsers.create(
    template="tpl_…",                       # a browser-capable template
    viewport={"width": 1280, "height": 720},
    start_url="https://duckduckgo.com",     # the page it opens on
    visibility="public",                    # public | private | signed_url
)
browser.wait_until_ready(timeout=60)
print(browser.connect_url)                  # wss CDP endpoint
print(browser.live_view_url)                # embeddable live view

You need a browser-capable template — build one from the qbox/browser base image (it’s offered in the dashboard’s template-creation screen). A regular sandbox template won’t work.

Connect with Playwright

from playwright.async_api import async_playwright

async with async_playwright() as pw:
    pw_browser = await pw.chromium.connect_over_cdp(browser.connect_url)
    page = await pw_browser.contexts[0].new_page()
    await page.goto("https://example.com")
    print(await page.title())

Puppeteer and any other CDP client connect the same way, using connect_url.

Watch it live

Every session exposes an interactive live view you can embed in an iframe at live_view_url. It streams the browser over WebRTC and lets you click and type directly in the page.

A qbox browser session live view rendering DuckDuckGo

<iframe src="{browser.live_view_url}" allow="autoplay; clipboard-read; clipboard-write" />

live_view_url opens directly — no separate login step. The live view comes up a moment after CDP; if you embed it the instant a session goes ready, pass wait_for_live_view=True to create() (or ?wait=true on the API) so the session isn’t reported ready until the live view is serving. Automation-only callers leave it off and connect as soon as CDP is up.

Visibility

TierWho can open the live view / CDP
public (default)anyone with the URL
privaterequires a qbox dashboard session (needs HTTPS + a shared cookie domain)
signed_urla time-limited signed link — mint_signed_url(ttl=…)

public is the default because private needs the dashboard session cookie to reach the *.browser.<domain> subdomain (HTTPS + QBOX_COOKIE_DOMAIN). Use signed_url for shareable, access-controlled links without that setup.

Lifetime

By default a session ends when the controlling CDP connection disconnects (the Browserbase model). Override it:

browser = qbox.Browsers.create(
    template="tpl_…",
    keep_alive=True,        # survive CDP disconnects
    timeout_seconds=3600,   # hard ceiling (default 1h, max 6h)
)

CLI

qbox browsers create --template <tpl-id> --viewport 1280x720
qbox browsers list
qbox browsers connect-url <browser-id>
qbox browsers live-view <browser-id>
qbox browsers terminate <browser-id>

Self-hosting requirements

CDP automation (connect_url) works out of the box — it rides the same proxy as everything else. The live view streams video over WebRTC, which is UDP and needs a TURN relay (bundled coturn) to cross NAT. The installer wires this automatically; for a manual deploy set, in your .env:

QBOX_TURN_HOST=<your public host or IP>   # same address as the dashboard
QBOX_TURN_PASSWORD=<a strong secret>

and open these UDP ports on the host and any cloud firewall:

  • 3478 — the TURN listener
  • 49160–49200 — the media relay range

Without coturn reachable, automation still works but the live view won’t connect. See Configuration → Browser sessions for the full list of TURN settings.

HTTPS deployments

Browser sessions are served on *.browser.<your-domain> subdomains. If your dashboard is served over HTTPS, those subdomains must be HTTPS too — a browser blocks ws:///http:// content loaded from an HTTPS page (“mixed content”), so the live view and CDP connection fail with a console error like “attempted to connect to the insecure WebSocket endpoint ws://…”.

To serve them over wss:///https://, set in your .env:

QBOX_PROXY_SCHEME=https

then docker compose up -d. The control plane now hands out wss:///https:// URLs, and Caddy obtains a TLS certificate per browser subdomain on demand (HTTP-01, authorized by qbox-proxy) — so you do not need a wildcard certificate or DNS-01. You only need:

  • a wildcard DNS record *.browser.<your-domain> → your host’s IP, and
  • ports 80 and 443 reachable (for certificate issuance + serving).

Each session is a new subdomain (a new certificate), so very high session churn can hit Let’s Encrypt rate limits (certs per registered domain per week); for heavy production use, provision a wildcard cert via DNS-01 instead. Leaving QBOX_PROXY_SCHEME=http (the default) keeps everything on HTTP — fine if you reach the dashboard over HTTP, but an HTTPS dashboard with HTTP subdomains will mixed-content-block the live view.

Troubleshooting. If a browser subdomain fails TLS (tlsv1 alert internal error from curl, EPROTO from a CDP client, or the live view stays blank), on-demand issuance isn’t completing. Check that port 80 is reachable from the public internet (Let’s Encrypt validates over HTTP-01 there), that you’re under the rate limit, and that *.browser.<domain> resolves. If issuance stays flaky, use a wildcard cert (DNS-01) — that sidesteps per-subdomain issuance entirely.