Skip to content

OpenClaw Deployment Guide

Status: Live as of 2026-02-27 URL: https://claw.baseworks.com Server: baseworks-claw (46.224.129.16)


Browser
|
v
Cloudflare (edge SSL, DDoS protection)
|
v
nginx (origin cert + HTTP Basic Auth)
|
v
OpenClaw Gateway (port 18789, localhost only)
|
v
OpenRouter API (LLM provider)

OpenClaw is the “brain” layer (natural language, agent routing, memory). n8n (on the separate Agents VPS) is the “hands” layer (deterministic execution, file writes, API calls, Slack posts). OpenClaw calls n8n via webhooks.


FieldValue
ProviderHetzner via xCloud
Server namebaseworks-claw
IP46.224.129.16
OSUbuntu 24.04.4 LTS
SpecsCX33 — 4 vCPU, 8GB RAM, 75GB disk
Docker29.2.1
SSHssh patrick@46.224.129.16

Terminal window
ssh patrick@46.224.129.16

Patrick has passwordless sudo via /etc/sudoers.d/patrick.

  • Username: agents@baseworks.com
  • Password: ouqUAZuwVwhWYuS8riZLsUFj
de0b1a3a7023da19d797bbcc11c830880029828df65907c00494427a72d211aa
https://claw.baseworks.com/#token=de0b1a3a7023da19d797bbcc11c830880029828df65907c00494427a72d211aa

Same key as n8n VPS. Stored in:

  • /opt/baseworks-claw/openclaw/.env
  • /opt/baseworks-claw/config/openclaw.json

PathPurpose
/opt/baseworks-claw/openclaw/Git repo (cloned from github.com/openclaw/openclaw)
/opt/baseworks-claw/openclaw/.envDocker Compose environment variables
/opt/baseworks-claw/openclaw/docker-compose.ymlMain compose file (from repo)
/opt/baseworks-claw/openclaw/docker-compose.override.ymlOur override (adds OPENROUTER_API_KEY)
/opt/baseworks-claw/config/OpenClaw config (mounted into container)
/opt/baseworks-claw/config/openclaw.jsonMain config file
/opt/baseworks-claw/workspace/OpenClaw workspace (files available to agents)
/opt/baseworks-claw/state/Persistent state
/opt/baseworks-claw/credentials/Credentials storage
/etc/nginx/sites-available/claw.baseworks.comnginx site config
/etc/nginx/.openclaw_htpasswdHTTP Basic Auth credentials
/etc/ssl/cloudflare/baseworks.com.pemCloudflare origin certificate
/etc/ssl/cloudflare/baseworks.com.keyCloudflare origin private key

{
"gateway": {
"mode": "local",
"bind": "lan",
"port": 18789,
"auth": {
"mode": "token",
"token": "<gateway-token>"
},
"controlUi": {
"allowedOrigins": ["https://claw.baseworks.com"]
}
},
"models": {
"providers": {
"openrouter": {
"baseUrl": "https://openrouter.ai/api/v1",
"apiKey": "<openrouter-key>",
"models": [
{
"id": "anthropic/claude-sonnet-4",
"name": "Claude Sonnet 4 (OpenRouter)",
"input": ["text", "image"],
"contextWindow": 200000,
"maxTokens": 16384
},
{
"id": "anthropic/claude-haiku-4",
"name": "Claude Haiku 4 (OpenRouter)",
"input": ["text", "image"],
"contextWindow": 200000,
"maxTokens": 16384
},
{
"id": "google/gemini-2.0-flash-001",
"name": "Gemini 2.0 Flash (OpenRouter)",
"input": ["text", "image"],
"contextWindow": 1000000,
"maxTokens": 8192
}
]
}
}
}
}
server {
listen 80;
server_name claw.baseworks.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name claw.baseworks.com;
ssl_certificate /etc/ssl/cloudflare/baseworks.com.pem;
ssl_certificate_key /etc/ssl/cloudflare/baseworks.com.key;
location / {
auth_basic "OpenClaw — Baseworks";
auth_basic_user_file /etc/nginx/.openclaw_htpasswd;
proxy_pass http://127.0.0.1:18789;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 86400s;
proxy_send_timeout 86400s;
}
}
services:
openclaw-gateway:
environment:
OPENROUTER_API_KEY: ${OPENROUTER_API_KEY}
openclaw-cli:
environment:
OPENROUTER_API_KEY: ${OPENROUTER_API_KEY}

Terminal window
cd /opt/baseworks-claw/openclaw
sudo docker compose logs -f openclaw-gateway
Terminal window
sudo docker exec openclaw-openclaw-gateway-1 node dist/index.js health
Terminal window
cd /opt/baseworks-claw/openclaw
sudo docker compose down && sudo docker compose up -d openclaw-gateway
Terminal window
cd /opt/baseworks-claw/openclaw
sudo docker compose exec openclaw-gateway node dist/index.js devices list
sudo docker compose exec openclaw-gateway node dist/index.js devices approve <requestId>
Terminal window
cd /opt/baseworks-claw/openclaw
sudo docker compose run --rm openclaw-cli doctor --fix
Terminal window
cd /opt/baseworks-claw/openclaw
sudo git pull
sudo docker build -t openclaw:local -f Dockerfile .
sudo docker compose down && sudo docker compose up -d openclaw-gateway
Terminal window
# WhatsApp (QR code):
sudo docker compose run --rm openclaw-cli channels login
# Telegram:
sudo docker compose run --rm openclaw-cli channels add --channel telegram --token <bot-token>
# Slack:
sudo docker compose run --rm openclaw-cli channels add --channel slack --token <bot-token>

There are 3 layers to grant access through. All 3 are required.

Step 1: Create nginx Basic Auth credentials

Section titled “Step 1: Create nginx Basic Auth credentials”

SSH into the server and add a new username/password:

Terminal window
ssh patrick@46.224.129.16
sudo htpasswd /etc/nginx/.openclaw_htpasswd 'colleague@baseworks.com'
# You'll be prompted to set a password — share it with your colleague securely

To list existing users:

Terminal window
sudo cat /etc/nginx/.openclaw_htpasswd

To remove a user:

Terminal window
sudo htpasswd -D /etc/nginx/.openclaw_htpasswd 'colleague@baseworks.com'

Step 2: Share the dashboard URL with token

Section titled “Step 2: Share the dashboard URL with token”

Send your colleague this URL (it includes the gateway token so the UI auto-authenticates):

https://claw.baseworks.com/#token=de0b1a3a7023da19d797bbcc11c830880029828df65907c00494427a72d211aa

Along with their nginx Basic Auth username and password from Step 1.

When your colleague opens the URL for the first time, they’ll see “pairing required”. Their browser needs to be approved from the server:

Terminal window
ssh patrick@46.224.129.16
cd /opt/baseworks-claw/openclaw
# List pending pairing requests — find their requestId
sudo docker compose exec openclaw-gateway node dist/index.js devices list
# Approve their device
sudo docker compose exec openclaw-gateway node dist/index.js devices approve <requestId>

After approval, they should click Connect in the dashboard and it will go green.

  1. Go to: https://claw.baseworks.com/#token=de0b1a3a7023da19d797bbcc11c830880029828df65907c00494427a72d211aa
  2. When the browser prompts for a username/password, enter: colleague@baseworks.com / <password>
  3. You’ll see “pairing required” — let Patrick know and he’ll approve your device from the server
  4. Once approved, click Connect

There are 3 layers a user must pass through, in order:

Browser opens URL
Layer 1: nginx Basic Auth (username + password prompt)
│ ✓ Credentials match /etc/nginx/.openclaw_htpasswd
Layer 2: Gateway Token (stored in browser local storage)
│ ✓ Token matches the one in openclaw.json
Layer 3: Device Pairing (one-time per browser)
│ ✗ "pairing required" — new device not yet trusted
│ → Server admin runs: devices approve <requestId>
│ ✓ Device is now permanently paired
Connected — full access to Control UI

Day-to-day login: Only Layer 1 (username + password) is needed. The gateway token persists in browser local storage, and device pairing is permanent. You won’t be prompted for the token or pairing again unless you clear browser data or use a new device/browser.

When the token needs to be re-entered:

  • Browser data / cookies / local storage cleared
  • New browser or incognito window
  • Different device

When device pairing approval is needed again:

  • Same situations as above (clearing data generates a new device fingerprint)
  • The server admin must SSH in and run the approve command each time

OpenClaw is personal by default — it’s a single trust boundary. All paired users share the same agent permissions and tool access. For stricter multi-user setups, OpenClaw recommends separate gateway instances per user with isolated credentials. Fine for a small trusted team, but worth revisiting if more users are added.


  • Firewall (UFW): Ports 22, 80, 443 only
  • Fail2ban: SSH brute force protection (sshd jail)
  • Cloudflare: Edge SSL, DDoS protection (DNS proxied, orange cloud)
  • SSL: Cloudflare Origin Certificate (wildcard *.baseworks.com, expires 2041)
  • Auth layers: Cloudflare -> nginx Basic Auth -> OpenClaw gateway token -> device pairing
  • Isolation: Separate VPS from n8n to limit blast radius if compromised

  1. No pre-built Docker image — OpenClaw must be built from source (docker build -t openclaw:local). The openclaw/openclaw:latest image does not exist on Docker Hub.
  2. Interactive onboarding — The docker-setup.sh script requires interactive input. Can be bypassed by writing openclaw.json manually.
  3. Config schema — Use auth.mode (not auth.method), controlUi (not controlUI), and gateway.mode: "local" is required.
  4. apiKey format — Use a plain string for the API key. The {"env": "VAR_NAME"} shorthand doesn’t work; use {"source": "env", "provider": "...", "id": "..."} for env refs.
  5. Device pairing — New browser connections require server-side approval via devices approve <requestId>.
  6. xCloud sudo — Servers provisioned via xCloud don’t have passwordless sudo. Add via xCloud custom command: echo "patrick ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/patrick && chmod 440 /etc/sudoers.d/patrick
  7. Cloudflare proxy — With DNS proxied (orange cloud), use Cloudflare Origin Certificates instead of Let’s Encrypt. Set SSL mode to Full (Strict).

  1. Add messaging channels (Slack, Telegram, etc.)
  2. Configure agent identities and skills
  3. Connect OpenClaw to n8n via webhooks for the “hands” layer
  4. Build Vault Capture V2 (plan at 03-resources/agent-system/docs/Vault-Capture-V2-Plan.md)
  5. Reactivate n8n workflows one by one, replacing CrewAI calls with OpenClaw
  6. Set up embedding provider for memory search (OPENAI_API_KEY, GEMINI_API_KEY, VOYAGE_API_KEY, or MISTRAL_API_KEY)