diff --git a/openhands/usage/agent-canvas/backend-setup/docker.mdx b/openhands/usage/agent-canvas/backend-setup/docker.mdx
index 7e72170d..99454964 100644
--- a/openhands/usage/agent-canvas/backend-setup/docker.mdx
+++ b/openhands/usage/agent-canvas/backend-setup/docker.mdx
@@ -80,5 +80,4 @@ Then add the Docker backend:
- [Connect and Manage Backends](/openhands/usage/agent-canvas/backends)
- [Local Backend](/openhands/usage/agent-canvas/backend-setup/local)
-- [VM Backend](/openhands/usage/agent-canvas/backend-setup/vm)
-- [VM / Self-Hosted Backend](/openhands/usage/agent-canvas/backend-setup/vm)
+- [VM / Self-Hosted Installation](/openhands/usage/agent-canvas/backend-setup/vm)
diff --git a/openhands/usage/agent-canvas/backend-setup/local.mdx b/openhands/usage/agent-canvas/backend-setup/local.mdx
index 4d18882c..a6bf67a4 100644
--- a/openhands/usage/agent-canvas/backend-setup/local.mdx
+++ b/openhands/usage/agent-canvas/backend-setup/local.mdx
@@ -48,6 +48,5 @@ Switch between them from the backend selector depending on what you're working o
## Related Guides
- [Connect and Manage Backends](/openhands/usage/agent-canvas/backends)
-- [VM Backend](/openhands/usage/agent-canvas/backend-setup/vm) — headless backend on a remote machine
-- [VM / Self-Hosted Backend](/openhands/usage/agent-canvas/backend-setup/vm) — backend on a remote machine
+- [VM / Self-Hosted Installation](/openhands/usage/agent-canvas/backend-setup/vm) — backend-only or full Canvas on a remote machine
- [Docker Backend](/openhands/usage/agent-canvas/backend-setup/docker) — run in a container
diff --git a/openhands/usage/agent-canvas/backend-setup/vm.mdx b/openhands/usage/agent-canvas/backend-setup/vm.mdx
index 79ab742a..b71781f0 100644
--- a/openhands/usage/agent-canvas/backend-setup/vm.mdx
+++ b/openhands/usage/agent-canvas/backend-setup/vm.mdx
@@ -1,108 +1,261 @@
---
-title: VM / Self-Hosted Backend
-description: Run Agent Canvas on a VM or dedicated machine and connect to it remotely.
+title: VM / Self-Hosted Installation
+description: Install Agent Canvas on a VM as a backend-only service or full self-hosted Canvas.
---
-Use `--backend-only` to run the backend on a remote machine, then connect from your local frontend with `--frontend-only`.
+Run Agent Canvas on a VM or dedicated machine when you want an always-on backend, more compute, or a self-hosted Canvas that you can reach from other devices.
- The agent server can read and write the host filesystem, execute shell commands, and access the network. Lock down the machine before starting.
+ The agent server can read and write the host filesystem, execute shell commands, access the network, and store secrets. Treat the VM as trusted infrastructure. Use `--public`, a strong `LOCAL_BACKEND_API_KEY`, and a network access control layer before exposing it to the internet.
-## 1. Provision and Secure the Machine
+## Choose a Deployment Shape
-Any always-on Linux or macOS host:
+Agent Canvas supports two VM runtime modes and several ways to reach them:
-- **Cloud VM** — Ubuntu 24.04 LTS, 2 vCPU / 4 GB RAM is enough for a single user.
-- **Dedicated hardware** — Mac Mini, Intel NUC, spare laptop.
+| Setup | Start Command | How You Use It |
+|-------|---------------|----------------|
+| **Backend only** | `agent-canvas --backend-only --public` | Run only the agent server on the VM. Start `agent-canvas --frontend-only` on your laptop and add the VM URL in **Manage Backends**. |
+| **Backend only + ngrok** | `agent-canvas --backend-only --public` and `ngrok http 8000` | Use an ngrok URL as the backend URL. Do not add ngrok OAuth for this mode; rely on `LOCAL_BACKEND_API_KEY` plus a private or temporary URL. |
+| **Full Canvas** | `agent-canvas --public` | Serve both the Agent Canvas UI and the backend from the VM. Open the VM, reverse proxy, or ngrok URL in a browser. |
+| **Full Canvas + ngrok OAuth** | `agent-canvas --public` and `ngrok http 8000 --traffic-policy-file ~/policy.yml` | Protect the full Canvas URL with an ngrok login policy before users reach Agent Canvas. |
-Lock down inbound traffic **before** starting the backend:
+
+ Use **backend only** when you want to keep the UI on your laptop and switch between backends. Use **full Canvas** when the VM should serve the browser UI too.
+
+
+## 1. Provision and Secure the VM
+
+Use any always-on Linux or macOS host. Ubuntu 24.04 LTS with 2 vCPU and 4 GB RAM is enough for a single user.
-- **Port 22 (SSH)** — your IP or VPN CIDR only.
-- **Everything else** — drop.
+Before starting Agent Canvas, restrict inbound traffic:
+
+- **SSH (`22`)** — allow only your IP address or VPN CIDR.
+- **Agent Canvas (`8000`)** — keep closed unless you are using an SSH tunnel. If you expose it through ngrok, nginx, or another proxy, expose only that proxy.
+- **HTTP/HTTPS (`80`, `443`)** — open only if you configure a reverse proxy and TLS.
## 2. Install Prerequisites
-On Ubuntu:
+Agent Canvas requires:
+
+- [Node.js](https://nodejs.org/en/download) 22.12 or later, including `npm`.
+- [`uv`](https://docs.astral.sh/uv/getting-started/installation/) for the agent server runtime.
+- `git` and `curl`.
+- Optional: [`ngrok`](https://ngrok.com/download) for a temporary public URL.
+- Optional: `tmux` to keep Agent Canvas and ngrok running after disconnecting from SSH.
+
+### Ubuntu 22.04 / 24.04
+
+Install Node.js 22.x, `uv`, and Agent Canvas:
```bash
-apt-get update && apt-get install -y curl git
+sudo apt-get update
+sudo apt-get install -y ca-certificates curl gnupg git
+
+# Node.js 22.x from NodeSource.
+curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
+sudo apt-get install -y nodejs
-# Node.js 22.x (via nvm, asdf, or NodeSource)
-# uv (for the agent server runtime):
+# uv for the agent server runtime.
curl -LsSf https://astral.sh/uv/install.sh | sh
+source "$HOME/.local/bin/env"
+
+# Agent Canvas CLI.
+sudo npm install -g @openhands/agent-canvas
+
+node --version
+uv --version
+agent-canvas --version
```
-On macOS, install Node and `uv` via Homebrew instead.
+
+ If your `npm` global prefix is user-writable, omit `sudo` from `npm install -g`. For macOS or other Linux distributions, use the official Node.js, `uv`, and ngrok installation links above instead of the Ubuntu-specific commands.
+
-## 3. Start the Backend
+Install optional runtime helpers if needed:
```bash
-LOCAL_BACKEND_API_KEY= npx @openhands/agent-canvas --backend-only --public
+sudo apt-get install -y tmux
```
-- `--backend-only` starts only the backend (no frontend).
-- `--public` requires `LOCAL_BACKEND_API_KEY` — every API request must carry a matching `X-Session-API-Key` header.
+Install ngrok only if you plan to expose the VM through ngrok:
-
- To also serve the UI from the VM (e.g. to access it from a phone), drop `--backend-only`. With the full stack, `--public` requires users to enter the API key in the UI before interacting with the agent.
-
+```bash
+curl -sSL https://ngrok-agent.s3.amazonaws.com/ngrok.asc \
+ | sudo tee /etc/apt/trusted.gpg.d/ngrok.asc >/dev/null
-## 4. Connect from Your Local Machine
+echo "deb https://ngrok-agent.s3.amazonaws.com buster main" \
+ | sudo tee /etc/apt/sources.list.d/ngrok.list
-On your laptop, start the frontend:
+sudo apt-get update
+sudo apt-get install -y ngrok
+
+ngrok config add-authtoken
+```
+
+Get the authtoken from the [ngrok dashboard](https://dashboard.ngrok.com/get-started/your-authtoken).
+
+## 3. Set the Backend API Key
+
+Remote and shared deployments should always run in public mode. Public mode requires `LOCAL_BACKEND_API_KEY`.
+
+Create a local environment file on the VM:
```bash
-agent-canvas --frontend-only
+cat > ~/.agent-canvas.env <<'EOF_ENV'
+export LOCAL_BACKEND_API_KEY=""
+EOF_ENV
+chmod 600 ~/.agent-canvas.env
+source ~/.agent-canvas.env
```
-Then add the VM as a backend:
+Use a high-entropy secret. You will enter this key in Agent Canvas when connecting to the VM backend or opening the full Canvas UI.
+
+## 4. Start Agent Canvas
+
+
+
+ Start only the backend on the VM:
+
+ ```bash
+ source ~/.agent-canvas.env
+ agent-canvas --backend-only --public
+ ```
+
+ Then start the frontend on your laptop:
+
+ ```bash
+ agent-canvas --frontend-only
+ ```
+
+ Add the VM backend in Agent Canvas:
+
+ 1. Click the backend switcher, then select `Manage Backends`.
+ 2. Click `Add Backend`.
+ 3. Enter a name, such as `my-vm`.
+ 4. Enter the **Host / Base URL**:
+ - `http://localhost:8000` if you use an SSH tunnel.
+ - The `https://...ngrok-free.app` URL if you use ngrok.
+ - Your reverse proxy URL if you use nginx or another proxy.
+ 5. Enter the `LOCAL_BACKEND_API_KEY` from the VM.
+ 6. Save and select the backend.
+
+
+ Start the full UI and backend on the VM:
-1. Click the backend switcher → **Manage Backends** → **Add Backend**.
-2. Fill in:
- - **Name** — e.g. `my-vm`
- - **Host / Base URL** — `http://localhost:8000` (if using an SSH tunnel) or the VM's URL if you've set up a reverse proxy
- - **API Key** — the `LOCAL_BACKEND_API_KEY` from step 3
-3. Save and select it as the active backend.
+ ```bash
+ source ~/.agent-canvas.env
+ agent-canvas --public
+ ```
-### Using an SSH Tunnel
+ Open the VM, reverse proxy, or ngrok URL in a browser. Agent Canvas prompts for the `LOCAL_BACKEND_API_KEY` before allowing backend access.
+
+
-The simplest way to reach the backend without exposing ports:
+### Keep It Running with tmux
+
+Use `tmux` when you want Agent Canvas to keep running after your SSH session disconnects.
+
+
+
+ ```bash
+ tmux new-session -d -s canvas
+ tmux send-keys -t canvas 'source ~/.agent-canvas.env && agent-canvas --backend-only --public' Enter
+ tmux attach-session -t canvas
+ ```
+
+
+ ```bash
+ tmux new-session -d -s canvas
+ tmux send-keys -t canvas 'source ~/.agent-canvas.env && agent-canvas --public' Enter
+ tmux attach-session -t canvas
+ ```
+
+
+
+Detach from tmux with `Ctrl-b`, then `d`. Reattach later with `tmux attach-session -t canvas`.
+
+## 5. Choose an Access Method
+
+### Option A: SSH Tunnel
+
+Use an SSH tunnel when you only need personal access and do not want to expose a public URL.
+
+On your laptop:
```bash
ssh -L 8000:127.0.0.1:8000 user@your-vm
```
-Then use `http://localhost:8000` as the backend URL.
+Then use `http://localhost:8000` as the backend URL in **Manage Backends**.
-## 5. (Optional) Add a Domain with nginx + TLS
+### Option B: ngrok Without OAuth
-If you want direct HTTPS access without an SSH tunnel, point a domain at the machine and front it with nginx + Let's Encrypt.
+Use ngrok without OAuth only for temporary testing or personal access. Keep `--public` enabled and use a strong `LOCAL_BACKEND_API_KEY`.
-### Point a Domain at the Machine
-
-Create an `A` record pointing to the machine's public IP (e.g. `canvas.example.com`):
+On the VM, in a second terminal or tmux pane:
```bash
-dig +short canvas.example.com
+ngrok http 8000
```
-### Open Ports 80 and 443
+Use the `https://...ngrok-free.app` forwarding URL:
+
+- Backend-only mode: enter it as the **Host / Base URL** in **Manage Backends**.
+- Full Canvas mode: open it directly in your browser.
+
+### Option C: ngrok With Google OAuth
+
+Use ngrok OAuth with **full Canvas** deployments when the ngrok URL may be reachable by a team or a broader audience. OAuth is an additional gate in front of Agent Canvas; it does not replace `LOCAL_BACKEND_API_KEY`.
-Update your network firewall to additionally allow:
+For backend-only deployments, use ngrok without OAuth and keep `--public` enabled. OAuth is best suited to the full Canvas URL where the UI and backend share the same origin.
-- **Port 80 (HTTP)** — open to `0.0.0.0/0` (required for Let's Encrypt HTTP-01 challenges). nginx redirects all HTTP to HTTPS.
-- **Port 443 (HTTPS)** — restrict to your IP if possible. If you need it world-open, `LOCAL_BACKEND_API_KEY` is your primary defense.
+Create `~/policy.yml`, replacing `openhands.dev` with your allowed Google Workspace domain:
+
+```yaml
+on_http_request:
+ # Require Google OAuth login.
+ - actions:
+ - type: oauth
+ config:
+ provider: google
+
+ # Deny anyone outside the allowed domain.
+ - expressions:
+ - "!actions.ngrok.oauth.identity.email.endsWith('@openhands.dev')"
+ actions:
+ - type: deny
+ config:
+ status_code: 403
+```
-### Install nginx and Certbot
+Start ngrok with the traffic policy:
```bash
-apt-get install -y nginx certbot python3-certbot-nginx
+ngrok http 8000 --traffic-policy-file ~/policy.yml
```
-### Configure nginx
+To run full Canvas and ngrok side by side in tmux:
-Save this at `/etc/nginx/sites-available/canvas.example.com`, replacing the domain:
+```bash
+tmux new-session -d -s canvas
+tmux send-keys -t canvas 'source ~/.agent-canvas.env && agent-canvas --public' Enter
+tmux split-window -h -t canvas
+tmux send-keys -t canvas 'ngrok http 8000 --traffic-policy-file ~/policy.yml' Enter
+tmux attach-session -t canvas
+```
+
+### Option D: Reverse Proxy With TLS
+
+Use a reverse proxy when you need a stable domain instead of an ngrok URL. Point a domain at the VM, proxy it to `127.0.0.1:8000`, and terminate TLS at the proxy.
+
+On Ubuntu, install nginx and Certbot:
+
+```bash
+sudo apt-get install -y nginx certbot python3-certbot-nginx
+```
+
+Create `/etc/nginx/sites-available/canvas.example.com`, replacing `canvas.example.com` with your domain:
```nginx
server {
@@ -122,7 +275,7 @@ server {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
- # WebSocket / SSE support — required for live agent events.
+ # WebSocket / SSE support for live agent events.
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 3600s;
@@ -134,36 +287,33 @@ server {
Enable the site and issue a certificate:
```bash
-ln -sf /etc/nginx/sites-available/canvas.example.com \
- /etc/nginx/sites-enabled/canvas.example.com
-nginx -t && systemctl reload nginx
-
-certbot --nginx -d canvas.example.com \
- --non-interactive --agree-tos \
- --email you@example.com \
- --redirect
-```
-
-### Verify
-
-```bash
-curl -I https://canvas.example.com/ # → 200
-curl -I http://canvas.example.com/ # → 301 redirect to HTTPS
+sudo ln -sf /etc/nginx/sites-available/canvas.example.com \
+ /etc/nginx/sites-enabled/canvas.example.com
+sudo nginx -t
+sudo systemctl reload nginx
+
+sudo certbot --nginx -d canvas.example.com \
+ --non-interactive --agree-tos \
+ --email you@example.com \
+ --redirect
```
-Use `https://canvas.example.com` as the **Host / Base URL** when adding the backend in Manage Backends.
+Use `https://canvas.example.com` as the URL for either the remote backend entry or the full Canvas UI.
## Security Checklist
-Before exposing the backend to a broader network:
+Before exposing Agent Canvas beyond an SSH tunnel:
-1. **Restrict inbound network access** — only open ports you need (SSH, 80/443 for the reverse proxy).
-2. **Use `--public` mode** with a strong `LOCAL_BACKEND_API_KEY`.
-3. **Use TLS** — put a reverse proxy in front with Let's Encrypt if the backend is internet-reachable.
-4. **Treat the host as sensitive infrastructure** — it stores secrets, conversations, and working copies.
+1. **Run with `--public`** and set a strong `LOCAL_BACKEND_API_KEY`.
+2. **Restrict network access** with a firewall, VPN, ngrok OAuth, or an identity-aware proxy.
+3. **Use HTTPS** for any internet-reachable URL.
+4. **Limit who can SSH to the VM** and keep the OS patched.
+5. **Protect the VM filesystem** because it stores settings, secrets, conversations, and working copies.
+6. **Rotate keys** if an ngrok URL, API key, or VM login is shared too broadly.
## Related Guides
+- [Install](/openhands/usage/agent-canvas/setup)
- [Connect and Manage Backends](/openhands/usage/agent-canvas/backends)
- [Local Backend](/openhands/usage/agent-canvas/backend-setup/local)
- [Docker Backend](/openhands/usage/agent-canvas/backend-setup/docker)
diff --git a/openhands/usage/agent-canvas/backends.mdx b/openhands/usage/agent-canvas/backends.mdx
index b2741b43..a3693eee 100644
--- a/openhands/usage/agent-canvas/backends.mdx
+++ b/openhands/usage/agent-canvas/backends.mdx
@@ -17,5 +17,5 @@ Settings, LLM configuration, MCP servers, and automations are all scoped to the
|-------|-------------|-----|
| **Default local** | Quick local work on your machine | Run `agent-canvas` — a local backend is created automatically |
| **Backend-only (local)** | Multiple projects, or separate frontend and backend processes | Run `agent-canvas --backend-only` (optionally on different ports), connect with `--frontend-only`. See [Local Backend](/openhands/usage/agent-canvas/backend-setup/local). |
-| **Backend-only (remote)** | Always-on server, more powerful hardware, or team-shared access | Run `agent-canvas --backend-only --public` on a VM with a `LOCAL_BACKEND_API_KEY`, connect via SSH tunnel or reverse proxy. See [VM / Self-Hosted Backend](/openhands/usage/agent-canvas/backend-setup/vm). |
+| **Self-hosted VM** | Always-on server, more powerful hardware, team-shared access, or a full self-hosted Canvas | Run `agent-canvas --backend-only --public` for backend-only mode, or `agent-canvas --public` for the full UI and backend. Expose it with SSH, ngrok, or a reverse proxy. See [VM / Self-Hosted Installation](/openhands/usage/agent-canvas/backend-setup/vm). |
| **Cloud** | Managed sandboxes without local resources | Connect to [OpenHands Cloud](/openhands/usage/cloud/openhands-cloud) from **Manage Backends**. See [Cloud Backend](/openhands/usage/agent-canvas/backend-setup/cloud). |
diff --git a/openhands/usage/agent-canvas/overview.mdx b/openhands/usage/agent-canvas/overview.mdx
index 65a62795..92a8eb91 100644
--- a/openhands/usage/agent-canvas/overview.mdx
+++ b/openhands/usage/agent-canvas/overview.mdx
@@ -36,4 +36,4 @@ description: A lightweight platform to run agents and automations — locally or
- [Connect and Manage Backends](/openhands/usage/agent-canvas/backends) — Switch between local and remote backends.
- [Customize and Settings](/openhands/usage/agent-canvas/customize-and-settings) — Configure skills, MCP servers, and backend-synced settings.
- [Setup a Pre-built Automation](/openhands/usage/agent-canvas/prebuilt-automations) — Get started with a ready-made automation workflow.
-- [VM / Self-Hosted Backend](/openhands/usage/agent-canvas/backend-setup/vm) — Run a backend on a VM and connect remotely.
+- [VM / Self-Hosted Installation](/openhands/usage/agent-canvas/backend-setup/vm) — Run backend-only or full Canvas on a VM and connect remotely.
diff --git a/openhands/usage/agent-canvas/setup.mdx b/openhands/usage/agent-canvas/setup.mdx
index f86ecfdc..f05cd008 100644
--- a/openhands/usage/agent-canvas/setup.mdx
+++ b/openhands/usage/agent-canvas/setup.mdx
@@ -4,7 +4,7 @@ description: Install and run Agent Canvas via npm or Docker.
---
- Agent Canvas starts an agent server on the machine where you run it. Treat that machine as trusted infrastructure and review the guidance in [VM / Self-Hosted Backend](/openhands/usage/agent-canvas/backend-setup/vm) before exposing it to a network you do not control.
+ Agent Canvas starts an agent server on the machine where you run it. Treat that machine as trusted infrastructure and review the guidance in [VM / Self-Hosted Installation](/openhands/usage/agent-canvas/backend-setup/vm) before exposing it to a network you do not control.
@@ -28,7 +28,7 @@ description: Install and run Agent Canvas via npm or Docker.
| Flag | Description |
|------|-------------|
| `-p`, `--port ` | Set the ingress port (default `8000`) |
- | `--public` | Enable public mode — requires `LOCAL_BACKEND_API_KEY`. The key is **not** injected into the frontend; users must enter it when the UI loads. Use this for any deployment reachable by others. See [VM / Self-Hosted Backend](/openhands/usage/agent-canvas/backend-setup/vm). |
+ | `--public` | Enable public mode — requires `LOCAL_BACKEND_API_KEY`. The key is **not** injected into the frontend; users must enter it when the UI loads. Use this for any deployment reachable by others. See [VM / Self-Hosted Installation](/openhands/usage/agent-canvas/backend-setup/vm). |
| `--backend-only` | Start only the backend behind ingress (no frontend). Use this to run a headless backend on a VM or server. |
| `--frontend-only` | Start only the static frontend behind ingress (no agent server or automation). Use this to point a local UI at a remote backend. |
| `-v`, `--version` | Show the version number |