Skip to content

Instantly share code, notes, and snippets.

@JacobYZ
Last active March 11, 2026 17:54
Show Gist options
  • Select an option

  • Save JacobYZ/1752e639b2a00d732f2536f7defefe85 to your computer and use it in GitHub Desktop.

Select an option

Save JacobYZ/1752e639b2a00d732f2536f7defefe85 to your computer and use it in GitHub Desktop.

From Zero to OpenClaw on Proxmox LXC (Debian 13): A Real-World Build Log

I wanted a self-hosted OpenClaw deployment on Proxmox, reachable on my LAN, with:

  • Gemini API key support
  • Gemini CLI OAuth (Google AI Pro)
  • Telegram bot integration
  • Brave Search API integration
  • Browser tool support in an LXC
  • OpenRouter free model option (without Gemini CLI install)
  • A second LXC that reuses the same working OpenClaw state/config

This post is the full reproducible path I used, including fixes and gotchas.

All secrets are shown as placeholders.
Replace values like <GEMINI_API_KEY>, <TELEGRAM_BOT_TOKEN>, and <OPENCLAW_GATEWAY_TOKEN>.

Table of Contents

  1. Architecture and End State
  2. Prerequisites
  3. Build the First OpenClaw LXC (101)
  4. Configure OpenClaw Gateway + Model
  5. Add Telegram, Brave Search, and Gemini CLI OAuth
  6. Move to LXC 361 (DHCP + SLAAC)
  7. Enable Browser Tool in LXC
  8. Clone the Setup to LXC 362 with OpenClaw v2026.2.9
  9. Upgrade Existing LXC 361 (2026.1.29 to 2026.2.9)
  10. Upgrade LXC 361 to 2026.2.12 and rotate Gemini CLI account
  11. Upgrade LXC 361 to 2026.2.19 and fix Telegram after update
  12. Deploy LXC 363 with OpenClaw 2026.2.15 + OpenRouter Free (No Gemini CLI)
  13. Deploy LXC 365 with OpenClaw 2026.2.26 and Google Primary Model
  14. Troubleshooting + Security

Architecture and End State

  • Proxmox host runs Debian 13 LXCs.
  • Primary OpenClaw LXC eventually became 361 (DHCP/SLAAC).
  • Later, I deployed OpenClaw v2026.2.9 into a new 362 using the same OpenClaw config/state pattern.
  • 361 was later upgraded in place through 2026.2.12 and 2026.2.19, with a Telegram post-update network tweak.
  • 363 was deployed with OpenClaw 2026.2.15, but using OpenRouter free model routing instead of Gemini CLI.
  • 365 was later deployed with OpenClaw 2026.2.26, reusing API-key based routing (Google primary + OpenRouter fallbacks).
  • OpenClaw gateway listens on 0.0.0.0:18789 with token auth.
  • Control UI is accessed via:
    • http://<LAN_IP>:18789/?token=<OPENCLAW_GATEWAY_TOKEN> for older non-secure-context flows
    • https://<YOUR_HOSTNAME>/?token=<OPENCLAW_GATEWAY_TOKEN> for secure browser device-identity flows

Prerequisites

  • Proxmox VE node with pct, pveam, pvesh
  • Bridge network (e.g. vmbr0) with DHCP/IPv6 RA available if using DHCP/SLAAC
  • Enough disk for Chromium + Playwright (browser tooling can add substantial footprint)

Step 1: Download Debian 13 LXC Template

pveam update
pveam available --section system | grep debian-13
pveam download local debian-13-standard_13.1-2_amd64.tar.zst

Step 2: Create the First OpenClaw Container (ID 101)

I initially created 101 with a static IP, then later cloned/migrated to 361.

# Optional: find next available VMID
pvesh get /cluster/nextid

# Generate and store initial root password
PW=$(openssl rand -base64 24)
printf '%s\n' "$PW" > /root/openclaw-ct101-password.txt
chmod 600 /root/openclaw-ct101-password.txt

pct create 101 local:vztmpl/debian-13-standard_13.1-2_amd64.tar.zst \
  --hostname openclaw \
  --password "$PW" \
  --cores 2 --memory 2048 --swap 512 \
  --rootfs local-lvm:8 \
  --net0 name=eth0,bridge=vmbr0,ip=192.168.31.120/24,gw=192.168.31.1 \
  --features keyctl=1,nesting=1 \
  --unprivileged 1 \
  --onboot 1 \
  --nameserver 192.168.31.1

pct start 101

Step 3: Install Node.js 22 and Core Packages Inside the LXC

pct exec 101 -- bash -lc '
  apt-get update &&
  apt-get install -y ca-certificates curl gnupg git &&
  mkdir -p /etc/apt/keyrings &&
  curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key \
    | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg &&
  echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_22.x nodistro main" \
    > /etc/apt/sources.list.d/nodesource.list &&
  apt-get update &&
  apt-get install -y nodejs
'

Step 4: Install OpenClaw

pct exec 101 -- bash -lc "npm install -g openclaw@2026.1.29"

Step 5: Configure OpenClaw (Gateway + Secrets + Model)

5.1 Create secrets env file

Create /etc/openclaw/openclaw.env:

GEMINI_API_KEY=<GEMINI_API_KEY>
OPENCLAW_GATEWAY_TOKEN=<RANDOM_TOKEN>
BRAVE_API_KEY=<BRAVE_API_KEY>   # added later

Permissions:

install -d -m 700 /etc/openclaw
chmod 600 /etc/openclaw/openclaw.env

5.2 Create main OpenClaw config

Create /root/.openclaw/openclaw.json:

{
  "agents": {
    "defaults": {
      "model": {
        "primary": "google/gemini-flash-latest"
      }
    }
  },
  "gateway": {
    "port": 18789,
    "mode": "local",
    "bind": "lan",
    "auth": {
      "mode": "token"
    },
    "controlUi": {
      "allowInsecureAuth": true
    }
  }
}

Why allowInsecureAuth: true?
Because we were exposing HTTP on LAN and needed token-based access without HTTPS/Tailscale at this stage.


Step 6: Run OpenClaw as a systemd Service

Create /etc/systemd/system/openclaw-gateway.service:

[Unit]
Description=OpenClaw Gateway
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=root
EnvironmentFile=/etc/openclaw/openclaw.env
WorkingDirectory=/root
ExecStart=/usr/bin/openclaw gateway run --bind lan --port 18789 --auth token
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target

Enable and start:

systemctl daemon-reload
systemctl enable --now openclaw-gateway.service

Step 7: Access the Control UI from LAN

Use token in query string:

http://<LAN_IP>:18789/?token=<OPENCLAW_GATEWAY_TOKEN>

Quick probe from host:

curl -I http://<LAN_IP>:18789

Step 8: Telegram Bot Integration

Docs reference: https://docs.openclaw.ai/channels/telegram

Update config:

{
  "channels": {
    "telegram": {
      "enabled": true,
      "botToken": "<TELEGRAM_BOT_TOKEN>",
      "dmPolicy": "pairing"
    }
  }
}

Then restart gateway:

systemctl restart openclaw-gateway.service

If using pairing mode:

openclaw pairing list telegram
openclaw pairing approve telegram <CODE>

Step 9: Add Brave Search API Key

OpenClaw web search uses BRAVE_API_KEY in /etc/openclaw/openclaw.env.

After adding/updating key:

systemctl restart openclaw-gateway.service

Step 10: Enable Gemini CLI OAuth (Google AI Pro)

10.1 Install Gemini CLI

npm install -g @google/gemini-cli

10.2 Enable plugin and run login

openclaw plugins enable google-gemini-cli-auth
systemctl restart openclaw-gateway.service

openclaw models auth login --provider google-gemini-cli --method oauth

In remote/VPS mode, OpenClaw prints a login URL. You:

  1. Open it locally
  2. Sign in with Google
  3. Paste the callback URL (http://localhost:8085/oauth2callback?...) back into the prompt

The OAuth profile is stored under:

/root/.openclaw/agents/main/agent/auth-profiles.json

Step 11: Set Model to Gemini CLI 3 Pro

Set:

{
  "agents": {
    "defaults": {
      "model": {
        "primary": "google-gemini-cli/gemini-3-pro-preview"
      },
      "models": {
        "google-gemini-cli/gemini-3-pro-preview": {}
      }
    }
  }
}

Verify:

openclaw models status --plain

Step 12: Move from LXC 101 to LXC 361 (DHCP + SLAAC)

I switched from static to DHCP/SLAAC by cloning and reconfiguring networking.

pct shutdown 101
pct clone 101 361 --full --hostname openclaw
pct set 361 --net0 name=eth0,bridge=vmbr0,ip=dhcp,ip6=auto
pct start 361

Verify addresses:

pct exec 361 -- ip -4 addr show dev eth0
pct exec 361 -- ip -6 addr show dev eth0

Remove old container:

pct destroy 101

Step 13: Enable Browser Tool in LXC

Docs reference: https://docs.openclaw.ai/tools/browser

13.1 Browser config

{
  "browser": {
    "enabled": true,
    "defaultProfile": "openclaw",
    "headless": true,
    "noSandbox": true
  }
}

headless: true + noSandbox: true is important for many LXC setups.

13.2 Install dependencies

apt-get update
apt-get install -y chromium
npm install -g playwright

13.3 Start and verify

openclaw browser start
openclaw browser status --json

Expected:

  • running: true
  • cdpReady: true
  • chosenBrowser: chromium

Step 14: Deploy Same OpenClaw Setup to New LXC 362 (OpenClaw v2026.2.9)

Later, I created 362 and reused the working setup pattern from 361.

14.1 Install runtime and OpenClaw on 362

# inside 362
apt-get update
apt-get install -y ca-certificates curl gnupg git chromium

mkdir -p /etc/apt/keyrings
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key \
  | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_22.x nodistro main" \
  > /etc/apt/sources.list.d/nodesource.list
apt-get update
apt-get install -y nodejs

npm install -g openclaw@2026.2.9 playwright @google/gemini-cli

14.2 Copy OpenClaw state/config from 361 to 362

From Proxmox host:

pct exec 361 -- bash -lc "tar -C / -cf - \
  root/.openclaw \
  etc/openclaw/openclaw.env \
  etc/systemd/system/openclaw-gateway.service" > /root/openclaw-361-sync.tar

pct push 362 /root/openclaw-361-sync.tar /root/openclaw-361-sync.tar
pct exec 362 -- bash -lc "tar -C / -xf /root/openclaw-361-sync.tar && rm -f /root/openclaw-361-sync.tar"

14.3 Enable service on 362

pct exec 362 -- bash -lc "
  systemctl daemon-reload &&
  systemctl enable --now openclaw-gateway.service &&
  systemctl restart openclaw-gateway.service
"

14.4 Validate

pct exec 362 -- openclaw --version
pct exec 362 -- openclaw models status --plain
pct exec 362 -- systemctl status openclaw-gateway.service
curl -I http://<LXC_362_IP>:18789

Step 15: Upgrade OpenClaw on Existing LXC 361 (2026.1.29 to 2026.2.9)

This is the exact update flow I used later, after 361 had already been running.

15.1 (Recommended) Create a Proxmox snapshot first

pct snapshot 361 pre-openclaw-2026-2-9

15.2 Run OpenClaw updater

pct exec 361 -- sh -lc "openclaw update --tag 2026.2.9 --yes"

In my run, the package upgrade itself succeeded, but the updater reported a daemon restart issue:

  • Daemon restart failed: SyntaxError: The requested module './entry.js' does not provide an export named 'ut'

The fix was just a manual service restart.

15.3 Restart gateway manually

pct exec 361 -- sh -lc "systemctl restart openclaw-gateway.service"

15.4 Verify success

pct exec 361 -- sh -lc "openclaw --version"
pct exec 361 -- sh -lc "systemctl --no-pager --full status openclaw-gateway.service"
pct exec 361 -- sh -lc "openclaw models status --plain"

Expected:

  • OpenClaw version is 2026.2.9
  • Gateway is active (running)
  • Model remains the configured default (in this setup: google-gemini-cli/gemini-3-pro-preview)

15.5 Optional fallback if updater fails

pct exec 361 -- sh -lc "npm install -g openclaw@2026.2.9 && systemctl restart openclaw-gateway.service"

Step 16: Upgrade LXC 361 to 2026.2.12 and rotate Gemini CLI account

This captures the later follow-up where 361 moved from 2026.2.9 to 2026.2.12, and Gemini CLI OAuth switched to guituyu38@gmail.com.

16.1 Run updater

pct exec 361 -- sh -lc "openclaw update --tag 2026.2.12 --yes"

In this run, package update succeeded, but the updater ended with a service-check warning:

  • Gateway service check failed: Error: systemctl --user unavailable: Failed to connect to user scope bus via local transport: No such file or directory

As before, a manual service restart fixed runtime state.

16.2 Restart and verify

pct exec 361 -- sh -lc "systemctl restart openclaw-gateway.service"
pct exec 361 -- sh -lc "openclaw --version"
pct exec 361 -- sh -lc "systemctl --no-pager --full status openclaw-gateway.service"
pct exec 361 -- sh -lc "openclaw models status --plain"

Expected:

  • OpenClaw version is 2026.2.12
  • Gateway is active (running)
  • Model remains google-gemini-cli/gemini-3-pro-preview

16.3 Switch Gemini CLI OAuth account

Run this from an interactive shell in container 361:

pct enter 361
openclaw models auth login --provider google-gemini-cli --method oauth

Then:

  1. Open the Google URL shown by the CLI in your local browser.
  2. Sign in as guituyu38@gmail.com.
  3. Paste the full callback URL (http://localhost:8085/oauth2callback?...) back into the prompt.

After success, exit container shell:

exit

16.4 Pin new profile and remove old profile

Pin auth selection to the new profile:

pct exec 361 -- sh -lc "openclaw models auth order set --provider google-gemini-cli 'google-gemini-cli:guituyu38@gmail.com'"
pct exec 361 -- sh -lc "openclaw models auth order get --provider google-gemini-cli"

Remove old profile (yujingzzzz@gmail.com) from auth store:

pct exec 361 -- sh -lc "python3 - <<'PY'
import json
p='/root/.openclaw/agents/main/agent/auth-profiles.json'
with open(p,'r') as f:
    data=json.load(f)
profiles=data.setdefault('profiles',{})
profiles.pop('google-gemini-cli:yujingzzzz@gmail.com', None)
data.setdefault('order', {})['google-gemini-cli']=['google-gemini-cli:guituyu38@gmail.com']
with open(p,'w') as f:
    json.dump(data,f,indent=2)
    f.write('\\n')
PY"

Quick verification:

pct exec 361 -- sh -lc "python3 - <<'PY'
import json
p='/root/.openclaw/agents/main/agent/auth-profiles.json'
with open(p,'r') as f:
    data=json.load(f)
print(list(data.get('profiles',{}).keys()))
print(data.get('order',{}).get('google-gemini-cli'))
PY"

Expected output should only include google-gemini-cli:guituyu38@gmail.com.


Step 17: Upgrade LXC 361 to 2026.2.19 and fix Telegram after update

This section captures the later upgrade from 2026.2.12 to 2026.2.19, including a Telegram regression fix.

17.1 (Recommended) Backup config/state before upgrading

pct exec 361 -- sh -lc 'ts=$(date +%Y%m%d-%H%M%S); \
  mkdir -p "/root/.openclaw/backups/$ts" && \
  cp /root/.openclaw/openclaw.json "/root/.openclaw/backups/$ts/openclaw.json" && \
  cp /root/.openclaw/agents/main/agent/auth-profiles.json "/root/.openclaw/backups/$ts/auth-profiles.json" && \
  cp /etc/openclaw/openclaw.env "/root/.openclaw/backups/$ts/openclaw.env" && \
  echo "backup_dir=/root/.openclaw/backups/$ts"'

17.2 Run updater with controlled restart

I used --no-restart and then restarted systemd manually. This avoids the updater's service-check quirks in this LXC environment.

pct exec 361 -- sh -lc "openclaw update --tag 2026.2.19 --yes --no-restart"
pct exec 361 -- sh -lc "systemctl restart openclaw-gateway.service"

17.3 Verify core upgrade health

pct exec 361 -- sh -lc "openclaw --version"
pct exec 361 -- sh -lc "systemctl --no-pager --full status openclaw-gateway.service"
pct exec 361 -- sh -lc "openclaw update status --json"

Expected:

  • OpenClaw version is 2026.2.19
  • Gateway is active (running)
  • Update channel remains stable

17.4 Telegram fix for 2026.2.19 in this LXC

After this upgrade, Telegram startup logged command-sync network failures in this environment (setMyCommands / deleteMyCommands).
The fix was to explicitly set:

  • channels.telegram.network.autoSelectFamily = false

Apply fix:

pct exec 361 -- sh -lc "python3 - <<'PY'
import json
p='/root/.openclaw/openclaw.json'
with open(p,'r') as f:
    cfg=json.load(f)
tele=cfg.setdefault('channels',{}).setdefault('telegram',{})
tele.setdefault('network',{})['autoSelectFamily']=False
with open(p,'w') as f:
    json.dump(cfg,f,indent=2)
    f.write('\\n')
PY"

pct exec 361 -- sh -lc "systemctl restart openclaw-gateway.service"

17.5 Verify Telegram after the fix

# Confirm log now shows "autoSelectFamily=false (config)" for Telegram startup
pct exec 361 -- sh -lc "journalctl -u openclaw-gateway.service -n 120 --no-pager"

# Verify Telegram API reachability from inside the container
pct exec 361 -- sh -lc "python3 - <<'PY'
import json, urllib.request
cfg=json.load(open('/root/.openclaw/openclaw.json'))
token=((cfg.get('channels') or {}).get('telegram') or {}).get('botToken')
for method in ('getMe','getWebhookInfo','getMyCommands'):
    with urllib.request.urlopen('https://api.telegram.org/bot'+token+'/'+method, timeout=20) as r:
        data=json.loads(r.read().decode())
    print(method, data.get('ok'))
PY"

If all return True, Telegram connectivity is healthy again.


Step 18: Deploy LXC 363 with OpenClaw 2026.2.15 + OpenRouter Free (No Gemini CLI)

This reproduces the same working node pattern on a fresh Debian 13 container (363), with one key difference:

  • Do not install Gemini CLI
  • Use OpenRouter with openrouter/openrouter/free as the default model

Important distinction:

  • "No Gemini CLI" means you do not install @google/gemini-cli and do not use Gemini CLI OAuth plugin flows.
  • You can still use Google API models (for example google/gemini-2.5-flash-lite) with GEMINI_API_KEY.

18.1 Install runtime + OpenClaw on 363

pct exec 363 -- bash -lc '
  apt-get update &&
  apt-get install -y ca-certificates curl gnupg git chromium &&
  mkdir -p /etc/apt/keyrings &&
  curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key \
    | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg &&
  echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_22.x nodistro main" \
    > /etc/apt/sources.list.d/nodesource.list &&
  apt-get update &&
  apt-get install -y nodejs &&
  npm install -g openclaw@2026.2.15 playwright
'

Verify:

pct exec 363 -- sh -lc "node -v && npm -v"
pct exec 363 -- sh -lc "openclaw --version"

18.2 Create env file for OpenRouter + gateway + Brave

pct exec 363 -- bash -lc '
  TOKEN=$(openssl rand -hex 24)
  install -d -m 700 /etc/openclaw
  cat > /etc/openclaw/openclaw.env <<EOF
GEMINI_API_KEY=<GEMINI_API_KEY>
OPENROUTER_API_KEY=<OPENROUTER_API_KEY>
OPENCLAW_GATEWAY_TOKEN=${TOKEN}
BRAVE_API_KEY=<BRAVE_API_KEY>
EOF
  chmod 600 /etc/openclaw/openclaw.env
'

Use your real OpenRouter key (starts with sk-or-v1-...).
Keep this file root-only.

Keep GEMINI_API_KEY when your primary model is under google/*.

18.3 Configure OpenClaw for OpenRouter free model

Create /root/.openclaw/openclaw.json:

{
  "agents": {
    "defaults": {
      "model": {
        "primary": "openrouter/openrouter/free"
      },
      "models": {
        "openrouter/openrouter/free": {}
      }
    }
  },
  "gateway": {
    "port": 18789,
    "mode": "local",
    "bind": "lan",
    "auth": {
      "mode": "token",
      "token": "<OPENCLAW_GATEWAY_TOKEN>"
    },
    "remote": {
      "token": "<OPENCLAW_GATEWAY_TOKEN>"
    },
    "controlUi": {
      "allowInsecureAuth": true
    }
  },
  "channels": {
    "telegram": {
      "enabled": true,
      "botToken": "<TELEGRAM_BOT_TOKEN>",
      "dmPolicy": "pairing",
      "network": {
        "autoSelectFamily": false
      }
    }
  },
  "browser": {
    "enabled": true,
    "defaultProfile": "openclaw",
    "headless": true,
    "noSandbox": true
  }
}

18.4 Enable gateway service

If /etc/systemd/system/openclaw-gateway.service does not exist yet on 363, create it (same unit as Step 6):

pct exec 363 -- bash -lc "cat > /etc/systemd/system/openclaw-gateway.service <<'EOF'
[Unit]
Description=OpenClaw Gateway
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=root
EnvironmentFile=/etc/openclaw/openclaw.env
WorkingDirectory=/root
ExecStart=/usr/bin/openclaw gateway run --bind lan --port 18789 --auth token
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
EOF"

Then enable/start/restart:

pct exec 363 -- sh -lc "
  systemctl daemon-reload &&
  systemctl enable --now openclaw-gateway.service &&
  systemctl restart openclaw-gateway.service
"

18.5 Validate OpenRouter model and LAN access

pct exec 363 -- sh -lc "openclaw --version"
pct exec 363 -- sh -lc "systemctl --no-pager --full status openclaw-gateway.service"
pct exec 363 -- sh -lc "openclaw models status --plain"
curl -I http://<LXC_363_IP>:18789

Probe model auth from env:

pct exec 363 -- sh -lc "set -a; . /etc/openclaw/openclaw.env; set +a; openclaw models status --probe --json"

Expected:

  • version is 2026.2.15
  • default model is openrouter/openrouter/free
  • probe result for provider openrouter shows status: ok

18.6 Confirm Gemini CLI is intentionally absent

pct exec 363 -- sh -lc "gemini --version"

Expected: gemini: not found

18.7 Optional mixed-provider setup (Google primary + OpenRouter fallbacks)

If google/gemini-2.5-flash-lite works well for you, this is valid without Gemini CLI. Use Google API key auth for primary and OpenRouter for fallbacks:

{
  "agents": {
    "defaults": {
      "model": {
        "primary": "google/gemini-2.5-flash-lite",
        "fallbacks": [
          "openrouter/stepfun/step-3.5-flash:free",
          "openrouter/openrouter/free"
        ]
      },
      "models": {
        "google/gemini-2.5-flash-lite": {},
        "openrouter/stepfun/step-3.5-flash:free": {},
        "openrouter/openrouter/free": {}
      }
    }
  }
}

Notes:

  • When agents.defaults.models exists, it acts as an allowlist.
  • Include the primary model and all fallback models in that map.
  • This still does not require Gemini CLI installation.

Step 19: Deploy LXC 365 with OpenClaw 2026.2.26 and Google Primary Model

This reproduces the API-key model setup onto a new Debian 13 container (365) and sets:

  • Primary model: google/gemini-3.1-pro-preview
  • OpenRouter fallbacks for resilience
  • Control UI compatibility setting required by 2026.2.26 on non-loopback bind

19.1 Install runtime + OpenClaw on 365

pct exec 365 -- bash -lc '
  apt-get update &&
  apt-get install -y ca-certificates curl gnupg git chromium &&
  mkdir -p /etc/apt/keyrings &&
  curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key \
    | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg &&
  echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_22.x nodistro main" \
    > /etc/apt/sources.list.d/nodesource.list &&
  apt-get update &&
  apt-get install -y nodejs &&
  npm install -g openclaw@2026.2.26 playwright
'

Verify:

pct exec 365 -- sh -lc "node -v && npm -v"
pct exec 365 -- sh -lc "openclaw --version"

19.2 Create env file on 365 (reuse API keys, new gateway token)

pct exec 365 -- bash -lc '
  TOKEN=$(openssl rand -hex 24)
  install -d -m 700 /etc/openclaw
  cat > /etc/openclaw/openclaw.env <<EOF
GEMINI_API_KEY=<GEMINI_API_KEY_FROM_363>
OPENROUTER_API_KEY=<OPENROUTER_API_KEY_FROM_363>
OPENCLAW_GATEWAY_TOKEN=${TOKEN}
BRAVE_API_KEY=<BRAVE_API_KEY_FROM_363>
EOF
  chmod 600 /etc/openclaw/openclaw.env
'

19.3 Configure /root/.openclaw/openclaw.json on 365

{
  "agents": {
    "defaults": {
      "model": {
        "primary": "google/gemini-3.1-pro-preview",
        "fallbacks": [
          "openrouter/stepfun/step-3.5-flash:free",
          "openrouter/arcee-ai/trinity-large-preview:free",
          "openrouter/openrouter/free"
        ]
      },
      "models": {
        "google/gemini-3.1-pro-preview": {},
        "google/gemini-2.5-flash-lite": {},
        "openrouter/openrouter/free": {},
        "openrouter/arcee-ai/trinity-large-preview:free": {},
        "openrouter/stepfun/step-3.5-flash:free": {}
      }
    }
  },
  "gateway": {
    "port": 18789,
    "mode": "local",
    "bind": "lan",
    "auth": {
      "mode": "token",
      "token": "<OPENCLAW_GATEWAY_TOKEN>"
    },
    "remote": {
      "token": "<OPENCLAW_GATEWAY_TOKEN>"
    },
    "controlUi": {
      "allowInsecureAuth": true,
      "dangerouslyAllowHostHeaderOriginFallback": true
    }
  },
  "channels": {
    "telegram": {
      "enabled": true,
      "botToken": "<TELEGRAM_BOT_TOKEN>",
      "dmPolicy": "pairing",
      "network": {
        "autoSelectFamily": false
      }
    }
  },
  "browser": {
    "enabled": true,
    "defaultProfile": "openclaw",
    "headless": true,
    "noSandbox": true
  }
}

dangerouslyAllowHostHeaderOriginFallback: true is required for this release when Control UI is exposed on non-loopback bind without explicit allowedOrigins.

19.4 Enable/start gateway service on 365

If /etc/systemd/system/openclaw-gateway.service does not exist yet on 365, create it (same unit as Step 6), then:

pct exec 365 -- sh -lc "
  systemctl daemon-reload &&
  systemctl enable --now openclaw-gateway.service &&
  systemctl restart openclaw-gateway.service
"

19.5 Validate deployment

pct exec 365 -- sh -lc "openclaw --version"
pct exec 365 -- sh -lc "systemctl --no-pager --full status openclaw-gateway.service"
pct exec 365 -- sh -lc "openclaw models status --plain"
pct exec 365 -- sh -lc "set -a; . /etc/openclaw/openclaw.env; set +a; openclaw models status --probe --json"
curl -I http://<LXC_365_IP>:18789

Expected:

  • version is 2026.2.26
  • service is active (running)
  • default model is google/gemini-3.1-pro-preview
  • providers probe with valid ok status for configured keys

19.6 Control UI access and first-time pairing on HTTPS hostname

In this release line:

  • https://<LXC_365_IP>:18789 fails (port serves plain HTTP, not TLS)
  • http://<LXC_365_IP>:18789 may fail browser identity checks in non-secure context

Use HTTPS hostname via reverse proxy/tunnel:

https://<YOUR_HOSTNAME>/?token=<OPENCLAW_GATEWAY_TOKEN>

If UI shows pairing required, approve pending device:

pct exec 365 -- sh -lc "openclaw devices list --json"
pct exec 365 -- sh -lc "openclaw devices approve --latest --json"
pct exec 365 -- sh -lc "openclaw devices list --json"

Confirm successful connect:

pct exec 365 -- sh -lc "journalctl -u openclaw-gateway.service -n 120 --no-pager"

Look for webchat connected.

19.7 Telegram token sharing caveat

If multiple LXCs use the same Telegram bot token simultaneously, one may get:

  • 409 Conflict: terminated by other getUpdates request

Keep only one active poller per token, or use separate bot tokens per LXC.


Operational Gotchas I Hit

1) Control UI on LAN HTTP in 2026.2.26

Typical symptoms:

  • http://<LAN_IP>:18789/?token=<OPENCLAW_GATEWAY_TOKEN> -> control ui requires device identity (use HTTPS or localhost secure context)
  • https://<LAN_IP>:18789/?token=<OPENCLAW_GATEWAY_TOKEN> -> ERR_SSL_PROTOCOL_ERROR

This is expected in this release line (:18789 serves plain HTTP, while browser identity checks prefer secure context).
Use HTTPS hostname access (or localhost workflows). Full commands and flow are in Step 19.6.

2) Telegram 409 getUpdates conflict

If two gateways run with the same Telegram bot token (e.g. 361 + 362, or 361 + 363), one will conflict:

  • Conflict: terminated by other getUpdates request

Fix:

  • Run only one active poller for that bot token, or
  • Use different bot tokens per instance.
  • See Step 19.7 for the same issue in the 365 deployment context.

3) Browser start issues in LXC

If browser fails to start:

  • Ensure chromium is installed
  • Ensure playwright is installed
  • Set browser.headless: true and browser.noSandbox: true

4) Telegram command sync failures after 2026.2.19 update

Symptom in logs right after gateway start:

  • deleteMyCommands failed: Network request for 'deleteMyCommands' failed!
  • setMyCommands failed: Network request for 'setMyCommands' failed!

Fix used on this node:

  • Set channels.telegram.network.autoSelectFamily to false in /root/.openclaw/openclaw.json
  • Restart openclaw-gateway.service

5) Cloudflare hostname shows Disconnected from gateway. pairing required

On first HTTPS hostname login, the Control UI device can stay pending until explicitly approved.
Use the openclaw devices list/approve --latest workflow and log verification from Step 19.6.


Security Notes

  • Keep /etc/openclaw/openclaw.env mode 600.
  • Treat OPENCLAW_GATEWAY_TOKEN, Telegram bot tokens, and API keys as secrets.
  • Prefer private network exposure or Tailscale for remote access.
  • Rotate tokens if shared accidentally.

Useful Paths

  • OpenClaw config: /root/.openclaw/openclaw.json
  • OpenClaw env secrets: /etc/openclaw/openclaw.env
  • OAuth profiles: /root/.openclaw/agents/main/agent/auth-profiles.json
  • Service unit: /etc/systemd/system/openclaw-gateway.service
  • Logs:
    • journalctl -u openclaw-gateway.service -f
    • OpenClaw runtime logs under /tmp/openclaw/

Quick Health Checklist

Run these after any change:

openclaw --version
openclaw models status --plain
openclaw browser status --json
systemctl is-active openclaw-gateway.service
curl -I http://127.0.0.1:18789

If all pass, your OpenClaw node is healthy and ready.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment