OpenClaw on Hetzner: The Complete Setup Guide
Every command, every config file, every mistake. From zero to a running AI agent with Telegram, memory, living documents, and 3-tier web publishing.
This is the technical companion to my post about how I rewrote my entire workflow in two weeks. That post covers the why. This one covers the how — every step, every gotcha, every config file.
~/agent/ and ~/web/ are suggestions — name them whatever works for you.The Server
Hetzner Cloud. Pick the datacenter closest to you — if you're in Asia, Singapore gives ~40ms. EU locations add 150ms+. Latency matters when you're chatting with your agent all day.
| Setting | Choice | Why |
|---|---|---|
| Location | Closest to you | Lower latency = snappier chat |
| Image | Ubuntu 24.04 | OpenClaw docs are written for it |
| Networking | IPv4 + IPv6 | Need public IP for initial SSH |
| SSH Key | Add yours | Never use password auth |
| Volumes | Skip | Built-in disk is enough |
| Backups | Skip (for now) | Optional, 20% extra cost |
SSH in and set up basics:
ssh root@your-server-ip
apt-get update && apt-get upgrade -y
apt-get install -y git curl ca-certificates
ssh-keygen -t ed25519 on your Mac, then cat ~/.ssh/id_ed25519.pub and paste it into Hetzner.Install OpenClaw
OpenClaw needs Node 22+. Install both:
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
sudo apt install -y nodejs
node --version # should show v22+
curl -fsSL https://openclaw.ai/install.sh | bash
openclaw onboard --install-daemon
The wizard walks you through: security disclaimer (accept), gateway runtime (Node), install daemon (yes). At the end it installs a systemd service.
Fix: Gateway Won't Start
The gateway immediately crash-looped with this error, 55+ times:
Gateway start blocked: set gateway.mode=local (current: unset)
Edit the config file:
nano ~/.openclaw/openclaw.json
Add "mode": "local" inside the "gateway" object. Not inside "auth" — as a sibling:
{
"gateway": {
"mode": "local", โ ADD THIS LINE
"auth": {
"mode": "token",
"token": "your-a...oken"
}
}
}
"mode": "local" inside the "auth" block. Wrong nesting. It has to be a direct child of "gateway".openclaw daemon restart
openclaw gateway status # look for "RPC probe: ok"
Phase 3
Connect Your LLM (Codex)
The direct command openclaw models auth login --provider openai-codex fails with "No provider plugins found." The working approach:
openclaw onboard --auth-choice openai-codex
When it asks about existing config, choose Keep. It shows an OAuth URL — open it in your browser, sign in with your ChatGPT account.
openclaw models status
You should see your model (openai-codex/gpt-5.3-codex), OAuth status, and usage remaining.
Telegram
During onboarding, select Telegram as your channel. Get a bot token:
- Open Telegram, search @BotFather
- Send
/newbot - Choose a name and username (must end in
_bot) - Copy the token and paste it into the wizard
After the gateway starts, message your bot on Telegram with /start. It gives you a pairing code. Approve it on the server:
openclaw pairing approve telegram YOUR_CODE
Your agent is now live on Telegram.
Phase 5Memory Search (Embeddings)
Memory gives the agent context from past conversations. Without it, every session starts blank.
Codex OAuth doesn't cover the embeddings endpoint. You need a separate OpenAI API key from platform.openai.com with billing enabled.
# Set the OpenAI provider (embeddings)
openclaw config set models.providers.openai \
'{"baseUrl":"https://api.openai.com/v1","apiKey": "***","models":[]}' \
--json
# Enable memory search
openclaw config set plugins.slots.memory memory-core
openclaw config set agents.defaults.memorySearch.enabled true --json
openclaw config set agents.defaults.memorySearch.provider openai
openclaw config set agents.defaults.memorySearch.model text-embedding-3-small
openclaw config set agents.defaults.memorySearch.sources '["memory"]' --json
# Hybrid search (vector + keyword + temporal decay)
openclaw config set agents.defaults.memorySearch.query.hybrid.enabled true --json
openclaw config set agents.defaults.memorySearch.query.hybrid.mmr.enabled true --json
openclaw config set agents.defaults.memorySearch.query.hybrid.temporalDecay.enabled true --json
# Create memory directory and restart
mkdir -p ~/.openclaw/workspace/memory
openclaw daemon restart
Verify:
openclaw memory status --deep
You want: Embeddings: ready, Vector: ready, FTS: ready.
Phase 6Data Architecture
Flat files instead of databases. YAML for config, Markdown for notes, JSONL for logs. The paths below are suggestions — name your directories whatever makes sense to you. The agent doesn't care about the names, only the SKILL.md files that reference them.
mkdir -p ~/agent/{data,memory,skills}
cd ~/agent && git init
Create the core files:
# Policies (read-only constraints the agent can't modify)
cat > ~/agent/data/policies.yaml << 'EOF'
spending:
daily_cap: 5000 # adjust to your comfort level
require_approval_above: 2000
deletions:
require_confirmation: true
never_delete: [transactions.jsonl, contacts.yaml]
messaging:
agent_can_send: false # agent cannot message anyone proactively
EOF
# Preferences (agent can read and update)
cat > ~/agent/data/defaults.yaml << 'EOF'
user:
name: "Your Name"
timezone: "Your/Timezone" # e.g. Asia/Kolkata, America/New_York
wake_time: "07:00"
location: "Your City"
notifications:
quiet_hours: ["23:00", "07:00"]
EOF
# Agent identity โ customize this to define your agent's personality
cat > ~/agent/SOUL.md << 'EOF'
# Agent Identity
You are a personal AI assistant for [your name].
## Values
- Be concise and direct
- Always confirm before destructive actions
- Respect quiet hours
- Never send messages without explicit approval
## Boundaries
- Do not modify policies.yaml
- Do not delete files listed in never_delete
- Ask before spending above approval threshold
# Add your own rules here โ writing style, communication preferences, etc.
EOF
# Quick capture + memory
touch ~/agent/data/inbox.md
touch ~/agent/data/transactions.jsonl
touch ~/agent/MEMORY.md
| File | Purpose | Access |
|---|---|---|
SOUL.md | Agent identity and values | Read-only |
USER.md | Your preferences | Read-only |
MEMORY.md | Curated long-term memory | Read + Write |
data/policies.yaml | Hard constraints | Read-only |
data/defaults.yaml | Mutable preferences | Read + Write |
data/inbox.md | Quick capture | Append |
Living Documents (Caddy + 3-Tier Web)
This is where it gets good. Install Caddy, point a domain at your server, and the agent can publish web pages.
Install Caddy
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' \
| sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' \
| sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update && sudo apt install caddy
DNS Setup
Point your domain to your server with two A records:
| Type | Host | Value |
|---|---|---|
| A | @ | your-server-ip |
| A | * | your-server-ip |
Configure 3 Tiers
mkdir -p ~/web/{public,shared,private}
# Generate password hash for shared tier
caddy hash-password
# Type a password, copy the hash
sudo tee /etc/caddy/Caddyfile << 'EOF'
yourdomain.com {
handle / {
root * /home/youruser/web/public
file_server
}
handle /public/* {
root * /home/youruser/web
file_server
}
handle /shared/* {
basic_auth {
friend YOUR_HASH_HERE
}
root * /home/youruser/web
file_server
}
handle /private/* {
@notTailscale not remote_ip 100.64.0.0/10
respond @notTailscale 403
root * /home/youruser/web
file_server
}
handle {
respond "Not found" 404
}
}
www.yourdomain.com {
redir https://yourdomain.com{uri} permanent
}
EOF
Fix: Caddy 403 Error
Caddy runs as a non-root user and may not be able to access your web directory. Fix permissions:
chmod -R 755 ~/web
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo systemctl restart caddy
/root/, you'll also need chmod 755 /root. Better practice: create a dedicated user and keep web files in their home directory.Test with a sample page:
mkdir -p ~/web/public/hello
cat > ~/web/public/hello/index.html << 'EOF'
<!DOCTYPE html>
<html>
<head><title>Hello</title></head>
<body style="font-family:sans-serif;max-width:600px;margin:40px auto;">
<h1>It works.</h1>
<p>Published by OpenClaw.</p>
</body>
</html>
EOF
Visit https://yourdomain.com/public/hello/ — you should see the page with HTTPS.
Enable File Writing
By default, OpenClaw's tool profile is "messaging" — the agent can only chat. It'll tell you to run commands instead of running them itself. Switch to the coding profile:
openclaw config set tools.profile "coding"
openclaw daemon restart
Now the agent has shell access. It can create HTML files, write to your data directory, and publish pages directly.
Set Up Skills + System Prompt
Skills are SKILL.md files that teach the agent how to handle specific tasks. Here are starter examples — customize the paths and behavior to match your setup:
# Create skill directories
mkdir -p ~/agent/skills/{capture,journal,web-publisher}
# Capture skill โ quick inbox for thoughts
cat > ~/agent/skills/capture/SKILL.md << 'EOF'
# Capture Skill
Quick inbox for thoughts, tasks, links, and notes.
## Behavior
- Append to ~/agent/data/inbox.md with a timestamp
- Format: `- [YYYY-MM-DD HH:MM] {content}`
- Do NOT organize or process. Just capture.
## File
- Inbox: ~/agent/data/inbox.md
EOF
# Journal skill โ session summaries
cat > ~/agent/skills/journal/SKILL.md << 'EOF'
# Journal Skill
Session summaries and daily logs.
## Behavior
- Write session summaries to ~/agent/memory/YYYY-MM-DD.md
- Include: key topics, decisions, action items
- Keep each summary under 200 words
EOF
# Web publisher skill โ living documents
cat > ~/agent/skills/web-publisher/SKILL.md << 'EOF'
# Web Publisher Skill
Build and publish HTML documents to the server.
## Behavior
- Build complete HTML pages with inline CSS
- Save to the web directory for your chosen access tier
- Always include "Last updated" timestamp
- Ask which tier (public/shared/private) if not specified
EOF
# System prompt โ tells the agent where to find everything
cat > ~/.openclaw/workspace/AGENTS.md << 'EOF'
# Agent System Prompt
Read ~/agent/SOUL.md for identity and values.
Read ~/agent/data/defaults.yaml for user preferences.
## Skills
Read SKILL.md files in ~/agent/skills/ when relevant.
## Rules
- Quick captures โ append to inbox.md
- Important memories โ update MEMORY.md
- Research tasks โ build and publish HTML pages
EOF
openclaw daemon restart
Security Hardening
Your agent has shell access. If someone gets your gateway token, they effectively have your machine. It's easy to accidentally expose credentials while debugging — gateway tokens, API keys, bot tokens can all end up in logs or chat sessions. Here's how to lock it down.
User Separation
Create separate Linux users so the agent can't access secrets directly:
# Create agent user and secrets user
sudo useradd -m -s /bin/bash agent-user
sudo useradd -m -s /bin/bash agent-secrets
# Lock down secrets
sudo chmod 700 /home/agent-secrets
sudo chown -R agent-secrets:agent-secrets /home/agent-secrets
# Verify: agent can't see secrets
sudo -u agent-user ls /home/agent-secrets
# Should say: Permission denied
Controlled Elevation
A wrapper script that sources secrets as env vars only during execution, then they vanish:
sudo tee /usr/local/bin/with-secrets.sh << 'EOF'
#!/bin/bash
set -euo pipefail
source /home/agent-secrets/.env
exec "$@"
EOF
sudo chmod 700 /usr/local/bin/with-secrets.sh
echo 'agent-user ALL=(root) NOPASSWD: /usr/local/bin/with-secrets.sh' \
| sudo tee /etc/sudoers.d/agent-user
Token Rotation
# If your gateway token was ever exposed:
openclaw gateway token rotate
The Gotchas
Everything that cost me time, so it doesn't cost you time:
| Problem | Cause | Fix |
|---|---|---|
| Gateway crash-loops (55+ restarts) | gateway.mode not set | Add "mode": "local" to gateway config |
| "No provider plugins found" | Running models auth login directly | Use openclaw onboard --auth-choice openai-codex |
| Embeddings "unavailable" | Codex OAuth โ embeddings | Add separate OpenAI API key via JSON config block |
| Env var not picked up | Typo: Environmnet in .service file | Spell-check your systemd files |
| Caddy 403 | Can't read web directory | chmod 755 on web dir and parent |
| Agent asks you to run commands | Tools profile = "messaging" | openclaw config set tools.profile "coding" |
| Config validation errors | Setting provider fields individually | Set entire provider as one JSON block |
"mode": "local" wrong location | Nested inside "auth" | Must be sibling of "auth" |
What You've Built
All on a single ~$5/month VPS. The agent handles everything between your Telegram message and the published output.
Final Verification
openclaw gateway probe --timeout 15000
openclaw models status
openclaw memory status --deep --index --verbose
You want:
- Gateway: Reachable: yes, RPC: ok
- Model: openai-codex/gpt-5.3-codex with OAuth active
- Memory: Embeddings ready, Vector ready, FTS ready
- Telegram: bot responding to messages
- Web pages: loading at your domain with HTTPS
This setup is inspired by Prateek Karnal's agentic infrastructure — his living documents, 3-tier publishing model, and the idea that every decision should have a URL.
That's the complete setup. For what this actually does in practice — the apps it replaced, the workflow it enabled, and the personality layer that makes it mine — read I Rewrote My Entire Workflow in Two Weeks →