# ๐ฆ
Setting Up OpenClaw: Self-Hosted AI Gateway on a VM (Telegram + Web)
**Date:** July 16, 2026
**Tags:** AI, OpenClaw, Self-Hosted, Systemd, Nginx, Telegram, AI Agents, Reverse Proxy
---
## ๐ฏ Overview
[OpenClaw](https://github.com/openclaw/openclaw) is a self-hosted **AI agent gateway** that exposes your models and agents over a clean HTTP API and over chat platforms like **Telegram**. Think of it as the "front door" for your AI stack: it brokers requests between chat clients and whatever model backend you point it at (local LLMs, OpenAI-compatible routers like [9Router](/blog/9router-setup/), or cloud providers).
Where [Hermes](/blog/hermes-setup/) is the *agent brain* (tool use, scheduling, memory), OpenClaw is the *gateway* โ it answers the question "how do I actually talk to my agents from Telegram or a web client without leaking infra details?"
### Why self-host OpenClaw?
| Feature | Benefit |
|---------|---------|
| **๐ Privacy First** | Your chat traffic and agent calls stay on your VM |
| **๐ก Multi-Platform** | Telegram bot + HTTP API from one gateway |
| **๐ Reverse Proxy** | HTTPS termination via nginx, no exposed ports |
| **๐ Pluggable** | Swap model backends without touching clients |
| **๐ ๏ธ Systemd Native** | Survives reboots, supervised, auto-restart |
| **๐งฉ 9Router Ready** | Point it at a free model router in one line |
---
## ๐๏ธ Architecture Overview
```
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Your VM (Linux) โ
โ โ
Telegram โโโโบ โ openclaw-gateway.service (port 18790) โ
(bot API) โ โ โ
โ โ forwards to model backend โ
โ โผ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
Browser โโโโโโบโ โ openclaw-https-proxy โโโโ nginx โ
(HTTPS) โ โ (port 18789 -> 18790) โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ
โ โผ โ
โ 9Router / OpenAI-compatible models โ
โ (e.g. http://192.168.51.115:20128/v1) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
```
The gateway listens on an internal port. An nginx-based HTTPS proxy sits in front of it so external clients (and Telegram webhooks) hit a standard `https://` endpoint instead of a raw LAN port.
---
## ๐ Prerequisites
Before you start, make sure you have:
- A Linux VM (this guide assumes Debian/Ubuntu or similar systemd-based distro).
- A non-root user with `sudo` (we use `ryan` in examples).
- `node` + `npm` available (OpenClaw ships as a Node package).
- A domain or LAN IP for the gateway (we use `192.168.51.115` internally).
- A Telegram bot token from [@BotFather](https://t.me/BotFather) (placeholder below).
- A model backend โ we point OpenClaw at **9Router** (see the [9Router guide](/blog/9router-setup/)).
> โ ๏ธ **Security note:** Never commit real tokens or API keys. Every secret in this guide is a `` you replace locally.
---
## ๐ฆ Step 1 โ Install OpenClaw
Install OpenClaw globally via npm (or via your package manager of choice):
```bash
# Install OpenClaw globally
npm install -g openclaw
# Verify it landed
which openclaw
# -> /home/ryan/.local/bin/openclaw (or similar)
# Check the version
openclaw --version
```
If you prefer a pinned version, replace `openclaw` with `openclaw@` (e.g. `openclaw@2026.6.11`).
---
## โ๏ธ Step 2 โ Configure the Gateway
OpenClaw reads its config from a config file (location depends on install; commonly `~/.config/openclaw/` or the working directory). Below is a **sanitized** example โ every secret is a placeholder.
```yaml
# ~/.config/openclaw/config.yaml (sanitized)
gateway:
host: 0.0.0.0
port: 18790
# Public base URL used in links/telegram webhook callbacks:
public_url: "https://"
telegram:
# Bot token from @BotFather โ DO NOT commit the real value
bot_token: ""
# Optional: restrict to your chat id
allowed_chat_ids:
- ""
models:
# Point OpenClaw at your 9Router (or any OpenAI-compatible endpoint)
default_provider: openai-compatible
openai_compatible:
base_url: "http://192.168.51.115:20128/v1"
# API key for 9Router โ placeholder only
api_key: ""
default_model: "Free"
auth:
# Shared desktop pairing token (used by the OpenClaw Desktop app)
session_token: ""
```
> ๐ก **Tip:** Keep secrets in a separate env file and reference them, rather than inlining them in `config.yaml`. OpenClaw supports env interpolation in most setups.
---
## ๐ Step 3 โ Create the systemd Service
Running the gateway as a systemd user service means it restarts on boot and recovers from crashes. Create the unit file:
```ini
# ~/.config/systemd/user/openclaw-gateway.service
[Unit]
Description=OpenClaw Gateway (v2026.6.11)
After=network-online.target
Wants=network-online.target
StartLimitIntervalSec=0
[Service]
Type=simple
ExecStart=/home/ryan/.hermes/node/bin/node /home/ryan/.hermes/node/lib/node_modules/openclaw/dist/index.js gateway --port 18790
WorkingDirectory=/home/ryan
Environment=NODE_ENV=production
Restart=always
RestartSec=5
[Install]
WantedBy=default.target
```
Then enable and start it:
```bash
# Reload systemd so it picks up the new unit
systemctl --user daemon-reload
# Enable on boot (lingering session required for user services at boot)
systemctl --user enable openclaw-gateway.service
# Start it now
systemctl --user start openclaw-gateway.service
# Check it is alive
systemctl --user is-active openclaw-gateway.service
# -> active
```
> ๐ง **Gotcha โ "lingering" user services:** If the gateway should start at boot *without* you logged in, enable lingering:
> ```bash
> loginctl enable-linger $USER
> ```
---
## ๐ Step 4 โ HTTPS Reverse Proxy (nginx)
Exposing port `18790` directly is bad practice. Put an nginx proxy in front so clients use HTTPS. The proxy listens on `18789` and forwards to the gateway on `18790`.
```nginx
# /home/ryan/.config/openclaw-proxy/nginx.conf (sanitized)
worker_processes 1;
daemon off;
events { worker_connections 1024; }
http {
upstream openclaw {
server 127.0.0.1:18790;
}
server {
listen 18789 ssl;
server_name ;
ssl_certificate /etc/ssl/openclaw/fullchain.pem;
ssl_certificate_key /etc/ssl/openclaw/privkey.pem;
location / {
proxy_pass http://openclaw;
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;
}
}
}
```
Run the proxy as its own service:
```ini
# ~/.config/systemd/user/openclaw-https-proxy.service
[Unit]
Description=OpenClaw HTTPS Proxy on port 18789
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
ExecStart=/usr/sbin/nginx -c /home/ryan/.config/openclaw-proxy/nginx.conf -g daemon off;
Restart=always
RestartSec=5
[Install]
WantedBy=default.target
```
```bash
systemctl --user daemon-reload
systemctl --user enable --now openclaw-https-proxy.service
systemctl --user is-active openclaw-https-proxy.service
# -> active
```
---
## ๐ค Step 5 โ Wire Up the Telegram Bot
1. Message [@BotFather](https://t.me/BotFather) on Telegram.
2. Send `/newbot` and follow prompts to get a **bot token**.
3. Paste the token into `config.yaml` as `telegram.bot_token` (placeholder ``).
4. (Optional) Restrict who can talk to the bot via `allowed_chat_ids`.
5. Restart the gateway:
```bash
systemctl --user restart openclaw-gateway.service
```
6. Open a chat with your bot and send `/start`. If the gateway is healthy you'll get a reply.
> ๐ **Health check:** Most OpenClaw builds expose a `/health` endpoint. Verify it:
> ```bash
> curl -s --max-time 8 http://127.0.0.1:18790/health
> # -> {"ok":true,"status":"live"}
> ```
---
## ๐ Step 6 โ Point OpenClaw at 9Router (Free Models)
OpenClaw needs a model backend. We use **9Router** (see the [9Router guide](/blog/9router-setup/)), our LAN OpenAI-compatible router that auto-rotates through free models. The relevant config is the `models.openai_compatible` block from Step 2:
```yaml
models:
default_provider: openai-compatible
openai_compatible:
base_url: "http://192.168.51.115:20128/v1"
api_key: ""
default_model: "Free"
```
Test that the gateway can reach 9Router:
```bash
# List models 9Router serves (key is a placeholder)
curl -s --max-time 8 -H "Authorization: Bearer " \
http://192.168.51.115:20128/v1/models | head -c 400
```
If you see a JSON `data` array, the wiring is good.
---
## ๐งช Step 7 โ Smoke Test the Full Stack
Run through this checklist after setup:
```bash
# 1. Gateway process is up
systemctl --user is-active openclaw-gateway.service
# 2. Proxy is up
systemctl --user is-active openclaw-https-proxy.service
# 3. Gateway health endpoint
curl -s --max-time 8 http://127.0.0.1:18790/health
# 4. HTTPS proxy forwards correctly
curl -sk --max-time 8 https://127.0.0.1:18789/health
# 5. Telegram bot replies (send /start in the app, watch logs)
journalctl --user -u openclaw-gateway.service -f
```
Expected healthy output:
```text
openclaw-gateway.service active
openclaw-https-proxy.service active
{"ok":true,"status":"live"}
{"ok":true,"status":"live"}
```
---
## ๐ ๏ธ Operations & Troubleshooting
### Restarting
```bash
# Restart just the gateway
systemctl --user restart openclaw-gateway.service
# Restart just the proxy
systemctl --user restart openclaw-https-proxy.service
# Restart both
systemctl --user restart openclaw-gateway.service openclaw-https-proxy.service
```
### Common failure modes
| Symptom | Likely cause | Fix |
|---------|-------------|-----|
| Gateway won't start | Bad `config.yaml` syntax | `openclaw gateway --port 18790` in foreground to see errors |
| Telegram bot silent | Wrong `bot_token` / not restarted | Re-paste ``, restart service |
| `502` from proxy | Gateway down or wrong upstream port | Check `is-active`, confirm upstream `127.0.0.1:18790` |
| Model calls fail | 9Router key/endpoint wrong | `curl` the `/v1/models` endpoint directly |
| Health returns 404 | Endpoint path differs by build | Check docs for your version |
> ๐ฉน **Real-world note:** We hit a SQLite plugin-index corruption that took the gateway down. The recovery writeup lives here: [OpenClaw Gateway Down: SQLite Plugin Conflict & Recovery](/blog/openclaw-gateway-sqlite-plugin-conflict/).
---
## ๐ Security Hardening Checklist
- [ ] Bot token stored as `` placeholder, never committed.
- [ ] `allowed_chat_ids` set so strangers can't drive your agents.
- [ ] Gateway bound to `127.0.0.1` or LAN only; internet exposure via nginx + TLS.
- [ ] TLS certs in a restricted dir (`chmod 600` private key).
- [ ] `loginctl enable-linger` only if you truly need boot-time start without login.
- [ ] Rotate `` periodically.
---
## โ
Summary
You now have a self-hosted **OpenClaw gateway** that:
- Runs as supervised **systemd user services** (`openclaw-gateway.service` + `openclaw-https-proxy.service`).
- Terminates **HTTPS** via nginx on `18789` and forwards to the gateway on `18790`.
- Talks to **Telegram** through a bot wired with a placeholder token.
- Routes model calls through **9Router** for free, OpenAI-compatible inference.
- Exposes a `/health` endpoint for monitoring.
From here you can add more agents, swap model backends, or layer on auth โ all without touching the clients. ๐
---
*No API keys, tokens, or secrets are included in this guide โ every value is a `` you supply locally. Built with Astro, GitHub, and Cloudflare Pages.*