Skip to content

knowbody/hetzner-openclaw

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Hetzner OpenClaw Infrastructure

Production-grade Terraform + cloud-init setup for a hardened Hetzner VPS that can run OpenClaw 24/7.

Overview

This repository provisions and hardens a Hetzner host for private OpenClaw usage.

It includes:

  • A Hetzner VPS
  • Managed SSH key registration
  • Locked-down Hetzner firewall
  • First-boot hardening via cloud-init
  • Tailscale bootstrap for private access

For a comprehensive beginner walkthrough (getting every terraform.tfvars value + full service setup), use:

  • docs/SETUP_FROM_ZERO.md

Before anything else, create your local variables file:

cp terraform.tfvars.example terraform.tfvars

Architecture

High-level flow

┌─────────────────────┐          ┌────────────────────────────────────────────┐
│   Your workstation  │          │            Hetzner Cloud                   │
│                     │          │                                            │
│  terraform apply ───┼─────────>│  VPS + cloud-init hardening + firewall     │
│                     │          │                                            │
│  SSH over Tailscale ┼<─────────┤  openclaw user + Tailscale node            │
│                     │          │                                            │
└─────────────────────┘          └────────────────────────────────────────────┘

Provisioned resources

Terraform resources

  • hcloud_ssh_key.default
  • hcloud_server.vps
  • hcloud_firewall.vps
  • hcloud_firewall_attachment.vps

First-boot hardening (scripts/cloud-init.sh)

  • System updates and security packages
  • Non-root sudo user creation
  • SSH key setup for that user
  • SSH hardening (no password auth, no root login, limits/timeouts)
  • fail2ban with UFW integration
  • UFW configured with deny-by-default inbound policy
  • Unattended security upgrades
  • Sysctl network hardening
  • Tailscale installation and join

Repository Layout

.
├── data.tf
├── main.tf
├── outputs.tf
├── variables.tf
├── terraform.tfvars.example
└── scripts/
    └── cloud-init.sh

Prerequisites

  • Terraform >= 1.6.0
    • Install: https://developer.hashicorp.com/terraform/install
  • Hetzner Cloud project + API token
  • One SSH public key ready
  • Tailscale account + auth key

Early in the setup, set up Tailscale on your laptop (install client, sign in, and confirm your laptop appears in Tailscale Machines). Tailscale is a private mesh VPN that lets you reach your VPS securely without exposing SSH publicly. For this project, the free Tailscale plan is usually enough.

Quick Start

  1. Copy example variables:

    cp terraform.tfvars.example terraform.tfvars
  2. Edit terraform.tfvars with your values:

    • hcloud_token
    • ssh_public_key
    • allowed_ssh_ips ([] for Tailscale-only, or your public IP CIDR for fallback)
    • tailscale_auth_key
  3. Deploy:

    terraform init
    terraform validate
    terraform plan
    terraform apply
  4. Wait for cloud-init to complete (Terraform finishes before hardening is done):

    Preferred (Tailscale-first): SSH using the node's Tailscale IP or MagicDNS name after it appears in Tailscale Machines.

    ssh openclaw@<tailscale-ip-or-magicdns-name> 'tail -f /var/log/cloud-init-custom.log'

    First connection note: SSH may ask to trust the host key (The authenticity of host ... can't be established). Type yes once to continue.

    Fallback (only if you set public SSH CIDR in allowed_ssh_ips):

    ssh openclaw@$(terraform output -raw server_ip) 'tail -f /var/log/cloud-init-custom.log'

Beginner Setup (From Zero)

Use the full guide here:

  • docs/SETUP_FROM_ZERO.md

It includes account setup, collecting all terraform.tfvars values, server size selection, deployment, and post-deploy service setup.

Configuration

terraform.tfvars Value Reference (Field-by-Field)

Use this checklist when someone asks: "where do I get each value?"

  • hcloud_token
    • Source: Hetzner project -> Security -> API Tokens.
    • Format: starts with hcloud_.
    • Required: yes.
  • server_name
    • Source: you choose.
    • Recommendation: openclaw-prod.
    • Required: no (has default).
  • server_type
    • Source: you choose based on size/cost.
    • Recommendation: cx22 to start.
    • Required: no (has default).
  • image
    • Source: Hetzner OS image naming.
    • Recommendation: ubuntu-24.04.
    • Required: no (has default).
  • location
    • Source: Hetzner datacenter slug.
    • Recommendation: nbg1 or nearest region.
    • Required: no (has default).
  • ssh_public_key
    • Source: your local ~/.ssh/*.pub key contents.
    • Required: yes.
  • allowed_ssh_ips
    • Source: your public IP (curl -4 ifconfig.me) or VPN CIDR.
    • Required: no. With required Tailscale, you can set [] and use tailnet SSH only.
  • tailscale_auth_key
    • Source: Tailscale Admin -> Settings -> Keys.
    • Required: yes.

Example terraform.tfvars

hcloud_token   = "your-hetzner-api-token"
server_name    = "openclaw-prod"
server_type    = "cx22"
image          = "ubuntu-24.04"
location       = "nbg1"
ssh_public_key = "ssh-ed25519 AAAA... you@machine"

# Security: restrict SSH to your IP or VPN range
allowed_ssh_ips = []

tailscale_auth_key = "tskey-auth-xxxxx"

Tailscale Auth Key

  1. Create/sign in to Tailscale.

  2. Open Admin Console -> Settings -> Keys.

  3. Generate auth key with:

    • Description: Terraform OpenClaw
    • Reusable: Off
    • Expiration: short (for example 1 day)
    • Ephemeral: Off
    • Tags: Off (unless you use tagged-node ACLs)
  4. Set:

    tailscale_auth_key = "tskey-auth-..."

Why this helps:

  • Keep OpenClaw bound to 127.0.0.1
  • Access privately over tailnet HTTPS
  • No public dashboard exposure, no port forwarding
  • No need to know the node's Tailscale IP before provisioning

If your tailnet requires device approval, approve the new node in Machines after bootstrap.

Service Setup Guides

This project depends on three services. Use these mini-guides for teammates who are brand new.

Hetzner Cloud setup checklist

  1. Create Hetzner account.
  2. Create a project dedicated to this environment.
  3. Create API token with Read & Write.
  4. Add billing method so provisioning can succeed.
  5. Keep token in password manager; do not commit it.

Tailscale setup checklist

  1. Create Tailscale account.
  2. Install Tailscale client on your laptop/phone.
  3. Confirm your device appears in Machines.
  4. Generate auth key for server bootstrap.
  5. Put key in terraform.tfvars as tailscale_auth_key.
  6. After deploy, verify the VPS appears in your tailnet.

Find your Tailscale SSH target

Use one of these as <tailscale-ip-or-magicdns-name>:

  1. Tailscale Admin -> Machines:

    • open your new server entry
    • copy either:
      • the Tailscale IPv4 (usually 100.x.y.z), or
      • the MagicDNS hostname (for example openclaw-prod.tail1234.ts.net)
  2. On the VPS (if already connected another way), run:

    tailscale ip -4
    tailscale status

Then SSH with:

ssh openclaw@<tailscale-ip-or-magicdns-name>

On first SSH connection, confirm host key trust prompt with yes.

OpenClaw setup checklist

  1. SSH to VPS as openclaw.
  2. Install Node (nvm) and optionally Homebrew.
  3. Install OpenClaw globally: npm i -g openclaw.
  4. Run openclaw onboard in Manual mode.
  5. Keep current default model (openai-codex/gpt-5.3-codex).
  6. Set workspace directory to /home/openclaw/.openclaw/workspace when prompted.
  7. Keep gateway port at 18789.
  8. Set gateway bind to Loopback (127.0.0.1).
  9. Use gateway auth: Token.
  10. Use Tailscale exposure: Serve.
  11. Set Reset Tailscale serve/funnel on exit? to No.
  12. For Gateway token (blank to generate), leave it blank to auto-generate.
  13. For Configure skills now? (recommended), choose No.
  14. For Enable hooks?, choose Skip for now.
  15. Install systemd service when prompted.
  16. Verify with systemctl --user status openclaw-gateway.

Install OpenClaw on the VPS

SSH in:

ssh openclaw@<tailscale-ip-or-magicdns-name>

Fallback if public SSH is enabled:

ssh openclaw@$(terraform output -raw server_ip)

Install Node via nvm:

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash
source ~/.bashrc
nvm install 24
node -v

Install Homebrew (if required by selected skills):

NONINTERACTIVE=1 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
echo 'eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"' >> ~/.bashrc
source ~/.bashrc

Install OpenClaw:

npm i -g openclaw

If npm global install fails with permissions (EACCES), use a user prefix:

mkdir ~/.npm-global
npm config set prefix '~/.npm-global'
echo 'export PATH=~/.npm-global/bin:$PATH' >> ~/.bashrc
source ~/.bashrc

Run onboarding:

openclaw onboard

Suggested onboarding choices:

  • Manual mode
  • Local gateway on this VPS
  • Default model: Keep current (openai-codex/gpt-5.3-codex)
  • Workspace directory: /home/openclaw/.openclaw/workspace
  • Gateway port: 18789
  • Gateway bind: Loopback (127.0.0.1)
  • Gateway auth: Token
  • Tailscale exposure: Serve (not Funnel)
  • Reset Tailscale serve/funnel on exit?: No
  • Gateway token (blank to generate): leave blank (auto-generate)
  • Configure skills now? (recommended): No
  • Enable hooks?: Skip for now
  • Install systemd service
  • Runtime: Node

Operations

Outputs

After apply:

  • server_ip: public IPv4
  • server_ipv6: public IPv6
  • ssh_command: public-IP SSH command (fallback if public SSH is enabled)

Use:

terraform output
terraform output -raw server_ip

Access the Dashboard Locally

With the recommended setup (Gateway bind: Loopback + Tailscale exposure: Serve), open the dashboard from your laptop using the tailnet URL:

openclaw status

From the Tailscale line, open the HTTPS URL (for example: https://openclaw-prod.tailfc18f0.ts.net).

You can also open/print the dashboard link directly with:

openclaw dashboard

If you prefer an SSH tunnel fallback instead of Tailscale Serve:

ssh -N -L 18789:127.0.0.1:18789 openclaw@<tailscale-ip-or-magicdns-name>

Then open:

http://127.0.0.1:18789

First connection from a new browser/device

If the dashboard shows disconnected (1008): pairing required, approve the device on the VPS:

# On the VPS
openclaw devices list
openclaw devices approve <requestId>

Then refresh the dashboard page.

If the UI asks for auth, fetch your gateway token on the VPS and paste it in dashboard settings:

openclaw config get gateway.auth.token

Post-onboarding quick validation

Run these on the VPS to confirm gateway health and tailnet exposure:

openclaw status
systemctl --user status openclaw-gateway
tailscale serve status

Expected indicators:

  • Gateway service: systemd installed · enabled · running
  • Tailscale: includes your tailnet URL
  • tailscale serve status: shows proxy to http://127.0.0.1:18789

Configure your first channel (Telegram example)

If you want to enable Telegram right away:

openclaw channels add

Recommended wizard choices:

  • Configure chat channels now?: Yes
  • Select a channel: Telegram (Bot API)
  • Telegram account: default (primary)
  • Enter Telegram bot token from @BotFather
  • Configure DM access policies now?: Yes
  • Telegram DM policy: Pairing (recommended)

When a Telegram user starts a chat, approve pairing with:

openclaw pairing approve telegram <code>

You can inspect channel state anytime:

openclaw channels list
openclaw channels status --deep

Operate the Gateway Service

Check status:

systemctl --user status openclaw-gateway

Follow logs:

journalctl --user -u openclaw-gateway -f

WhatsApp Integration Notes

  • Use a dedicated number for WhatsApp Business onboarding.
  • Verify once, then pair with QR during OpenClaw setup.
  • Send /start to initialize session.

Security Considerations

Security posture

Defence in depth:

  • Hetzner firewall at hypervisor level
  • UFW inside VM
  • fail2ban for SSH brute-force mitigation
  • hardened SSH config
  • unattended security patches
  • sysctl hardening for common network attack patterns

Troubleshooting and Gotchas

  • Cloud-init timing: Terraform may succeed before hardening finishes.

  • Tailscale SSH behavior: tailnet ACLs can affect SSH behavior when using tailscale up --ssh.

  • Tailscale Serve permissions: new deployments set tailscale operator permissions for openclaw automatically via cloud-init. For older deployments, run once:

    sudo tailscale set --operator=openclaw

    If Serve is disabled at tailnet level, enable it in Tailscale admin first, then run tailscale serve --bg --yes 18789.

  • openclaw status security warnings: seeing Reverse proxy headers are not trusted is expected when using loopback bind without a custom reverse proxy. The denyCommands entries are ineffective warning is from OpenClaw command-name validation; review and tighten command names later if you customize gateway.nodes.denyCommands.

  • Dual firewalls: Hetzner firewall + UFW are both active by design.

  • Homebrew sudo prompt edge case: if Brew still prompts for a sudo password, add per-user sudo defaults and retry:

    sudo tee /etc/sudoers.d/openclaw >/dev/null <<'EOF'
    Defaults:openclaw verifypw=any
    Defaults:openclaw listpw=never
    openclaw ALL=(ALL:ALL) NOPASSWD:ALL
    EOF
    sudo chmod 0440 /etc/sudoers.d/openclaw
    sudo visudo -c
    NONINTERACTIVE=1 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

Recommended Next Improvements

  • Add backup strategy for /home/openclaw/clawd
  • Add monitoring/alerting (for example, Uptime Kuma or node exporter)
  • Consider non-standard SSH port (mainly reduces log noise)
  • Add app-layer rate limits if exposing HTTP endpoints

Teardown

Destroy everything:

terraform destroy

About

Deploy OpenClaw on Hetzner the safe way with Terraform, server hardening, and private Tailscale access.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors