pax@torvalds: ~/blog/getting-openclaw-running — bash

Getting OpenClaw Running:
VM, OS, Install, and Discord

A full walkthrough: build a VM, configure Ubuntu, install OpenClaw under a dedicated unprivileged user, and wire up a Discord bot. By the end you'll have a self-hosted AI agent on isolated hardware, reachable from your Discord server.

// 01 — introduction

Most "AI assistant" products want you to send your conversations, your files, and your integrations through someone else's infrastructure. OpenClaw doesn't. It's an open-source gateway you run yourself — it handles routing, channel connections, and agent sessions entirely on your own hardware. The only data that leaves your machine is what you explicitly send to your LLM provider.

This guide takes the long path on purpose. We're not running OpenClaw as root, we're not skipping the firewall, and we're not hand-waving the security bits. We're spinning up a Linode VPS, locking it down, and running the agent under a dedicated unprivileged account — the way a service should be run. Here's the full stack:

  • A Linode 2 GB instance running Ubuntu 24.04 LTS, protected by Linode's Cloud Firewall
  • A hardened OS baseline — SSH key auth only, UFW, automatic security updates
  • A dedicated clawuser account with no sudo rights
  • OpenClaw installed under that account and running as a systemd user service
  • A Discord bot connected, paired, and responding in your server

By the end you'll have a self-hosted AI agent running on a server you control, reachable from Discord, with no unnecessary ports exposed and no elevated privileges in sight.


// 02 — vm build (linode)

We're running this on Linode (now Akamai Connected Cloud) — straightforward pricing, clean Ubuntu images, and a solid Cloud Firewall product that sits in front of your instance before traffic even hits the NIC. Good fit for a self-hosted gateway.

Step 1 — Create a Linode account

Head to cloud.linode.com and sign up. Once you're in, you'll land on the Cloud Manager dashboard. Everything below happens here.

Step 2 — Add an SSH key

Before creating the instance, upload your public SSH key so Linode can pre-provision it. This way you never need a root password to log in.

  1. Generate a key pair if you don't have one
    local machinebash
    ssh-keygen -t ed25519 -C "openclaw-linode"
    # Accept defaults — key lands in ~/.ssh/id_ed25519.pub
  2. Upload to Linode

    In Cloud Manager: Profile (top right) → SSH Keys → Add an SSH Key. Paste the contents of ~/.ssh/id_ed25519.pub. Give it a label and save.

Step 3 — Create the Linode instance

  1. Start the wizard

    Click Create → Linode in the top navigation bar.

  2. Choose an image

    Under Choose a Distribution, select Ubuntu 24.04 LTS.

  3. Choose a region

    Pick the region closest to you. Latency won't matter much for this workload, but backups and egress pricing are region-dependent.

  4. Choose a plan

    Select Shared CPU → Linode 2 GB ($12/mo). The 1 GB Nanode isn't enough — OpenClaw's gateway plus a running agent session will exhaust it. The 2 GB plan is the minimum you want here.

  5. Set a label and root password

    Label it something like openclaw-01. Set a strong root password — you won't use it to log in day-to-day, but Linode requires one and it's your break-glass credential.

  6. Add your SSH key

    Under SSH Keys, check the key you uploaded in Step 2. Linode will drop it into /root/.ssh/authorized_keys on first boot.

  7. Create the Linode

    Click Create Linode. Provisioning takes about 60 seconds. The instance's public IPv4 address appears in the dashboard once it's running.

💡
Verify SSH access before going further
ssh root@<LINODE_IP>
If this works, you're in. If not, check that the SSH key was selected during creation and that the instance finished provisioning.

Step 4 — Create the Cloud Firewall

Linode's Cloud Firewall filters traffic at the infrastructure level — packets are dropped before they reach your instance. This is what you want: a hard perimeter that doesn't depend on anything running inside the VM.

  1. Open the Firewall manager

    In Cloud Manager: Network → Firewalls → Create Firewall. Give it a label (e.g. openclaw-fw) and assign it to your Linode instance.

  2. Configure inbound rules

    Add the following rules under Inbound Rules. Delete any default "accept all" rule if one exists.

    Label Protocol Port Sources Action
    allow-ssh TCP 22 Your IP (not 0.0.0.0/0) Accept
    drop-all All All 0.0.0.0/0, ::/0 Drop

    Rules are evaluated top-to-bottom. SSH allow rule must come before the drop-all catch. Restrict the SSH source to your own IP — don't leave it open to the world.

  3. Configure outbound rules

    Leave outbound as Accept All (the default). OpenClaw needs to reach your LLM provider, Discord's API, and package mirrors. There's no benefit to restricting outbound on this workload.

  4. Save and apply

    Click Save Changes. The firewall is applied immediately — no reboot required. Confirm SSH still works from your machine before moving on.

⚠️
Lock SSH to your IP, not the world Setting the SSH source to 0.0.0.0/0 exposes port 22 to every bot on the internet. Use your actual IP (check ifconfig.me). If your IP changes frequently, use a CIDR for your ISP range — not wide open.
ℹ️
OpenClaw gateway port stays internal The gateway binds to loopback (127.0.0.1:18789) by default. There's no reason to open that port in the firewall — Discord communicates outbound from your server to Discord's API, not the other way around.

// 03 — os setup & configuration

Linode provisions with root access. First login is as root — we'll create a proper admin account before we're done and lock root out of SSH.

from your local machinebash
ssh root@<LINODE_IP>

Set the hostname

give the machine an identitybash
hostnamectl hostname openclaw
hostnamectl  # confirm

System updates

first thing, alwaysbash
apt update && apt full-upgrade -y
apt autoremove -y
ℹ️
full-upgrade, not upgrade full-upgrade handles dependency changes that upgrade skips. On a fresh Linode it usually makes no difference, but it's the correct habit — especially after a kernel update.

Install essentials

tools we'll needbash
apt install -y \
  curl git ufw fail2ban unattended-upgrades \
  build-essential ca-certificates gnupg

Configure fail2ban

fail2ban watches auth logs and bans IPs that repeatedly fail SSH logins. Don't rely on it as your only line of defence — that's what the Linode Cloud Firewall is for — but it adds a useful layer inside the VM. Create a jail config for SSH:

/etc/fail2ban/jail.d/sshd.confini
[sshd]
enabled   = true
port      = ssh
filter    = sshd
backend   = systemd
maxretry  = 5
findtime  = 10m
bantime   = 1h
ignoreip  = 127.0.0.1/8 ::1
enable and verifybash
systemctl restart fail2ban
systemctl enable fail2ban

fail2ban-client status       # should show sshd jail active
fail2ban-client status sshd

Firewall (UFW)

Linode's Cloud Firewall already blocks traffic at the network edge. UFW adds a second layer inside the instance — defence in depth.

configure ufwbash
ufw default deny incoming
ufw default allow outgoing
ufw allow ssh
ufw enable
ufw status

Harden SSH

⚠️
Copy your SSH key first From your local machine: ssh-copy-id root@<LINODE_IP>. Confirm key-based login works in a second terminal before disabling password auth — locking yourself out is a bad time.

Edit /etc/ssh/sshd_config and set the following:

/etc/ssh/sshd_config — key changesbash
PasswordAuthentication no
PermitRootLogin no
applybash
systemctl restart ssh
systemctl status ssh

Enable automatic security updates

unattended-upgradesbash
dpkg-reconfigure --priority=low unattended-upgrades
# Choose "Yes" when prompted

Create user accounts

Two accounts: clawadmin for day-to-day administration (sudo, SSH access), and clawuser for running OpenClaw with no elevated privileges whatsoever.

admin account with sudobash
# Create the admin user and add to sudo group
adduser clawadmin
usermod -aG sudo clawadmin

# Verify sudo membership
groups clawadmin
⚠️
Add your SSH public key to clawadmin before logging out of root Since password authentication is disabled, you must have key-based auth in place for clawadmin or you will not be able to log back in. From your local machine:

ssh-copy-id clawadmin@<LINODE_IP>

Then open a second SSH session as clawadmin and confirm sudo whoami returns root before closing your root session.
service account — no sudobash
# Create the OpenClaw service user
adduser clawuser

# Verify: clawuser should NOT be in sudo group
groups clawuser
💡
Why a separate service account? The principle of least privilege. OpenClaw doesn't need root — or even sudo — to do its job. If something goes wrong, the blast radius is limited to clawuser's home directory. Sysadmin 101.

Enable lingering so clawuser's systemd user services survive logouts and start automatically on boot:

enable systemd lingeringbash
loginctl enable-linger clawuser

// 04 — openclaw install (unprivileged user)

Switch into the clawuser account. Everything in this section runs as clawuser, not your admin account.

switch userbash
sudo -i -u clawuser
# Prompt should now show: clawuser@openclaw:~$

Install Node.js 22 (via fnm)

We install Node under clawuser's home directory using fnm (Fast Node Manager). No root required, no system Node to conflict with.

install fnm and node 22bash
# Install fnm into ~/.local/bin
curl -fsSL https://fnm.vercel.app/install | bash

# Load fnm in the current shell
export PATH="$HOME/.local/bin:$PATH"
eval "$(fnm env --use-on-cd)"

# Install and activate Node 22
fnm install 22
fnm use 22
fnm default 22

# Verify
node --version   # should print v22.x.x
npm --version

Make fnm available in every future shell session:

add to ~/.bashrcbash
cat >> ~/.bashrc <<'EOF'

# fnm (Node version manager)
export PATH="$HOME/.local/bin:$PATH"
eval "$(fnm env --use-on-cd)"
EOF

source ~/.bashrc

Install OpenClaw

install the CLI globally (under clawuser)bash
npm install -g openclaw@latest

# Verify
openclaw --version
ℹ️
openclaw not found? The global npm bin directory needs to be on your PATH. fnm handles this automatically, but if not: add export PATH="$(npm prefix -g)/bin:$PATH" to your ~/.bashrc and re-source it.

Run the onboarding wizard

The wizard sets up auth, workspace, gateway config, and the systemd user service in one guided flow.

run as clawuserbash
openclaw onboard --install-daemon

When the wizard asks:

  • QuickStart or Advanced? — QuickStart is fine unless you have specific needs
  • Model/Auth: enter your Anthropic API key and pick a model
  • Workspace: accept the default (~/.openclaw/workspace)
  • Gateway port: default 18789, loopback bind — no public exposure
  • Channels: skip for now — we'll configure Discord manually next
  • Install daemon: Yes — this creates a systemd user unit for clawuser

Verify the gateway

health checkbash
# Check gateway status
openclaw gateway status

# Run the doctor — catches config issues
openclaw doctor

# Confirm the systemd user service is active
systemctl --user status openclaw

# Tail live logs
openclaw logs --follow

You should see Runtime: running and RPC probe: ok. If openclaw doctor flags anything, fix it before moving on.

💡
Gateway lifecycle commands openclaw gateway start|stop|restart|status — these all work as clawuser and talk to the systemd user service. No sudo needed.

// 05 — discord configuration

We need two things: a Discord bot with the right permissions, and OpenClaw configured to use it. The bot token is a secret — treat it like a password. We'll set it via the CLI so it never touches a chat message or log file.

Step 1 — Create the Discord application and bot

  1. New application

    Go to the Discord Developer Portal, click New Application, and name it (e.g. "OpenClaw").

  2. Create the bot

    Click Bot in the sidebar. Set the bot's username — this is what will appear in your Discord server. Then scroll to Privileged Gateway Intents and enable:

    • Message Content Intent (required)
    • Server Members Intent (recommended)
  3. Copy the bot token

    Click Reset Token (don't worry — this generates your first token, nothing breaks). Copy it and save it somewhere secure. You'll need it shortly.

Step 2 — Invite the bot to your server

  1. Generate the invite URL

    Click OAuth2 in the sidebar → OAuth2 URL Generator. Enable scopes: bot and applications.commands. A Bot Permissions panel appears below — enable:

    • View Channels
    • Send Messages
    • Read Message History
    • Embed Links
    • Attach Files
    • Add Reactions (optional)

    Do not grant Administrator. Least privilege.

  2. Add the bot

    Copy the generated URL at the bottom, paste it in your browser, select your server, and click Authorize. The bot should now appear in your server's member list (offline for now).

Step 3 — Collect your IDs

Enable Discord Developer Mode: User Settings → Advanced → Developer Mode. Then right-click to copy:

  • Server ID — right-click server icon → Copy Server ID
  • Your User ID — right-click your avatar → Copy User ID

Keep these alongside your bot token. You'll use all three in the next step.

Step 4 — Configure OpenClaw (as clawuser)

⚠️
Never paste your bot token in a chat or message Set it via the CLI only. The command below writes it directly to OpenClaw's config file — it never appears in any log or message history.
set the bot token securely (as clawuser)bash
# Set the token — replace YOUR_BOT_TOKEN with the real value
openclaw config set channels.discord.token '"YOUR_BOT_TOKEN"' --json

# Enable the Discord channel
openclaw config set channels.discord.enabled true --json

# Add your server to the guild allowlist
# Replace YOUR_SERVER_ID and YOUR_USER_ID with real values
openclaw config set \
  'channels.discord.guilds.YOUR_SERVER_ID' \
  '{"requireMention": false, "users": ["YOUR_USER_ID"]}' \
  --json

# Set guild policy to allowlist (secure default)
openclaw config set channels.discord.groupPolicy '"allowlist"' --json

# Restart the gateway to pick up the new config
openclaw gateway restart

Step 5 — Allow DMs and pair

In Discord, right-click your server icon → Privacy Settings and enable Direct Messages. This lets the bot DM you for the pairing handshake.

Now DM your bot in Discord. It will respond with a pairing code. Back on the VM (as clawuser), approve it:

approve pairing (as clawuser)bash
# List pending pairing requests
openclaw pairing list discord

# Approve with the code from your DM
openclaw pairing approve discord <CODE>

Step 6 — Verify

confirm channel is livebash
openclaw channels status --probe

Discord should show as connected. Send a message in any channel on your server — the bot will respond. Send a DM — it will respond there too.

💡
requireMention: true vs false With requireMention: false, the bot responds to every message in allowed channels. On a private server with just you and the bot, that's what you want. On a shared server, set it to true so the bot only responds when @mentioned.

That's the full stack: isolated VM, hardened OS, unprivileged service account, OpenClaw gateway running as a proper systemd user service, and a Discord bot that answers to you and only you. Clean, contained, and entirely yours. No subscriptions. No data leaving your control. Just a process running on metal you own, doing exactly what you tell it.

From here: explore channels, install skills from clawhub.com, and read the official docs for anything this guide skipped. There's a lot more under the hood.

← all posts 2026-02-26  ·  Pax Torvalds
pax@torvalds:~/blog$