Deploy OpenClaw on your own VPS with one click.
Full privacy, dedicated resources, no shared infrastructure.
Website · Blog · Self-Host Guide
ClawHost is an open-source, self-hostable cloud hosting platform that lets anyone deploy an AI agent runtime — OpenClaw or Hermes — on a dedicated VPS in under a minute. It handles server provisioning, DNS, SSL, firewall configuration, and agent installation automatically.
- One-Click Deploy — Pick a plan, pay, and your agent runtime is live within minutes
- Multi-Agent — Deploy either OpenClaw or Hermes; each ships with its own gateway service
- Hetzner Cloud — Reliable, high-performance VPS provisioning powered by Hetzner
- Dedicated VPS — Real servers with full root access, not shared containers
- Browser Terminal — Full SSH terminal access directly from the dashboard via WebSocket
- Diagnostics & Logs — Monitor server health, view logs, and repair instances
- File Management — Edit configuration files remotely
- Version Management — View installed agent version, browse available versions, and upgrade
- Automatic SSL — HTTPS via Let's Encrypt, configured automatically
- DNS Management — Automatic subdomain creation via Cloudflare
- SSH Key Management — Store and assign keys for passwordless access
- Persistent Storage — Attach additional volumes to any instance
- Multi-Auth — Sign in with OTP email, Google, or GitHub
- Billing Built-In — Polar.sh integration for subscriptions, invoicing, and billing portal
- Affiliate & Referrals — Built-in referral codes and payouts
- Export & Backup — Export agent configurations for backup and migration
- Cross-Platform — Web and desktop (Electron) apps
- Fully Open Source — MIT licensed, self-host the entire platform yourself
ClawHost is a TypeScript monorepo built with Turborepo and managed with Bun.
clawhost/
├── apps/
│ ├── api/ # Hono.js backend API (runs on Bun)
│ ├── web/ # React + Vite frontend
│ └── clawhostgo/ # Electron desktop app (ClawHostGo)
├── packages/
│ ├── shared/ # @openclaw/shared — HTTP client, API paths, status/role/provider
│ │ # constants, input validation, plan catalog, error types
│ └── i18n/ # @openclaw/i18n — Internationalization (EN, FR, ES, DE)
├── scripts/
│ ├── build-go-manifest.ts # Generates the desktop-app release manifest
│ └── version-patch.ts # Auto-bumps app versions on commit
├── turbo.json # Turborepo build orchestration
└── bun.lock # Bun lockfile
Cloud-init is not a static YAML file — it is generated per-server at provision time by
apps/api/src/controllers/agents/helpers/generateCloudInit.tsso the bootstrap script can be tailored to the selected agent runtime (OpenClaw or Hermes), subdomain, gateway token, and tool defaults.
| Layer | Technology |
|---|---|
| Runtime | Bun 1.3+ (all apps, scripts, and tests run on Bun) |
| API Framework | Hono on Bun (HTTP + native WebSocket on a single port) |
| Database | PostgreSQL (Neon) with Drizzle ORM |
| Authentication | Firebase (OTP email, Google, GitHub) |
| Server Provisioning | Hetzner Cloud |
| Remote Management | SSH2 for remote command execution, file management, and diagnostics |
| Browser Terminal | xterm.js with WebSocket proxy over SSH2 |
| DNS | Cloudflare API |
| Billing | Polar.sh |
| Resend with React Email | |
| Frontend | React 18 + Vite |
| UI Components | shadcn/ui + Radix UI + Tailwind CSS |
| Visual Canvas | React Flow with Dagre layout |
| Code Editor | CodeMirror via @uiw/react-codemirror |
| State Management | Zustand |
| Data Fetching | TanStack React Query |
| Icons | Phosphor Icons |
| Animations | Framer Motion |
| Blog | MDX with frontmatter |
| Desktop | Electron with Electron Forge |
| Monorepo | Turborepo + Bun |
| Table | Purpose |
|---|---|
users |
Firebase-authenticated users with Polar customer IDs and auth methods |
agents |
Cloud server instances (status, IP, subdomain, gateway token, agent type, etc) |
pendingAgents |
Temporary storage for in-progress checkout sessions before provisioning |
sshKeys |
SSH public keys with Hetzner key IDs |
volumes |
Persistent storage volumes attached to agents |
otpCodes |
OTP authentication codes with expiration and attempt tracking |
rateLimits |
Rate limiting for authentication and other sensitive endpoints |
waitlist |
Sign-ups awaiting access |
emails |
Outbound email log (deliverability + auditing) |
referrals |
Affiliate referral codes and user-attribution links |
referralPayments |
Commission payouts owed to referrers |
- Bun 1.0+
- PostgreSQL database (Neon, Supabase, or self-hosted)
| Service | Purpose | What You Need |
|---|---|---|
| Hetzner Cloud | Server provisioning | API Token (Read & Write) |
| Firebase | Authentication | Project credentials + Service account |
| Cloudflare | DNS management | API Token + Zone ID |
| Polar.sh | Billing & subscriptions | API credentials + Webhook secret |
| Resend | Transactional email | API Key |
A Hetzner Cloud API token is required for server provisioning.
git clone https://github.com/bfzli/clawhost.git
cd clawhost
bun installAPI — create apps/api/.env:
# Database
DATABASE_URL=postgresql://user:password@host:5432/database?sslmode=require
# Firebase Admin SDK
FIREBASE_PROJECT_ID=your-project-id
FIREBASE_CLIENT_EMAIL=firebase-adminsdk-xxxxx@your-project.iam.gserviceaccount.com
FIREBASE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n"
# Hetzner Cloud
HETZNER_API_TOKEN=your-hetzner-api-token
# Cloudflare DNS
CLOUDFLARE_API_TOKEN=your-cloudflare-api-token
CLOUDFLARE_ZONE_ID=your-zone-id
# Polar (payments)
POLAR_ACCESS_TOKEN=your-polar-access-token
POLAR_ORGANIZATION_ID=your-polar-org-id
POLAR_WEBHOOK_SECRET=your-polar-webhook-secret
# Polar product IDs — one per Hetzner plan + interval you sell.
# Naming scheme: POLAR_PRODUCT_<PLAN>_<MONTHLY|YEARLY>
# Plans: CX23, CX33, CX43, CX53, CPX11, CPX21, CPX31, CPX41, CPX51,
# CAX11, CAX21, CAX31, CAX41, CCX13, CCX23, CCX33, CCX43, CCX53, CCX63
POLAR_PRODUCT_CX23_MONTHLY=...
POLAR_PRODUCT_CX23_YEARLY=...
# ... repeat for every plan you offer
POLAR_PRODUCT_LICENSE=... # one-time desktop (ClawHostGo) license product
# Resend (email)
RESEND_API_KEY=your-resend-api-key
FROM_EMAIL=OpenClaw <[email protected]>
RESEND_AUDIENCE_ID=... # optional: auto-subscribe new users to a Resend audience
# Cron (required to call any /cron/* endpoint)
CRON_SECRET=... # any high-entropy string; sent as `Authorization: Bearer <secret>`
# Optional — only needed for the AI blog generator cron job
OPENAI_API_KEY=...
GITHUB_TOKEN=... # used by the blog generator to read repo metadata
# Encryption (AES-256-GCM for secrets at rest: gateway tokens, SSH keys, root passwords)
# Generate with: bun -e "console.log(crypto.getRandomValues(new Uint8Array(32)).reduce((s,b)=>s+b.toString(16).padStart(2,'0'),''))"
ENCRYPTION_KEY=... # 32-byte hex string (64 chars)
# Server
PORT=2222
CLIENT=localhost:1111
The API uses a single Bun port for both HTTP and WebSocket (via
Bun.serve's built-in WebSocket upgrade handler). There is no separate WS server.
Web — create apps/web/.env:
# API
VITE_API_URL=/api
VITE_API_PORT=2222
VITE_WS_PORT=2222 # same as VITE_API_PORT; the API serves HTTP + WS on one port
VITE_PORT=1111
# Firebase Client SDK
# authDomain is derived from VITE_FIREBASE_PROJECT_ID at runtime — no AUTH_DOMAIN needed.
VITE_FIREBASE_API_KEY=AIza...
VITE_FIREBASE_PROJECT_ID=your-project-id
VITE_FIREBASE_STORAGE_BUCKET=your-project.appspot.com
VITE_FIREBASE_MESSAGING_SENDER_ID=123456789
VITE_FIREBASE_APP_ID=1:123456789:web:abc123
# OAuth (used by the in-app Google/GitHub account linking flows)
VITE_GOOGLE_OAUTH_CLIENT_ID=
VITE_GITHUB_OAUTH_CLIENT_ID=See
apps/api/.env.exampleandapps/web/.env.examplefor ready-to-copy templates with every variable the code actually reads.
Hetzner Cloud
- Go to Hetzner Cloud Console
- Create a new project or select an existing one
- Navigate to Security > API Tokens
- Generate a token with Read & Write permissions
- Copy to
HETZNER_API_TOKEN
Firebase
- Go to Firebase Console
- Create a new project
- Enable Authentication > Sign-in method > Email/Password (required for OTP login)
- Add your domain to Authorized domains
- For the web app: Project Settings > General > Your apps > Add a web app and copy config
- For the API: Project Settings > Service accounts > Generate a new private key
Google Sign-In:
- In Authentication > Sign-in method, enable Google
- Set a project support email
GitHub Sign-In:
- Create an OAuth App on GitHub Developer Settings
- Set the Authorization callback URL to your Firebase callback URL (found in Firebase Console under the GitHub provider setup)
- In Authentication > Sign-in method, enable GitHub and paste the Client ID and Client Secret from your GitHub OAuth App
All three sign-in methods (OTP, Google, GitHub) are always displayed in the UI, so all three must be configured in Firebase for a working setup. Users can also link/unlink Google and GitHub accounts from their Account settings page.
Cloudflare
- Go to Cloudflare Dashboard
- Add your domain or select an existing one
- Copy the Zone ID from the domain overview page
- Create an API token with Zone:DNS:Edit permission
- Copy Zone ID and API Token to your
.env
Polar.sh
- Go to Polar.sh
- Create an organization and set up your products/subscriptions
- Generate an access token and copy to
POLAR_ACCESS_TOKEN - Copy your organization ID to
POLAR_ORGANIZATION_ID - Configure webhook to point to your API's
/api/webhooks/polarendpoint - Copy the webhook secret to
POLAR_WEBHOOK_SECRET
bun --filter api db:migratebun devThis starts both apps:
| App | URL |
|---|---|
| Web | http://localhost:1111 |
| API | http://localhost:2222 (HTTP + WebSocket on one port) |
The web dev server proxies /api requests to the API and /ws requests to the same port for WebSocket upgrades (terminal sessions).
To run the desktop app alongside:
bun dev:desktop| Command | Description |
|---|---|
bun dev |
Start all apps in development mode |
bun dev:web |
Start web app only |
bun dev:api |
Start API only |
bun dev:desktop |
Start the ClawHostGo desktop app (Electron) |
bun test |
Run Vitest across the monorepo |
bun test:watch |
Run Vitest in watch mode |
bun build-go-manifest |
Build the desktop release manifest |
bun version-patch |
Patch-bump app versions (used by Husky) |
bun build |
Build all apps for production |
bun lint |
Run ESLint across the monorepo |
bun lint:fix |
Auto-fix ESLint issues |
bun format |
Format all files with Prettier |
bun format:check |
Check formatting without writing |
bun check |
Run TypeScript type-check + ESLint for all apps |
| Command | Description |
|---|---|
bun --filter api db:generate |
Generate a new migration after schema changes |
bun --filter api db:migrate |
Apply pending migrations |
bun --filter api db:studio |
Open Drizzle Studio (database GUI) |
bun --filter api email:dev # Preview email templates at localhost:3333| Method | Endpoint | Description |
|---|---|---|
GET |
/api/ |
Health check |
POST |
/api/auth/send-otp |
Send OTP code via email |
POST |
/api/auth/verify-otp |
Verify OTP and get a Firebase custom token |
POST |
/api/auth/resolve-credential-conflict |
Resolve provider/email conflicts at login |
GET |
/api/plans/locations |
List available regions |
GET |
/api/plans/volume-pricing |
Get volume pricing |
GET |
/api/plans/availability |
Check plan availability per location |
POST |
/api/waitlist |
Join the waitlist |
GET |
/api/waitlist/status |
Check waitlist status by email |
POST |
/api/webhooks/polar |
Polar payment/subscription webhook |
Agents (Server Instances)
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/agents |
List the user's agents |
GET |
/api/agents/:id |
Get a specific agent |
GET |
/api/agents/check-subdomain |
Check whether a subdomain is available |
GET |
/api/agents/stars |
Get aggregate stars (OpenClaw repo metric) |
POST |
/api/agents/purchase |
Initiate a paid agent purchase via Polar |
DELETE |
/api/agents/pending/:id |
Cancel a pending purchase |
POST |
/api/agents/:id/sync |
Sync agent state with the cloud provider |
POST |
/api/agents/:id/start |
Start an agent |
POST |
/api/agents/:id/stop |
Stop an agent |
POST |
/api/agents/:id/restart |
Restart an agent |
POST |
/api/agents/:id/reinstall |
Reinstall the agent runtime (rate-limited) |
POST |
/api/agents/:id/cancel-deletion |
Cancel scheduled deletion |
PATCH |
/api/agents/:id |
Rename an agent |
PATCH |
/api/agents/:id/emoji |
Update the agent emoji |
PATCH |
/api/agents/:id/ssh-key |
Change the assigned SSH key |
PATCH |
/api/agents/:id/subdomain |
Change the agent subdomain |
DELETE |
/api/agents/:id |
Soft-delete an agent (schedules deletion) |
GET |
/api/agents/:id/export |
Export agent configuration |
GET |
/api/agents/:id/credentials |
Get agent credentials (root password, SSH info) |
GET |
/api/agents/:id/billing |
Per-agent billing summary |
POST |
/api/agents/:id/version |
Get the currently installed agent version |
POST |
/api/agents/:id/versions |
List available agent versions |
POST |
/api/agents/:id/metrics |
Live CPU/RAM/disk metrics |
POST |
/api/agents/:id/overview |
Aggregated overview (status + parsed gateway info) |
POST |
/api/agents/:id/enable-preview |
Enable the public preview surface for the agent |
POST |
/api/agents/:id/rotate-password |
Rotate the root password |
POST |
/api/agents/:id/rotate-gateway-token |
Rotate the Hermes/OpenClaw gateway token |
Agent Diagnostics
| Method | Endpoint | Description |
|---|---|---|
POST |
/api/agents/:id/diagnostics/status |
Get gateway/service status |
POST |
/api/agents/:id/diagnostics/logs |
Stream the gateway/service log |
Agent Files
| Method | Endpoint | Description |
|---|---|---|
POST |
/api/agents/:id/files |
List files on the instance |
POST |
/api/agents/:id/files/read |
Read a file |
PUT |
/api/agents/:id/files |
Update a file |
Admin Agent Endpoints
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/agents/admin |
List all agents |
POST |
/api/agents/:id/hard-delete |
Permanently delete an agent + cloud server |
POST |
/api/agents/:id/diagnostics/repair |
Attempt repair (restarts gateway/services) |
POST |
/api/agents/:id/install-version |
Install a specific runtime version |
Admin Dashboard (all under /api/admin, admin only)
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/admin/stats |
Platform stats |
GET |
/api/admin/analytics |
Time-series analytics |
GET |
/api/admin/billing |
Cross-user billing overview |
GET |
/api/admin/users |
List all users |
GET |
/api/admin/users/:id |
User detail |
PUT |
/api/admin/users/:id |
Update a user (role, flags) |
GET |
/api/admin/agents |
List all agents (admin view) |
GET |
/api/admin/pending-agents |
List pending purchases |
GET |
/api/admin/ssh-keys |
All SSH keys |
GET |
/api/admin/volumes |
All volumes |
GET |
/api/admin/referrals |
Affiliate referral log |
GET |
/api/admin/waitlist |
Waitlist entries |
GET |
/api/admin/emails |
Outbound email log |
SSH Keys
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/ssh-keys |
List SSH keys |
POST |
/api/ssh-keys |
Add an SSH key |
DELETE |
/api/ssh-keys/:id |
Delete an SSH key |
Users
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/users/me |
Get the current user profile |
PUT |
/api/users/me |
Update profile |
GET |
/api/users/me/stats |
Get user stats |
GET |
/api/users/me/billing |
Get billing history |
GET |
/api/users/me/billing/:orderId/invoice |
Get invoice for an order |
POST |
/api/users/me/billing/portal |
Open the Polar billing portal |
POST |
/api/users/me/license/checkout |
Purchase a ClawHostGo desktop license |
POST |
/api/users/me/auth/:method |
Connect an auth method (Google/GitHub) |
DELETE |
/api/users/me/auth/:method |
Disconnect an auth method |
Affiliate
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/affiliate |
Get the user's affiliate dashboard |
POST |
/api/affiliate/generate |
Generate a new referral code |
PUT |
/api/affiliate/code |
Update the user's referral code |
Cron — called by an external scheduler; not protected by Firebase auth. Each request must include Authorization: Bearer ${CRON_SECRET} matching the API env var.
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/cron/generate-blog-post |
Generate the daily AI blog post |
GET |
/api/cron/send-feature-emails |
Send feature announcement emails |
GET |
/api/cron/cleanup-expired-otps |
Delete expired OTP codes |
GET |
/api/cron/cleanup-stale-rate-limits |
Prune stale rate-limit rows |
WebSocket
| Protocol | Endpoint | Description |
|---|---|---|
WebSocket |
/ws/agents/:id/terminal?token= |
Live SSH terminal session, proxied through the API |
The web app builds to apps/web/dist/ as a static SPA with pre-rendered pages and a generated sitemap. Deploy to any static hosting provider:
- Cloudflare Pages (includes
_redirectsandfunctions/for SPA rewrites) - Netlify
- Nginx / Apache
bun buildThe API runs as a Hono.js application on Bun with native WebSocket for terminal access:
cd apps/api
bun start # HTTP + WebSocket on PORT (default 2222)When a user deploys a new agent, the platform:
- Creates a checkout — Initiates a Polar.sh subscription for the selected plan
- Generates cloud-init —
generateCloudInit.tsproduces a per-server bootstrap script tailored to the selected agent runtime (OpenClaw or Hermes), subdomain, gateway token, and tool defaults - Provisions a VPS — Spins up a server on Hetzner Cloud with that cloud-init payload
- Configures DNS — Creates a Cloudflare A record pointing to the server IP
- Delivers access — User gets a subdomain URL, root password, and SSH access
Shared (every agent):
- Node.js + system dependencies
- A dedicated unprivileged service user (
openclaworhermes) - A
systemdunit so the gateway restarts on failure and at boot - A health-check loop that retries until the gateway responds locally
OpenClaw runtime:
openclawinstalled globally vianpm- Google Chrome (for headless browser tooling)
- OpenClaw gateway on
127.0.0.1:18789, fronted by an Nginx reverse proxy on:443 - Nginx with WebSocket upgrade support and per-tenant CSP headers
- Let's Encrypt SSL via Certbot, with a renewal cron entry
- Homebrew install kicked off in the background for the service user
Hermes runtime:
- Hermes Agent installed for the
hermesuser via the upstream installer (raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.sh) hermes-gateway.servicerunshermes gateway startundersystemd- Hermes manages its own networking and TLS, so no Nginx/Certbot is provisioned
The gateway token is generated at purchase time, stored encrypted in the
agents table (AES-256-GCM), and baked into the cloud-init script so the
dashboard can authenticate against the gateway over HTTPS without ever exposing
the token to the browser. Tokens can be rotated via
POST /api/agents/:id/rotate-gateway-token.
Once provisioned, users can manage their agent through the dashboard — viewing metrics, browsing logs, editing config files over SSH, and dropping into a browser-based terminal that proxies an SSH session over the API's WebSocket.
Instances get subdomains like abc1234.yourdomain.com. To use your own domain, update the Cloudflare zone configuration and the cloud-init template.
The default pricing markup on cloud provider base prices is configurable in the plans controller.
Cloud-init is generated in code, not from a static YAML file. Edit
apps/api/src/controllers/agents/helpers/generateCloudInit.ts to change what
gets installed on new instances — add packages, change the agent install
command, tweak the Nginx config, or alter the gateway systemd unit. Both the
OpenClaw and Hermes bootstrap paths live there.
All UI text is managed through @openclaw/i18n. Translation strings live in packages/i18n/src/langs/en.ts, organized by category (common, nav, auth, dashboard, landing, etc.).
SSL certificates not working
Instances may take 1-2 minutes for SSL certificates to provision after the server boots. The cloud-init script includes retry logic for certificate generation. Ensure ports 80 and 443 are open.
DNS not resolving
New subdomains may take 1-5 minutes to propagate through Cloudflare. Check that your Cloudflare API token has Zone:DNS:Edit permission and the Zone ID is correct.
Firebase auth not working
- Verify your domain is listed in Firebase Authorized domains
- Confirm Email/Password sign-in is enabled under Authentication > Sign-in method
- If using Google/GitHub auth, ensure those providers are configured
- Double-check that all
VITE_FIREBASE_*values match your Firebase project
Database connection errors
- Verify
DATABASE_URLis correct and includes?sslmode=requirefor hosted databases - Run
bun --filter api db:migrateto apply any pending migrations - Use
bun --filter api db:studioto inspect the database directly
Contributions are welcome! Please open an issue first to discuss what you'd like to change.
- Fork the repository
- Create your feature branch (
git checkout -b feature/my-feature) - Make your changes following the project's code conventions
- Run
bun checkto verify TypeScript and linting pass - Run
bun formatto ensure formatting is correct - Commit and push your changes
- Open a pull request
MIT License — see LICENSE for details.