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, Openbox, and loopback-only x11vnc for HIL.
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 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 HIL 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.
- Four simultaneous HIL windows share one virtual desktop and are impractical for parallel human operation. Use separate Runtimes for interactive production workloads.
Human in the Loop
VNC must remain bound to the VM loopback address. When human action is required, create a local SSH tunnel:
ssh -N \
-L 15900:127.0.0.1:5900 \
-i ~/.ssh/ethankit-browser-runtime \
<user>@<private-ip>
Connect a VNC client to 127.0.0.1:15900. On macOS:
open 'vnc://127.0.0.1:15900'
The correct workflow is:
- Wait until EthanKit explicitly shows
WAITING FOR HUMANor “waiting for your action.” - Complete login, MFA, CAPTCHA, or approval in VNC. Never provide credentials or verification codes in chat.
- Do not close, manually minimize, or reopen Chromium. The Runtime must retain the same browser process and CDP target.
- Return to EthanKit and choose “I’m done, continue.” The Runtime verifies browser identity, minimizes the same window, and resumes automation.
Brief focus movement during automation may be the Agent operating the page. Once the Runtime is in WAITING FOR HUMAN, however, focus traversing elements continuously at a fixed rate is abnormal. If a VNC viewer disconnects before a key-release event arrives, X11 can retain that key as permanently pressed. macOS Screen Sharing may also open a short probe connection before the real viewer connection, making this failure easier to trigger.
-norepeat suppresses only some auto-repeat behavior, while -clear_keys runs when x11vnc starts and exits, not after every viewer disconnect. The supplied unit therefore invokes ethankit-x11vnc-reset-input through -afteraccept and -gone: once after the first viewer authenticates and again after the last viewer disconnects. Treat one virtual display as a single-operator HIL desktop; -shared is not a multi-user collaboration mechanism.
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 forwarding |
| TCP 5900 | VM loopback | No | x11vnc, reached through SSH forwarding |
| 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
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.