OpenClaw on Hetzner: The Complete Setup Guide

By Suraj Kumar ยท Mar 10, 2026 ยท 16 min read ยท 0 views ยท 0 likes

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.

All paths, domains, tokens, and credentials in this guide are placeholders. Replace them with your own values. Directory names like ~/agent/ and ~/web/ are suggestions — name them whatever works for you.
8Setup phases
~45mTotal time
~$5Monthly VPS
8Gotchas documented
1Server
2OpenClaw
3LLM
4Telegram
5Memory
6Data
7Web
8Tools
Phase 1

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.

SettingChoiceWhy
LocationClosest to youLower latency = snappier chat
ImageUbuntu 24.04OpenClaw docs are written for it
NetworkingIPv4 + IPv6Need public IP for initial SSH
SSH KeyAdd yoursNever use password auth
VolumesSkipBuilt-in disk is enough
BackupsSkip (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
Generate your SSH key first if you don't have one: ssh-keygen -t ed25519 on your Mac, then cat ~/.ssh/id_ed25519.pub and paste it into Hetzner.
Phase 2

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"
    }
  }
}
Common mistake: putting "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.

Phase 4

Telegram

During onboarding, select Telegram as your channel. Get a bot token:

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 5

Memory 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.

Don't try setting provider fields one at a time — the config validator rejects partial objects. Set the whole provider as one JSON block.
# 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 6

Data 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
FilePurposeAccess
SOUL.mdAgent identity and valuesRead-only
USER.mdYour preferencesRead-only
MEMORY.mdCurated long-term memoryRead + Write
data/policies.yamlHard constraintsRead-only
data/defaults.yamlMutable preferencesRead + Write
data/inbox.mdQuick captureAppend
Phase 7

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:

TypeHostValue
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
🌐 Public anyone on the internet
/public/* — articles, comparisons, research
🔒 Shared password protected
/shared/* — trip plans, group decisions, family
🛡 Private tailscale only
/private/* — finances, health, investments

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
If your web directory is under /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.

Phase 8

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
These are starter templates. The power comes from customizing them — add your own skills, adjust the file paths, define your agent's personality in SOUL.md. The agent discovers skills by reading SKILL.md files, so adding a new skill is just creating a new directory with a SKILL.md inside it.

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:

ProblemCauseFix
Gateway crash-loops (55+ restarts)gateway.mode not setAdd "mode": "local" to gateway config
"No provider plugins found"Running models auth login directlyUse openclaw onboard --auth-choice openai-codex
Embeddings "unavailable"Codex OAuth โ‰  embeddingsAdd separate OpenAI API key via JSON config block
Env var not picked upTypo: Environmnet in .service fileSpell-check your systemd files
Caddy 403Can't read web directorychmod 755 on web dir and parent
Agent asks you to run commandsTools profile = "messaging"openclaw config set tools.profile "coding"
Config validation errorsSetting provider fields individuallySet entire provider as one JSON block
"mode": "local" wrong locationNested inside "auth"Must be sibling of "auth"

What You've Built

Complete Stack
You (Telegram on phone)
messages
OpenClaw Gateway
routes to LLM
Codex GPT-5.3 / Claude
reads & writes
Flat Files · Memory · Skills
publishes to
Caddy (HTTPS) → yourdomain.com

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:

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 →

openclaw hetzner self-hosting setup-guide ai-agents caddy telegram