Linux Browser Runtime
Register and operate an EthanKit Browser Runtime on Linux with four-session capacity, private-network access, live streaming, and Human in the Loop.
When to use it
A registered Browser Runtime keeps the EthanKit model loop in the API while browser MCP calls, Chromium, live frames, and human handoff run on a Linux machine you control. Use it when:
- The target is reachable only from a corporate network or approved egress IP.
- Sign-in state must persist in a dedicated browser profile.
- Login, MFA, CAPTCHA, or approval requires Human in the Loop (HIL).
- One machine needs to host several isolated browser sessions.
The Runtime initiates its connection to the EthanKit API. A normal deployment needs outbound HTTPS/WSS only. It does not require a public IP or an inbound development, CDP, or VNC port.
Prerequisites
Ubuntu 22.04/24.04 x86_64 is recommended. For four concurrent sessions, start with 8 vCPU, 16 GiB RAM, and 30 GiB of free disk, then load-test representative pages. Video-heavy sites and large profiles need more resources. A GPU is optional.
Prepare:
- Node.js 24, Git, pnpm 10.30.2, and a Rust toolchain for the native supervisor.
- DNS and outbound TCP 443 access to the EthanKit API and target sites.
- A non-root Linux account. sudo is required for OS dependencies and lingering.
- Administrative SSH through JumpServer or another trusted network path. The VM does not need a public IP.
- Xvfb and Openbox for web HIL. Loopback-only x11vnc is reserved for administrator profile provisioning and emergency diagnosis.
Install browser and desktop dependencies:
sudo apt-get update
sudo apt-get install -y \
build-essential ca-certificates cargo curl ffmpeg git rustc \
xvfb openbox x11vnc xdotool dbus-x11 \
fonts-liberation fonts-noto-cjk
corepack enable
corepack prepare [email protected] --activate
pnpm --filter @ethankit/agent-browser exec playwright install-deps chromium
ffmpeg produces WebM operation reels. Browsing still works without it, but reel artifacts fail.
Configure SSH through JumpServer
Create a dedicated local key instead of reusing your primary personal key:
ssh-keygen -t ed25519 -f ~/.ssh/ethankit-browser-runtime \
-C "ethankit-browser-runtime"
Log in through JumpServer, append the complete .pub line to the Runtime user's ~/.ssh/authorized_keys, and fix permissions:
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys
Verify access:
ssh -i ~/.ssh/ethankit-browser-runtime <user>@<private-ip>
If the private address is not locally routed, use the company-provided ProxyJump configuration. Do not add a temporary public IP to the VM.
Install and register the Runtime
Check out the repository revision matching the EthanKit deployment, then run on the VM:
pnpm install --frozen-lockfile
pnpm browser-runtime:install
In the Browser Use inspector or Browser Use Lab, choose “Register a machine” and create a one-time pairing token. It expires after ten minutes and can be used once. Interactive entry keeps it out of shell history:
ethankit-browser setup \
--runtime-name prod-browser-runtime-shanghai-01
The CLI defaults to https://api.ethankit.com, installs the supported Playwright Chromium, creates a dedicated persistent EthanKit profile, and starts the Runtime in the background. Use --api-url <origin> only for a self-hosted API.
Never put a pairing token in documentation, chat, a systemd unit, or Git. After registration, the long-lived credential is stored with mode 0600 in ~/.ethankit/browser-runtime.json.
Allow private destinations
The SSRF guard is enabled by default. Public destinations work without configuration. A hostname that resolves to a private IP must be explicitly allowed:
ethankit-browser private-hosts add \
paigod.work '*.paigod.work' \
pplabs.tech '*.pplabs.tech' \
ppinfra.com '*.ppinfra.com' \
ppio.com '*.ppio.com'
A wildcard covers subdomains only. Add both example.com and '*.example.com' when the apex may also be visited. Quote * so the shell does not expand it.
When an Agent owns multiple registered machines, select Runtime pool in the
Browser Use inspector and configure pool members, host routes, and the default
route. Routing reads only browser_use.start_url; exact hosts take precedence
over wildcards. Once admitted, the browser session remains on the selected
Runtime, including live view, artifacts, and human handoff. Pool routing never
bypasses the local allowlist, so configure every private host on every Runtime
that may be selected.
When systemd owns the Runtime, stop the unit before changing configuration so the CLI and systemd do not race to restart the daemon:
systemctl --user stop ethankit-browser
ethankit-browser private-hosts add intranet.example '*.intranet.example'
systemctl --user start ethankit-browser
Configure the virtual desktop and systemd
deploy/browser-runtime-linux/ contains four user services and an input-state reset helper:
ethankit-xvfb.servicecreates a 1920×1080×24 display atDISPLAY=:99.ethankit-openbox.servicemanages Chromium windows.ethankit-x11vnc.serviceexposes an administrator fallback desktop on127.0.0.1:5900only.ethankit-browser.serviceruns the Runtime in foreground mode and advertises four-session capacity.ethankit-x11vnc-reset-inputreleases stale X11 key state after the first VNC client authenticates and after the last client disconnects.
Create the VNC password first:
install -d -m 700 ~/.vnc
x11vnc -storepasswd ~/.vnc/passwd
chmod 600 ~/.vnc/passwd
To avoid a small restored browser window, copy the default Openbox configuration:
install -d -m 700 ~/.config/openbox
cp /etc/xdg/openbox/rc.xml ~/.config/openbox/rc.xml
Add this rule inside <applications> in ~/.config/openbox/rc.xml:
<application class="Chromium-browser" type="normal">
<decor>yes</decor>
<focus>yes</focus>
<desktop>1</desktop>
<maximized>yes</maximized>
</application>
Install and enable the user services:
install -d -m 700 ~/.config/systemd/user
install -d -m 700 ~/.local/libexec
install -m 755 \
deploy/browser-runtime-linux/ethankit-x11vnc-reset-input \
~/.local/libexec/
install -m 644 deploy/browser-runtime-linux/*.service \
~/.config/systemd/user/
sudo loginctl enable-linger "$USER"
systemctl --user daemon-reload
systemctl --user enable --now \
ethankit-xvfb.service \
ethankit-openbox.service \
ethankit-x11vnc.service
# setup started a background daemon; stop it before switching to systemd.
ethankit-browser stop
systemctl --user enable --now ethankit-browser.service
If the CLI is installed in a custom directory, add that directory to PATH in ethankit-browser.service. Never run both the CLI-managed background daemon and the systemd foreground daemon.
Verify the installation:
systemctl --user --no-pager --full status \
ethankit-xvfb ethankit-openbox ethankit-x11vnc ethankit-browser
ethankit-browser status
ethankit-browser doctor
ethankit-browser logs -n 100
Initialize the login profile
A persistent profile lets tasks reuse sign-in state. Stop the systemd Runtime before opening the dedicated profile:
systemctl --user stop ethankit-browser
DISPLAY=:99 ethankit-browser profile init \
--url https://test-console.paigod.work/
Complete sign-in over the SSH/VNC tunnel described below, close the Chromium window, and restart the Runtime:
systemctl --user start ethankit-browser
Do not copy a system Chrome Default profile into managed Chromium. If browser versions become incompatible, use profile repair; it preserves a timestamped backup before creating a clean dedicated profile.
Four-session capacity
ethankit-browser.service sets AGENT_BROWSER_MAX_SESSIONS=4, so the Runtime advertises four slots to the API. Keep these constraints in mind:
- Start with at least 8 vCPU/16 GiB RAM and measure representative page memory.
- Four concurrent sessions should use isolated profiles. Each gets a private copy and cannot lock another session's Chrome databases.
- A persistent profile can be owned by only one session at a time. For four independent persistent sign-in contexts, deploy multiple Runtimes or OS users with separate profiles, state files, SSRF proxy ports, and displays.
- Web HIL sends input to each isolated CDP page and can run in parallel; load-test the real ceiling for JPEG bandwidth, CPU, and memory.
Human in the Loop
Production users take over the browser directly in the EthanKit run page. They do not need SSH, Linux, a VPN, or a VNC client. The page reuses the current Runtime session's JPEG stream and sends bounded pointer, wheel, keyboard, paste, and IME text events to the same Chromium CDP target.
The correct workflow is:
- Wait for the EthanKit page to show “waiting for your action” and for the browser viewport to become controllable.
- Click the viewport and complete login, MFA, CAPTCHA, or approval there. Never provide credentials or verification codes in chat.
- Do not close or reopen remote Chromium. The Runtime retains the same browser process, profile, and CDP target.
- Choose “I'm done, continue.” The system releases held keyboard and pointer state before automation resumes.
Only one web controller may hold a HIL action at a time; another window remains view-only and reports a control conflict. The short control lease expires after the page closes, sleeps, or disconnects. x11vnc remains available to administrators for profile provisioning and emergency diagnosis, but must stay loopback-only and is not part of the production user workflow.
The page also requires a fresh frame or Runtime status. Runtime sends a timestamped status every 2 seconds; after 6 seconds without either event the page marks the connection interrupted, disables input, and releases control while keeping the last JPEG only as stale context. Runtime transport reconnects preserve the same session for up to 90 seconds when routing returns to the owning API process.
Network and port security
| Port/protocol | Direction | Public exposure | Purpose |
|---|---|---|---|
| TCP 443 | Outbound | N/A | EthanKit HTTPS/WSS and target sites |
| TCP 22 | Inbound | JumpServer/trusted network only | Administrative SSH and fallback forwarding |
| TCP 5900 | VM loopback | No | Administrator fallback x11vnc |
| TCP 9234 | VM loopback | No | Runtime SSRF forward proxy |
| Random CDP port | VM loopback | No | Runtime observation of Chromium |
Do not expose ports 5900, 9234, or CDP in a security group. The Runtime needs neither a public IP nor a “development port.”
The Xvfb template permits local access for desktop processes on the same VM. Use a single-purpose VM and dedicated OS account; do not share it with untrusted local users.
Troubleshooting
First verify the expected lifecycle:
preparing_profile -> starting_engine -> waiting_for_cdp
-> starting_stream -> ready -> draining -> stopping -> cleaning -> closed
Follow logs with:
journalctl --user -u ethankit-browser -f
# or
ethankit-browser logs -f
Common symptoms:
- Stuck at
WAITING FOR CDP: confirm Xvfb, Openbox, and the Runtime all useDISPLAY=:99, then find the first-frame screenshot error. Do not run an external watcher that hides or unmaps Chromium before CDP is ready. page.screenshot: Timeout 10000ms exceededafterfonts loaded: the window was probably hidden too early, or the Runtime build is old. Remove external minimization scripts, upgrade, and restart.lost its CDP target/ECONNREFUSED 127.0.0.1:<port>: Chromium changed its CDP endpoint during navigation. Current Runtime builds keep readingDevToolsActivePortand resume the stream; upgrade older builds.- Operation screenshots exist but the live view stops: the stream observer is likely still bound to the previous CDP Browser object. Upgrade to a Runtime containing the reconnect fix.
- VNC is black: confirm
ethankit-xvfb,ethankit-openbox, andethankit-x11vncare active and that the tunnel targets127.0.0.1:5900. - The browser is too small: confirm Xvfb uses 1920×1080 and the Chromium maximize rule is active in Openbox.
- Focus keeps moving as if Tab were held during HIL: first confirm Runtime logs show
waiting_for_humanand no new browser tool call, then checkjournalctl --user -u ethankit-x11vncforkey_down=23 Tab. That message means VNC/X11 lost KeyUp; it is not a page animation. Release only Tab withDISPLAY=:99 xdotool keyup Tab. If the stuck key is unknown, runDISPLAY=:99 x11vnc -sync -R clear_allwhile nobody is typing. Verify that focus remains stable, then upgrade to the unit containing the input-reset hooks. SSRF blocked CONNECT <host>: add the exact private destination and wildcard, then restart the Runtime.persistent browser profile is busy: wait for the current persistent session, use isolated mode, or assign another Runtime.- A one-off
session lease does not matchafter session close usually means an old response arrived late. It is harmless if the Runtime reconnects; restart and compare API/daemon versions if it repeats.
Upgrade and rollback
Stop the systemd service before reinstalling from a release or branch:
systemctl --user stop ethankit-browser
git pull --ff-only
pnpm install --frozen-lockfile
pnpm browser-runtime:install --rebuild
install -d -m 700 ~/.local/libexec ~/.config/systemd/user
install -m 755 \
deploy/browser-runtime-linux/ethankit-x11vnc-reset-input \
~/.local/libexec/
install -m 644 deploy/browser-runtime-linux/*.service \
~/.config/systemd/user/
systemctl --user daemon-reload
systemctl --user restart \
ethankit-xvfb ethankit-openbox ethankit-x11vnc
systemctl --user start ethankit-browser
ethankit-browser doctor
The installer also detects an active ethankit-browser.service and stops and
restores it through systemd, preserving the authoritative DISPLAY=:99 and
service environment. This remains true with --setup: the installer updates
configuration without launching a competing background daemon from the SSH
shell. Production upgrades should still use the explicit
sequence above so the Xvfb, Openbox, and x11vnc units are refreshed together.
After an upgrade, doctor checks the daemon's inherited DISPLAY and matching
X11 socket; do not run Browser Test while that check fails.
Do not upgrade during an active HIL handoff; restarting the virtual desktop disconnects VNC and Chromium. An upgrade does not remove ~/.ethankit/browser-runtime.json, config.json, or the dedicated profile. Back up ~/.ethankit/browser-runtime/ before a production upgrade, then verify one live Browser Use Lab run and one HIL handoff.