OpenClaw Complete Guide Part 5: Deploying OpenClaw on a VPS

OpenClaw Complete Guide Part 5: Deploying OpenClaw on a VPS

Running OpenClaw on your laptop works well for experimentation, but a VPS deployment is what makes it genuinely useful. When your agent runs on a server, it is available 24/7 without your laptop needing to be on, it can handle cron jobs and scheduled tasks reliably, and it does not compete with your development workload for resources.

This post covers the full production deployment: provisioning a Linux server, installing OpenClaw, configuring it as a systemd service, securing it with a Cloudflare tunnel so the gateway is never exposed publicly, and setting up monitoring so you know when something goes wrong.

All commands are tested on Ubuntu 24.04 LTS, which is the recommended OS for this setup. The same steps work on Debian 12 with minor adjustments.

Choosing a VPS

OpenClaw is a Node.js process with an LLM connection. It does not need much compute on its own since the heavy lifting happens at the LLM provider. A minimal VPS is enough for a personal setup. Here is what to look for:

  • RAM: 1 GB minimum, 2 GB recommended if you plan to run browser automation or multiple agents
  • CPU: 1 vCPU is sufficient for personal use
  • Storage: 20 GB minimum to accommodate logs, memory files, and skill scripts
  • OS: Ubuntu 24.04 LTS
  • Location: choose a region close to you for lower latency on Telegram and API calls

Good options include DigitalOcean Droplets, Hetzner Cloud, Vultr, and Azure B1s instances. For a personal agent, the cheapest tier on any of these providers is more than sufficient.

The Full Deployment Architecture

flowchart TD
    Phone["Your Phone\n(Telegram / WhatsApp)"]
    CF["Cloudflare Tunnel\n(encrypted, no open ports)"]
    VPS["Linux VPS\nUbuntu 24.04"]
    GW["OpenClaw Gateway\n(systemd service, loopback only)"]
    LLM["LLM Provider\n(Anthropic / OpenAI / DeepSeek)"]
    Skills["Skills and Memory\n(/home/openclaw/workspace)"]

    Phone -->|"Message"| CF
    CF -->|"Secure tunnel"| VPS
    VPS --> GW
    GW -->|"API call"| LLM
    GW -->|"Reads / writes"| Skills
    LLM -->|"Response"| GW
    GW -->|"Reply via tunnel"| CF
    CF -->|"Message"| Phone

The Cloudflare tunnel is the key security element here. The gateway binds to loopback only (127.0.0.1) and is never directly reachable from the internet. All external traffic flows through the Cloudflare tunnel, which handles TLS termination and authentication. This eliminates the most common OpenClaw security mistake: an exposed port 18789.

Step 1: Initial Server Setup

SSH into your fresh VPS as root and run the initial setup:


# Update the system
apt update && apt upgrade -y

# Create a dedicated non-root user for OpenClaw
adduser openclaw
usermod -aG sudo openclaw

# Set up SSH key authentication for the new user
mkdir -p /home/openclaw/.ssh
cp ~/.ssh/authorized_keys /home/openclaw/.ssh/
chown -R openclaw:openclaw /home/openclaw/.ssh
chmod 700 /home/openclaw/.ssh
chmod 600 /home/openclaw/.ssh/authorized_keys

# Disable root SSH login and password authentication
sed -i 's/PermitRootLogin yes/PermitRootLogin no/' /etc/ssh/sshd_config
sed -i 's/#PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config
systemctl restart sshd

# Set up a basic firewall - allow SSH and nothing else inbound
ufw allow OpenSSH
ufw --force enable
ufw status

From this point, SSH in as the openclaw user, not root.

Step 2: Install Node.js 22


# Install nvm
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash

# Load nvm into current session
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"

# Install Node.js 22 and set as default
nvm install 22
nvm use 22
nvm alias default 22

# Verify
node --version   # v22.x.x
npm --version    # 10.x.x

# Add nvm to shell profile so it loads on login
echo 'export NVM_DIR="$HOME/.nvm"' >> ~/.bashrc
echo '[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"' >> ~/.bashrc
source ~/.bashrc

Step 3: Install OpenClaw


# Install OpenClaw globally
npm install -g openclaw@latest

# Verify
openclaw --version

# If openclaw command is not found, add npm global bin to PATH
echo 'export PATH="$(npm config get prefix)/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc

Step 4: Run the Onboard Wizard

On the VPS, run the onboard wizard without the daemon flag first. We will configure systemd manually for more control.


openclaw onboard

Complete the wizard: configure your LLM provider and API key, set your workspace directory (use the default ~/openclaw), and connect your Telegram bot. Skip the daemon installation step since we will handle that with systemd.

After the wizard completes, open ~/openclaw/openclaw.json and make sure the gateway is bound to loopback:


{
  "gateway": {
    "port": 18789,
    "bind": "loopback"
  }
}

Step 5: Configure systemd Service

We create a systemd user service so OpenClaw starts automatically on boot and restarts if it crashes, without needing root privileges.


# Create the systemd user service directory
mkdir -p ~/.config/systemd/user

# Find the full path to the openclaw binary
which openclaw   # note this path, e.g. /home/openclaw/.nvm/versions/node/v22.x.x/bin/openclaw

Create the service file:


cat > ~/.config/systemd/user/openclaw-gateway.service << 'EOF'
[Unit]
Description=OpenClaw Gateway
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
WorkingDirectory=/home/openclaw/openclaw
ExecStart=/home/openclaw/.nvm/versions/node/v22.14.0/bin/openclaw gateway start --foreground
Restart=always
RestartSec=10
Environment=PATH=/home/openclaw/.nvm/versions/node/v22.14.0/bin:/usr/local/bin:/usr/bin:/bin
Environment=HOME=/home/openclaw
Environment=NODE_ENV=production

# Resource limits
MemoryMax=512M
CPUQuota=50%

[Install]
WantedBy=default.target
EOF

Note: replace v22.14.0 with the actual Node.js version from node --version.


# Enable lingering so the user service runs without an active SSH session
loginctl enable-linger openclaw

# Reload systemd and enable the service
systemctl --user daemon-reload
systemctl --user enable openclaw-gateway
systemctl --user start openclaw-gateway

# Check status
systemctl --user status openclaw-gateway

# View logs
journalctl --user -u openclaw-gateway -f

Step 6: Set Up a Cloudflare Tunnel

The Cloudflare tunnel lets you access your gateway dashboard remotely without exposing any ports on your VPS. It also protects the Telegram webhook endpoint if you use it.

sequenceDiagram
    participant You as Your Browser
    participant CF as Cloudflare Edge
    participant Tunnel as cloudflared daemon
    participant GW as OpenClaw Gateway (127.0.0.1:18789)

    You->>CF: https://openclaw.yourdomain.com
    CF->>Tunnel: Route via persistent tunnel
    Tunnel->>GW: Forward to localhost:18789
    GW-->>Tunnel: Response
    Tunnel-->>CF: Response
    CF-->>You: Secure HTTPS response

# Install cloudflared
curl -L --output cloudflared.deb \
  https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb
sudo dpkg -i cloudflared.deb

# Log in to Cloudflare (opens a browser link - copy it to your local machine)
cloudflared tunnel login

# Create a named tunnel
cloudflared tunnel create openclaw-tunnel

# Note the tunnel ID from the output, then create the config file
mkdir -p ~/.cloudflared

# Replace YOUR_TUNNEL_ID and yourdomain.com with your values
cat > ~/.cloudflared/config.yml << 'EOF'
tunnel: YOUR_TUNNEL_ID
credentials-file: /home/openclaw/.cloudflared/YOUR_TUNNEL_ID.json

ingress:
  - hostname: openclaw.yourdomain.com
    service: http://localhost:18789
  - service: http_status:404
EOF

# Create a DNS record for the tunnel
cloudflared tunnel route dns openclaw-tunnel openclaw.yourdomain.com

Now create a systemd user service for cloudflared as well:


cat > ~/.config/systemd/user/cloudflared.service << 'EOF'
[Unit]
Description=Cloudflare Tunnel for OpenClaw
After=network-online.target openclaw-gateway.service
Wants=network-online.target

[Service]
Type=simple
ExecStart=/usr/bin/cloudflared tunnel --config /home/openclaw/.cloudflared/config.yml run
Restart=always
RestartSec=15

[Install]
WantedBy=default.target
EOF

systemctl --user daemon-reload
systemctl --user enable cloudflared
systemctl --user start cloudflared
systemctl --user status cloudflared

Your OpenClaw dashboard is now accessible at https://openclaw.yourdomain.com without any open ports on your VPS. You can also add Cloudflare Access in front of it to require authentication before anyone can reach the dashboard.

Step 7: Add Cloudflare Access Authentication (Recommended)

Cloudflare Access adds a login layer in front of your tunnel so only you can reach the dashboard, even if someone discovers the hostname.

  1. Go to your Cloudflare dashboard and navigate to Zero Trust
  2. Under Access, create a new Application of type Self-Hosted
  3. Set the domain to openclaw.yourdomain.com
  4. Create a policy: allow only your email address (or a specific identity provider)
  5. Save the application

Now anyone visiting openclaw.yourdomain.com will hit a Cloudflare login page before reaching your gateway. This is a free feature on Cloudflare's Zero Trust plan for up to 50 users.

Step 8: Set Up Log Rotation

OpenClaw writes daily memory logs and conversation history. Without log rotation these accumulate and consume disk space over time.


# Create a logrotate config for OpenClaw memory logs
sudo tee /etc/logrotate.d/openclaw << 'EOF'
/home/openclaw/openclaw/memory/*.md {
    weekly
    rotate 12
    compress
    delaycompress
    missingok
    notifempty
    create 644 openclaw openclaw
}
EOF

# Test the config
sudo logrotate --debug /etc/logrotate.d/openclaw

Step 9: Set Up a Health Check Script

This Node.js script checks that the gateway is responding and sends you a Telegram alert if it is not. You can run it from a cron job every few minutes.


// healthcheck.js
// Run with: node healthcheck.js
// Add to crontab: */5 * * * * node /home/openclaw/healthcheck.js

const http = require("http");
const https = require("https");

const GATEWAY_URL = "http://127.0.0.1:18789/health";
const TELEGRAM_TOKEN = process.env.TELEGRAM_BOT_TOKEN;
const TELEGRAM_CHAT_ID = process.env.TELEGRAM_CHAT_ID;
const TIMEOUT_MS = 5000;

function sendTelegramAlert(message) {
  if (!TELEGRAM_TOKEN || !TELEGRAM_CHAT_ID) return;

  const body = JSON.stringify({
    chat_id: TELEGRAM_CHAT_ID,
    text: `OpenClaw Health Alert\n\n${message}\n\nServer: ${process.env.HOSTNAME || "VPS"}`,
  });

  const req = https.request({
    hostname: "api.telegram.org",
    path: `/bot${TELEGRAM_TOKEN}/sendMessage`,
    method: "POST",
    headers: { "Content-Type": "application/json", "Content-Length": body.length },
  });

  req.write(body);
  req.end();
}

function checkHealth() {
  const req = http.get(GATEWAY_URL, { timeout: TIMEOUT_MS }, (res) => {
    if (res.statusCode === 200) {
      console.log(`[${new Date().toISOString()}] Gateway healthy - HTTP ${res.statusCode}`);
    } else {
      const msg = `Gateway returned HTTP ${res.statusCode}`;
      console.error(`[${new Date().toISOString()}] ${msg}`);
      sendTelegramAlert(msg);
    }
  });

  req.on("timeout", () => {
    req.destroy();
    const msg = "Gateway health check timed out after 5 seconds";
    console.error(`[${new Date().toISOString()}] ${msg}`);
    sendTelegramAlert(msg);
  });

  req.on("error", (err) => {
    const msg = `Gateway unreachable: ${err.message}`;
    console.error(`[${new Date().toISOString()}] ${msg}`);
    sendTelegramAlert(msg);
  });
}

checkHealth();

# Save the script and add environment variables
cp healthcheck.js ~/healthcheck.js

# Add to crontab - runs every 5 minutes
(crontab -l 2>/dev/null; echo "*/5 * * * * TELEGRAM_BOT_TOKEN=your_token TELEGRAM_CHAT_ID=your_chat_id node /home/openclaw/healthcheck.js >> /home/openclaw/healthcheck.log 2>&1") | crontab -

Monitoring Your Deployment

flowchart TD
    subgraph Monitor["Monitoring Stack"]
        HC["healthcheck.js\n(cron every 5 min)"]
        JC["journalctl\n(systemd logs)"]
        OC["openclaw logs --follow\n(gateway logs)"]
        DF["df -h\n(disk usage)"]
    end

    HC -->|"Failure"| TG["Telegram Alert"]
    JC -->|"Review"| Dev["You"]
    OC -->|"Debug"| Dev
    DF -->|"Low disk"| Action["Clean old logs\nExpand storage"]

Key commands to keep your deployment healthy:


# View gateway service status
systemctl --user status openclaw-gateway

# Stream live gateway logs
journalctl --user -u openclaw-gateway -f

# View OpenClaw's own logs
openclaw logs --follow

# Check disk usage
df -h
du -sh ~/openclaw/memory/

# Restart gateway after config changes
systemctl --user restart openclaw-gateway

# Update OpenClaw to latest version
npm install -g openclaw@latest
systemctl --user restart openclaw-gateway
openclaw --version

# Run diagnostics
openclaw doctor

Handling Automatic Updates

OpenClaw updates frequently. A simple update script with a cron job keeps your installation current without manual intervention:


# update-openclaw.sh
#!/bin/bash

CURRENT=$(openclaw --version 2>/dev/null)
npm install -g openclaw@latest --quiet
UPDATED=$(openclaw --version 2>/dev/null)

if [ "$CURRENT" != "$UPDATED" ]; then
  echo "[$(date)] Updated from $CURRENT to $UPDATED"
  systemctl --user restart openclaw-gateway
  systemctl --user status openclaw-gateway --no-pager
else
  echo "[$(date)] Already on latest: $CURRENT"
fi

chmod +x ~/update-openclaw.sh

# Run update check daily at 3 AM
(crontab -l 2>/dev/null; echo "0 3 * * * /home/openclaw/update-openclaw.sh >> /home/openclaw/update.log 2>&1") | crontab -

Complete Deployment Checklist

flowchart TD
    A["VPS provisioned\nUbuntu 24.04"]
    A --> B["Non-root user created\nSSH hardened\nufw enabled"]
    B --> C["Node.js 22 installed\nvia nvm"]
    C --> D["OpenClaw installed\nfrom npm"]
    D --> E["Onboard wizard complete\nLLM + Telegram configured"]
    E --> F["gateway.bind = loopback\nin openclaw.json"]
    F --> G["systemd user service\ncreated and enabled"]
    G --> H["loginctl enable-linger\nset for auto-start"]
    H --> I["Cloudflare tunnel\ninstalled and running"]
    I --> J["Cloudflare Access\nprotecting dashboard"]
    J --> K["Log rotation\nconfigured"]
    K --> L["Health check cron\nwith Telegram alerts"]
    L --> M["Auto-update cron\ndaily at 3 AM"]
    M --> N["Production ready"]

Troubleshooting Common VPS Issues

  • Service not starting after reboot: check that loginctl enable-linger openclaw was run and that nvm is loaded in the service's PATH
  • Gateway starts but Telegram does not respond: check outbound connectivity to api.telegram.org from the VPS with curl https://api.telegram.org
  • Memory growing unbounded: check du -sh ~/openclaw/memory/ and confirm logrotate is configured correctly
  • High CPU usage: usually caused by a skill running a tight loop or the browser tool opening many pages. Check openclaw logs --follow for repeated activity
  • Cloudflare tunnel disconnecting: check systemctl --user status cloudflared and review logs with journalctl --user -u cloudflared -f

What Is Next

Your agent is now running 24/7 on a server with no exposed ports, automatic restarts, and health monitoring. In Part 6 we go deep on security: mitigating CVE-2026-25253 and similar vulnerabilities, hardening skill permissions, auditing community skills before installation, and building a secure configuration checklist you can apply to any OpenClaw deployment.

References

Written by:

589 Posts

View All Posts
Follow Me :
How to whitelist website on AdBlocker?

How to whitelist website on AdBlocker?

  1. 1 Click on the AdBlock Plus icon on the top right corner of your browser
  2. 2 Click on "Enabled on this site" from the AdBlock Plus option
  3. 3 Refresh the page and start browsing the site