Browser automation on machines that can't run a browser
September 2, 2026 · browselenium.online
A full browser is one of the heaviest dependencies you can ask a host to carry. It wants hundreds of megabytes of RAM before it has loaded a page, GUI libraries or an Xvfb framebuffer to render into, a driver binary that must match the browser build exactly, and a permanent treadmill of version updates you have to chase. On a workstation you never notice. On a Raspberry Pi, a serverless function, or a CI runner, it is a non-starter — and on some runtimes it is not even possible in principle.
The usual playbook — install a browser, install a driver, drive it — fails on a long list of machines:
- Raspberry Pi Zero-class boards and 512 MB micro VPSes, where the browser's idle footprint alone exhausts the host.
- AWS Lambda and Google Cloud Functions, which allow no long-lived processes and punish large, slow-loading deployment images.
- Cloudflare Workers, which expose no subprocesses at all — you cannot spawn a browser even if you could fit one.
- Locked-down CI runners where you cannot install system packages or a display server.
- Docker images built from scratch, which ship no libc, no fonts, and no display stack to lean on.
Why shipping the browser fails on small hosts
Suppose you try anyway. Even when a browser technically fits, it makes the host worse at everything else it is supposed to do:
- Image bloat. A browser, its shared libraries, fonts, and a driver turn a 40 MB deployment package into one measured in gigabytes. Every deploy and every scaling event pays that tax again.
- Cold starts. Serverless platforms unpack your image and boot your runtime per invocation. A browser-sized image adds seconds before your first line runs, and the browser process adds more before the first page loads.
- OOM crashes. Browsers assume they can claim memory on demand. On a 512 MB function or a Pi Zero the OOM reaper kills them mid-run, leaving failures that look like network errors and are nothing of the kind.
- Driver version drift. The driver binary has to match the browser build. A silent browser update on the host breaks the pairing, and your cron job starts failing on a Sunday with nobody watching.
- Fingerprint flags. Stripped-down headless builds leak automation signals — exactly the missing pieces bot protection looks for. You do all the engineering and still get blocked at the door.
So the choice looks binary: give up on those hosts, or keep shipping hundreds of megabytes you cannot afford. There is a third option.
The thin-client pattern
Invert the dependency. Instead of putting the browser on your machine, put it on infrastructure built for it — and make your client trivially portable: all it does is make HTTPS calls and receive JSON. That is a dependency any runtime can carry. A Cloudflare Worker can do it. A 20-line Lambda can do it. A Python script on a Pi Zero can do it.
Latency, the standard objection, barely matters for this class of work. Scraping and monitoring are dominated by page loads and bot checks, not by round-trip time to your client. One extra HTTPS hop is noise next to a page that takes two seconds to render and a challenge that takes five.
The part that actually matters is state. The remote browser keeps cookies, history, and open tabs between calls on the server. Your client can also carry state explicitly: get_cookies returns the live cookie jar as JSON, and set_cookies injects one back. That is enough to move a login across invocations — across a serverless cold start, or onto a completely different machine:
# machine A, this morning: log in, save the session anywhere
# (the call shape, shown as pseudo-Python — over MCP these are the tool names)
navigate("https://shop.example.com/login")
type_text("#email", "[email protected]")
type_text("#password", "***")
click("button[type=submit]")
save_to_kv(get_cookies())
# machine B, three days later: cold start, different runtime
set_cookies(load_from_kv())
navigate("https://shop.example.com/orders")
orders = get_text("table.orders")
The call names read like selenium — navigate, find, click, type_text, execute_js, screenshot — and that is deliberate. If you have ever written a webdriver script, the surface is familiar. What you do not carry is the weight: no driver binary to version, no browser process to babysit, no display to provision. The client is a thin layer over HTTPS and runs anywhere HTTPS runs.
What this unlocks
- Price monitoring from a Raspberry Pi Zero cron job. The board wakes, makes a handful of HTTPS calls, sleeps. No memory pressure, no packages to update, no framebuffer.
- Scheduled scraping in Lambda with zero browser image. The deployment package stays in the kilobytes, cold starts stay fast, and nothing gets reaped mid-run.
- SERP checks from Cloudflare Workers. A runtime with no subprocess support can still fetch, parse, and act on search results on a schedule, with cookies persisted between runs.
- End-to-end checks from CI without browsers in the image. The runner stays lean and the checks run against a real browser that behaves like a real one — including on the bot-protected pages that break naive headless setups.
- An agent on a tiny VPS browsing through MCP. Point the agent at https://browselenium.online/mcp and it gets navigate, get_text, find, click, type_text, execute_js, get_cookies, set_cookies, and screenshot as tools. Full browsing, no browser on the box.
So which remote browser should you use?
The remote-browser market has three shapes, and they are not equivalent:
- Hosted webdriver grids. Real remote browsers, but you still manage capabilities and driver pairing, and the sessions are vanilla automation profiles that modern bot checks recognize and block.
- Consumer "browser APIs". REST wrappers around scraping endpoints. Fine for one-shot extraction, but opinionated: you give up low-level control — precise clicks, arbitrary JS execution, cookie surgery — and you are no longer writing selenium-style code.
- browselenium.online. Selenium-style control over HTTPS/MCP against a real, human-like browser in the cloud — one built to pass bot checks, not to trip them.
The human-like browser is the difference that matters. Everything else in this post only works if the pages you need actually load, and on bot-protected sites most remote stacks fail exactly where your local headless setup did. browselenium.online runs a real, human-like browser in the cloud, so what you get back is what a person would see.
It works for scripts and agents alike. Scripts drive the same calls over raw MCP. Agents get a hosted MCP endpoint at https://browselenium.online/mcp — opencode, openclaw, Claude Code, and Cursor all speak MCP, so setup is one prompt: point your agent at https://browselenium.online/agent-install.md and it wires itself up. The flow is the same either way: authenticate, browser_start, navigate, then get_text, find, click, or type_text, then browser_stop.
Pricing starts free: $0 gets you 50 requests/day and 1 minute of browser life. Pro is $5/mo for 500 requests/day and a 5 minute browser life; pro+ is $7/mo for 750 requests/day and 10 minutes; premium is $25/mo for 2000 requests/day, 15 minutes, and selenium-wire support. The free tier is enough to prove the whole pattern on whichever machine you assumed could never run a browser — because it can't, and now it doesn't have to.
Your agent read enough. Time to let it drive.
Create a free browselenium.online account and give it selenium-level browser control from any device — 50 requests a day on us, Stripe-billed upgrades when the scrapers get serious.
Create your free accountor compare the plans first