此页面尚未翻译成你的语言,因此以英文显示。
Self-hosting the web UI
Local web is an optional browser UI for your Host. Bots and channel connections work without it; the published app at app.clisbot.com and native/Desktop apps can connect directly or through relay.
The matching browser UI is bundled with Clisbot. Its serving process is independent of the Host and Hub, so enabling or stopping it preserves your agents and channel connections.
Start, check, stop
clisbot web start
clisbot web status
clisbot web stop
Start also works as the first command on an empty home, preparing protected Owner access before exposing web. It reuses your Host and any running Hub. It starts no Hub for Bot-only use. Open the actual loopback URL printed by the command; an occupied preferred port selects an available one. Tailscale is preferred: when available, the command also prints a private HTTPS URL. Without Tailscale, loopback still works. Relay carries backend traffic, not these static web pages.
For a protected Host, start also prints a single-use owner pairing link, valid for five minutes. Open it deliberately to authorize this browser, then use the Bot’s Connect channel action. A web URL alone grants no Owner access. An existing legacy/password-only Host needs planned protected setup first; web refuses exposure without changing its policy or restarting it. Status checks the actual private mapping and explains fallback. Managed Hosts require Hub access.
Web start saves your choice. Clisbot daemon start and personal serving restore enabled web; reboot restoration needs an installed OS service. Web stop clears the choice and stops only the web process. The Host's Local web UI controls use the same lifecycle.
How the connection works
The browser sees the UI, Host API/WebSocket and running Hub API/WebSocket on the same origin. Adding Hub after web starts updates routing without restarting either backend. Hub retains its canonical identity, OAuth issuer and existing grants when web is enabled or disabled. Sign-in uses exact trusted origins; opening web grants no new Host or Hub authority.
Existing deployments that deliberately serve UI inside the daemon remain supported through
features.webUi.enabled or CLISBOT_WEB_UI_ENABLED=true. The separate web commands are
the normal personal workflow and do not require daemon web/proxy flags or a daemon restart.
Topologies
Three common ways to run it, in order of exposure:
- Same machine. Run
clisbot web startand open its printed loopback URL. - Private network (LAN or VPN). Reach the daemon from other devices on a network you trust, a home LAN or a Tailscale tailnet. Follow the Connectivity guide for the complete direct setup.
- Public reverse proxy or tunnel. Expose the UI on a domain over HTTPS, terminating TLS at a reverse proxy or a tunnel. This is the full self-hosted setup.
The rest of this page builds from local to public. Verify a direct connection works before you add a proxy in front of it, it isolates daemon problems from proxy problems.
Existing daemon-hosted deployments
The remaining examples apply to the explicit legacy daemon-hosted UI (features.webUi.enabled), not the independent web process. For normal private access, use the Tailscale URL printed by web start; it manages only its own Serve mapping.
Exposing beyond localhost
By default the daemon listens on 127.0.0.1:6868, reachable only from the same machine. To reach it from other devices, bind it to a network interface:
clisbot daemon config set daemon.listen 0.0.0.0:6868
clisbot daemon start
Anyone who can reach the listening address can use your agents. Before you bind beyond localhost, set a password and review your host allowlist. The relay pairing path avoids this entirely by keeping the daemon bound to localhost, see Security.
Two things to configure when you expose the daemon directly:
-
Set a password so only authorized clients can connect:
clisbot daemon set-passwordSee password authentication for the persistent setup. Password auth controls access; it does not encrypt traffic, put TLS in front of it (below) on any untrusted network.
-
Allow your hostname so the daemon's DNS-rebinding protection accepts requests for your domain:
clisbot daemon config set daemon.hostnames '[".example.com"]'See DNS rebinding protection for how the host allowlist works.
The web app loads before authentication, by design. The static UI files are served without the daemon password so the login screen can render; the API and WebSocket still require the password before any agent data is returned or any command runs. Don't treat "the page loaded" as "the daemon is open", but do set a password before binding to a network so the data behind the page stays protected.
Reverse proxy
To serve the UI on a domain over HTTPS, terminate TLS at a reverse proxy and forward everything to the daemon. Keep the daemon on localhost and let the proxy be the only thing exposed.
A working proxy must:
- Forward the WebSocket upgrade. The app streams agent output over a WebSocket at
/ws; without upgrade support the UI loads but never connects. - Not buffer responses. Terminal output and other live streams are long-lived; buffering makes the UI look frozen.
- Use long read timeouts. Those streams stay open for the life of a session.
- Allow large request bodies. Prompts and file uploads can be big.
- Preserve the
Hostheader and passX-Forwarded-Proto. The daemon uses these to tell the app which origin and scheme (wss://vsws://) to connect back on. Drop them and auto-connect points at the wrong place.
Nginx
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 443 ssl;
server_name clisbot.example.com;
ssl_certificate /etc/letsencrypt/live/clisbot.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/clisbot.example.com/privkey.pem;
client_max_body_size 100m;
location / {
proxy_pass http://127.0.0.1:6868;
proxy_http_version 1.1;
# WebSocket upgrade
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
# Preserve origin + scheme so the UI connects back over wss://
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# Long-lived, unbuffered streams
proxy_buffering off;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}
}
Caddy
Caddy handles TLS, the WebSocket upgrade, header forwarding, and streaming for you:
clisbot.example.com {
reverse_proxy 127.0.0.1:6868
}
That's the whole config. Caddy provisions a certificate automatically and preserves Host and X-Forwarded-Proto by default.
HTTPS and TLS
Terminate TLS at the proxy (or tunnel) and forward to the daemon over plain HTTP on localhost, that's what the configs above do. When the page is served over HTTPS and the proxy passes X-Forwarded-Proto: https, the app automatically connects back over wss://. You don't configure the scheme anywhere; it follows the edge.
The daemon trusts forwarded headers from loopback proxies by default, which is what all the setups above do, the proxy or tunnel forwards to 127.0.0.1:6868.
If your proxy reaches the daemon from another address, as in some Docker, LAN, or load-balancer setups, configure the trusted proxy ranges:
{
"daemon": {
"trustedProxies": ["loopback", "172.16.0.0/12"]
}
}
CLISBOT_TRUSTED_PROXIES accepts the same comma-separated values:
CLISBOT_TRUSTED_PROXIES=loopback,172.16.0.0/12 CLISBOT_WEB_UI_ENABLED=true clisbot daemon run
Only use trustedProxies: true when your final trusted proxy overwrites client-supplied X-Forwarded-* headers. Otherwise a client could spoof forwarded header values.
If you serve the UI over HTTPS but the app tries to connect over ws:// (and the browser blocks it as mixed content), your proxy isn't forwarding X-Forwarded-Proto or the daemon doesn't trust the proxy address. Fix whichever applies.
For the remote/relay path (driving a daemon through the Clisbot relay rather than a reverse proxy), the relay has its own public-vs-internal TLS settings, see Security.
Tunnels
If you don't want to manage a reverse proxy or open ports, a tunnel gives you an HTTPS URL that forwards to your local daemon.
-
Tailscale Serve keeps it inside your tailnet, no public exposure, TLS handled for you:
tailscale serve --bg http://127.0.0.1:6868Reach it at
https://<your-machine>.<tailnet>.ts.net/. Only devices on your tailnet can connect. -
Cloudflare Tunnel exposes it on a public hostname with TLS and WebSocket support:
cloudflared tunnel --url http://localhost:6868Cloudflare terminates TLS and sets
X-Forwarded-Proto: https, so auto-connect works. Because the URL is public, set a daemon password.
Security
Self-hosting the web UI puts you in charge of who can reach the daemon. The essentials:
- Set a password before binding beyond localhost. The static page loads without it, but agent data and commands stay behind it. See Security.
- Put TLS in front of any untrusted network. Password auth protects access, not confidentiality.
- Keep the daemon on localhost when you can and let a reverse proxy or tunnel be the only exposed surface.
- Review your host allowlist when serving on a custom domain.
For the full threat model, relay encryption, and DNS-rebinding details, see Security and SECURITY.md.
Troubleshooting
- Blank page or 404 at
/. Runclisbot web start, checkclisbot web status, and use the printed URL. For an explicit legacy deployment, check its daemon web UI configuration. - Page loads but never connects. The proxy isn't forwarding the WebSocket upgrade, or it's stripping the
Hostheader. Check the upgrade headers in your proxy config. - Connects, then output freezes. Response buffering is on, or read timeouts are too short. Disable buffering and raise the timeouts.
- "Mixed content" / connection blocked over HTTPS. The app fell back to
ws://. Either the proxy isn't sendingX-Forwarded-Proto: https, or the daemon doesn't trust the proxy address. Forward the header and configuredaemon.trustedProxiesif the proxy is not loopback. 403 Invalid Host header. Your domain isn't in the allowlist. Add it with--hostnamesordaemon.hostnames, see DNS rebinding protection.- Large prompts or uploads fail. Raise the proxy's max body size (
client_max_body_sizein Nginx).
See also
- Security, connection methods, relay encryption, password auth, host allowlist.
- Configuration,
config.json, environment variables, and CLI overrides. - CLI, the
clisbot daemoncommands. - Community projects, community-built self-hosting tooling.