Skip to content

Host OpenClaw

Your always-on personal AI agent, on its own VM and in your Telegram.

Deploy on exe.dev

Shelley handles the setup. About 15 minutes.

Prefer a terminal?
curl -fsSL https://hub.exe.dev/deploy/openclaw/prompt.txt | ssh exe.dev new --prompt=/dev/stdin

OpenClaw (formerly Clawdbot, then Moltbot) is an open-source personal AI agent. Its Gateway is one long-running Node.js process that owns the agent's memory, workspace, tools, and chat connections: you message it from Telegram, WhatsApp, Discord, Slack, or its web dashboard, and it runs commands, edits files, browses, and remembers what you told it, on the machine it lives on.

That last part is why it should not live on your laptop. An exe.dev VM is a separate Linux machine that is always on: the Gateway runs as a service that keeps going when you disconnect, the dashboard sits behind exe.dev's login at https://my-openclaw.exe.xyz/, Claude is reached through exe.dev's LLM integration so no API key is stored on the VM, and if the agent breaks something you restore a backup or start over. This guide sets up exactly that, with a Telegram bot that answers only you.

What you get#

  • A persistent agent: the OpenClaw Gateway runs as the systemd user service openclaw-gateway with lingering on, keeps running after you disconnect, and answers with Claude through exe.dev's LLM integration
  • One messaging integration: a Telegram bot that replies to the owner's Telegram account and to nobody else (dmPolicy allowlist)
  • Service restart: openclaw gateway restart, status, and logs work, and the Gateway comes back by itself after the VM reboots
  • Retained configuration: config, credentials, memory, and workspace live in ~/.openclaw, are archived nightly to ~/backups/openclaw, restore from an archive, and survive openclaw update
  • The dashboard at https://my-openclaw.exe.xyz/, private behind exe.dev login, with the owner's browser paired
What's running, and where
Private https://my-openclaw.exe.xyz/ the OpenClaw dashboard, behind exe.dev login and OpenClaw's own device pairing
my-openclaw.exe.xyz exe.dev VM
OpenClaw Gateway Node.js 24, systemd user service openclaw-gateway, loopback only :18789~/.openclaw
nginx reverse proxy for the dashboard, with WebSockets :8000
Claude exe.dev LLM integration at https://llm.int.exe.xyz, no API key on the VM
Telegram bot long polling to the Telegram Bot API, outbound only

Deploy with Shelley#

Click Deploy and Shelley will set up OpenClaw on a new VM, then check that it works. No config-file wrangling required.

Have these ready; Shelley asks for them:

  • TELEGRAM_BOT_TOKEN: the token @BotFather gives you when you send it /newbot in Telegram (looks like 123456789:AA...)
Prefer a terminal?
curl -fsSL \
  https://hub.exe.dev/deploy/openclaw/prompt.txt \
  | ssh exe.dev new --prompt=/dev/stdin
What Shelley is told
TELEGRAM_BOT_TOKEN=<fill-this-in>

Set up OpenClaw on this VM by following the exe.dev recipe at https://hub.exe.dev/deploy/openclaw.md from top to bottom.

The values at the top of this message are mine:
- TELEGRAM_BOT_TOKEN: the token @BotFather gives you when you send it /newbot in Telegram (looks like 123456789:AA...)
Ask me for any that still say <fill-this-in> before you need them.

The recipe's example VM name is my-openclaw. This VM's name is the output of `hostname`: use it wherever the recipe writes my-openclaw. Skip the step that creates the VM; you are on it.

You are done when all of these are true. Check each one yourself:
- A persistent agent: the OpenClaw Gateway runs as the systemd user service openclaw-gateway with lingering on, keeps running after you disconnect, and answers with Claude through exe.dev's LLM integration
- One messaging integration: a Telegram bot that replies to the owner's Telegram account and to nobody else (dmPolicy allowlist)
- Service restart: openclaw gateway restart, status, and logs work, and the Gateway comes back by itself after the VM reboots
- Retained configuration: config, credentials, memory, and workspace live in ~/.openclaw, are archived nightly to ~/backups/openclaw, restore from an archive, and survive openclaw update
- The dashboard at https://my-openclaw.exe.xyz/, private behind exe.dev login, with the owner's browser paired

Never make this VM's web server public (no `share set-public`): the dashboard
controls an agent that has a shell with sudo.
For the Telegram token, run `openclaw channels add` with the token the owner
gave you instead of the `read` line. If they have not created a bot yet,
finish every other section first, then walk them through @BotFather.
Approve a Telegram pairing request only after the owner tells you the code
their bot sent them, then lock the bot to their numeric user ID as shown.
The dashboard pairing link is single-use and expires after ten minutes:
produce it last, after the owner has logged in to https://<vm>.exe.xyz/,
and make a fresh one whenever they ask.
Keep test prompts to the model tiny.
Don't reboot the VM yourself (it would cut off this session): check that
lingering is on and the unit is enabled, and tell the owner how to test a
reboot.

Finish with the URLs I should open.

Also at /deploy/openclaw/prompt.txt. The recipe it follows is /deploy/openclaw.md.

Create the VM#

On your computer, create a VM. Names are global across exe.dev, so if my-openclaw is taken pick another (or run ssh exe.dev new and use the name it prints):

ssh exe.dev new --name=my-openclaw
ssh my-openclaw.exe.xyz

Everything below runs on the VM, as the default exedev user.

Install OpenClaw#

OpenClaw needs Node.js 24.16+ or 26.1+, and the VM image ships an older Node. The official installer takes care of it: on Ubuntu it installs Node 24 LTS from NodeSource (replacing the system nodejs package), installs OpenClaw with npm into ~/.npm-global, and puts that on your PATH.

On the VM:

curl -fsSL https://openclaw.ai/install.sh | bash -s -- --no-onboard --no-prompt
source ~/.bashrc
openclaw --version
node --version

It takes two or three minutes. You should see OpenClaw 2026.x.y and v24.x. --no-onboard skips the interactive setup wizard; the next steps do the same setup with flags.

Set up the Gateway service#

Non-interactive onboarding writes ~/.openclaw/openclaw.json, generates the Gateway's auth token, creates the agent workspace, and installs and starts the systemd user service openclaw-gateway. Models are configured in the next step, so it skips auth here:

openclaw onboard --non-interactive --accept-risk --mode local \
  --auth-choice skip --install-daemon --skip-channels --skip-search

--accept-risk is OpenClaw's acknowledgement that an agent with a shell is powerful; on a VM of its own, that is the point. The first start of the Gateway takes about a minute, so onboarding may end with Gateway did not become reachable. That is fine; wait for it:

until curl -fs http://127.0.0.1:18789/healthz; do sleep 5; done; echo

This prints {"ok":true,"status":"live"}.

A systemd user service normally stops when its user's last session ends. Lingering keeps it running with nobody logged in and starts it at boot. exe.dev VMs have it on for exedev; make sure:

sudo loginctl enable-linger "$USER"
loginctl show-user "$USER" -p Linger
systemctl --user is-enabled openclaw-gateway

Expect Linger=yes and enabled.

Connect Claude through exe.dev#

Every exe.dev VM can call Anthropic's Messages API at https://llm.int.exe.xyz with no key: exe.dev adds the credentials on the way out and bills your account. Point OpenClaw's built-in Anthropic provider there:

openclaw config patch --stdin <<'EOF'
{
  models: {
    providers: {
      anthropic: {
        baseUrl: "https://llm.int.exe.xyz",
        apiKey: "exe-llm-integration",
        request: { allowPrivateNetwork: true },
      },
    },
  },
  agents: { defaults: { model: { primary: "anthropic/claude-sonnet-5-5" } } },
  memory: { search: { provider: "none" } },
}
EOF
openclaw gateway restart

What each line does:

  • apiKey is a placeholder. OpenClaw wants a value; the integration ignores it.
  • allowPrivateNetwork is needed because llm.int.exe.xyz resolves to a link-local address inside the VM, which OpenClaw's SSRF guard blocks unless you opt in for that provider. Without it, every reply fails with Blocked: resolves to private/internal/special-use IP address.
  • Claude Sonnet 5.5 is the default model; anthropic/claude-opus-5-5 is stronger and costs more (openclaw models set anthropic/claude-opus-5-5).
  • memory.search.provider: "none" makes memory search keyword-based. OpenClaw's semantic memory search wants an OpenAI embeddings key, and the embeddings path doesn't accept the integration's address yet.

The restart takes up to a minute. Then send the agent a test message:

until curl -fs http://127.0.0.1:18789/readyz >/dev/null; do sleep 5; done
openclaw agent --agent main -m "Reply with exactly: pong"
openclaw models status | grep -E 'Default|Utility'

The agent answers pong, and the models are anthropic/claude-sonnet-5-5 and anthropic/claude-haiku-5-5 (for small background tasks), both through the integration.

Open the dashboard#

The Gateway listens on 127.0.0.1:18789 only. exe.dev's HTTPS proxy sends https://my-openclaw.exe.xyz/ to port 8000, private to you, so nginx on port 8000 passes it to the Gateway, WebSockets included. This replaces the image's default nginx site:

sudo tee /etc/nginx/sites-available/openclaw >/dev/null <<'EOF'
# https://<vm>.exe.xyz/ (exe.dev proxy) -> nginx :8000 -> OpenClaw Gateway 127.0.0.1:18789
map $http_upgrade $openclaw_connection {
    default upgrade;
    ""      close;
}

server {
    listen 8000 default_server;
    listen [::]:8000 default_server;
    server_name _;

    location / {
        proxy_pass http://127.0.0.1:18789;
        proxy_http_version 1.1;

        # The dashboard talks to the Gateway over a WebSocket
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $openclaw_connection;

        proxy_set_header Host $host;
        # Overwrite, never append: OpenClaw trusts these only from 127.0.0.1
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-Proto https;
        proxy_set_header X-Forwarded-Host $host;

        proxy_read_timeout 1d;
        proxy_send_timeout 1d;
        proxy_buffering off;
        client_max_body_size 50m;
    }
}
EOF
sudo rm -f /etc/nginx/sites-enabled/default
sudo ln -sf /etc/nginx/sites-available/openclaw /etc/nginx/sites-enabled/openclaw
sudo nginx -t && sudo systemctl reload nginx

Then tell the Gateway its public address and that nginx on 127.0.0.1 is a proxy it can trust for client addresses. The dashboard refuses browser origins it doesn't know, so the address must match exactly:

openclaw config patch --stdin <<EOF
{
  gateway: {
    publicOrigin: "https://$(hostname).exe.xyz",
    controlUi: { allowedOrigins: ["https://$(hostname).exe.xyz"] },
    trustedProxies: ["127.0.0.1"],
  },
}
EOF
curl -s http://127.0.0.1:8000/healthz; echo

The last line goes through nginx and prints {"ok":true,"status":"live"}. (Other paths return proxy_attribution_required when you curl them from the VM itself; that is OpenClaw refusing a forwarded request that claims to come from localhost, and it is expected.)

Now pair your browser. Two layers protect the dashboard: exe.dev's login, and OpenClaw's own auth, where each browser is a device the Gateway must approve.

  1. Open https://my-openclaw.exe.xyz/ and log in to exe.dev. The dashboard asks for a Gateway secret; leave that page open.

  2. On the VM, make a one-time owner pairing link for this address:

    openclaw dashboard --json | jq -r .browserUrl | sed \
      -e "s#^http://[^/]*/#https://$(hostname).exe.xyz/#" \
      -e "s#gatewayUrl=[^&]*#gatewayUrl=wss%3A%2F%2F$(hostname).exe.xyz#"
    
  3. Open the printed https://my-openclaw.exe.xyz/#bootstrapToken=... link in the same browser within ten minutes. It is single-use; the dashboard connects, and that browser stays paired as the owner.

Type a message in the chat box; Claude answers.

If the link expired, make a new one. To pair another browser or your phone without a link, run openclaw gateway auth-token --show in a terminal on the VM (it refuses to print into a pipe or log), paste the token into Gateway secret, then approve the request it creates:

openclaw devices list
openclaw devices approve <requestId>

Connect Telegram#

Telegram is the easiest channel to run headless: a bot token is the only credential, and OpenClaw long-polls Telegram, so nothing on the VM is opened to the internet.

  1. In Telegram, open a chat with @BotFather (check the handle exactly), send /newbot, choose a display name and a username ending in bot, and copy the token it replies with.

  2. On the VM, give OpenClaw the token. read -s keeps it out of your shell history; it is stored in ~/.openclaw/openclaw.json, which only exedev can read:

    read -rsp "Telegram bot token: " TELEGRAM_BOT_TOKEN; echo
    openclaw channels add --channel telegram --token "$TELEGRAM_BOT_TOKEN"
    unset TELEGRAM_BOT_TOKEN
    openclaw channels status --probe
    

    The status line for Telegram default shows enabled, configured and no error. getMe returned 401 means the token is wrong.

  3. Pair your own account. Send your bot any message. Telegram DMs start in pairing mode: the bot ignores the message and replies with an 8-character pairing code and your Telegram user ID. On the VM:

    openclaw pairing list telegram
    openclaw pairing approve telegram <CODE>
    

    Approve only the code your bot sent you. The first approval also makes you the command owner (commands.ownerAllowFrom), which owner-only commands and approval prompts need.

  4. Lock the bot to you. In pairing mode strangers who find the bot can still request codes; an allowlist with your numeric user ID makes it ignore everyone else outright. Set the list before the policy (an empty allowlist is rejected):

    TG_ID=123456789   # your numeric Telegram user ID, from the bot's pairing reply
    openclaw config set channels.telegram.allowFrom "[\"$TG_ID\"]" --strict-json
    openclaw config set channels.telegram.dmPolicy allowlist
    openclaw config set commands.ownerAllowFrom "[\"telegram:$TG_ID\"]" --strict-json
    openclaw config get channels.telegram.dmPolicy
    openclaw config get channels.telegram.allowFrom
    

    This prints allowlist and your ID. The change applies without a restart.

Send your bot another message: it answers within a few seconds. Groups stay blocked (groupPolicy defaults to allowlist with no groups); see Telegram access control if you want the bot in a group.

Restart, status, and logs#

The Gateway is a normal systemd user service; OpenClaw's commands wrap it and add health checks:

openclaw gateway status        # Runtime: running, Connectivity probe: ok
openclaw gateway restart       # drains running work, restarts, waits for health
openclaw status                # channels, model, recent sessions
openclaw logs --follow         # the Gateway's own log (also in /tmp/openclaw/)
journalctl --user -u openclaw-gateway -n 100 --no-pager
openclaw doctor                # read-only checks; --fix applies repairs

systemctl --user restart openclaw-gateway works too. A restart takes up to a minute before the dashboard reconnects on its own.

To see it come back after a reboot, reboot the VM (your SSH session drops):

sudo reboot

Give it a minute or two, then on your computer, without logging in to a shell first:

ssh my-openclaw.exe.xyz curl -s http://127.0.0.1:18789/healthz

It prints {"ok":true,"status":"live"}, and your Telegram bot answers again. The Gateway started at boot because the unit is enabled and lingering is on; nobody had to log in.

Where your data lives#

OpenClaw keeps everything that matters in ~/.openclaw. The program itself (~/.npm-global) can always be reinstalled.

Path What it holds
~/.openclaw/openclaw.json Config: Gateway token, Telegram bot token, model provider, allowlists (mode 600)
~/.openclaw/state/openclaw.sqlite The Gateway's shared state database
~/.openclaw/agents/main/agent/ The agent's database: sessions, transcripts, auth profiles
~/.openclaw/workspace/ The agent's workspace and memory: AGENTS.md, SOUL.md, USER.md, MEMORY.md, memory/
~/.config/systemd/user/openclaw-gateway.service The service unit (openclaw gateway install --force rewrites it)
/etc/nginx/sites-available/openclaw The dashboard proxy

Ask the agent to write lasting preferences into AGENTS.md or MEMORY.md rather than relying on chat history; those files are what it reads at the start of every session.

Back up and restore#

Don't copy the live SQLite files; openclaw backup create snapshots them safely while the Gateway runs and archives config, credentials, and the workspace with them:

mkdir -p ~/backups/openclaw && chmod 700 ~/backups
openclaw backup create --output ~/backups/openclaw --verify
ls -lh ~/backups/openclaw

A fresh install archives to a few hundred kilobytes, ending in Archive verification: passed. Make it nightly with a systemd timer that also deletes archives older than 14 days:

mkdir -p ~/.config/systemd/user
cat > ~/.config/systemd/user/openclaw-backup.service <<'EOF'
[Unit]
Description=OpenClaw backup archive

[Service]
Type=oneshot
ExecStart=%h/.npm-global/bin/openclaw backup create --output %h/backups/openclaw --verify
ExecStart=/usr/bin/find %h/backups/openclaw -name *-openclaw-backup.tar.gz -mtime +14 -delete
EOF
cat > ~/.config/systemd/user/openclaw-backup.timer <<'EOF'
[Unit]
Description=Nightly OpenClaw backup

[Timer]
OnCalendar=*-*-* 03:30:00
RandomizedDelaySec=30m
Persistent=true

[Install]
WantedBy=timers.target
EOF
systemctl --user daemon-reload
systemctl --user enable --now openclaw-backup.timer
systemctl --user start openclaw-backup.service
systemctl --user list-timers openclaw-backup.timer

The archives hold your bot token, the Gateway token, and every conversation, so keep copies private. A backup on the same disk doesn't survive losing the VM; on your computer, pull copies down now and then:

rsync -av my-openclaw.exe.xyz:backups/openclaw/ ./openclaw-backups/

To restore, unpack an archive into a staging directory, stop the Gateway, swap the state directory, and start again:

ARCHIVE=$(ls -t ~/backups/openclaw/*-openclaw-backup.tar.gz | head -1)
rm -rf ~/openclaw-restore
openclaw backup restore "$ARCHIVE" --target ~/openclaw-restore
systemctl --user stop openclaw-gateway
mv ~/.openclaw ~/.openclaw.before-restore
mv ~/openclaw-restore/*/payload/posix/home/exedev/.openclaw ~/.openclaw
rm -rf ~/openclaw-restore
openclaw doctor --non-interactive
systemctl --user start openclaw-gateway

backup restore verifies the archive before it writes anything, and never touches the live directory. Once everything works, delete ~/.openclaw.before-restore. To move to a new VM, install OpenClaw there (the first two sections), copy the archive over, restore it the same way, then rerun the nginx and gateway config steps from "Open the dashboard" so the address matches the new VM.

Update#

openclaw update detects the npm install, checks the new version while the old Gateway keeps serving, then swaps it in, runs openclaw doctor, and restarts the service. Your ~/.openclaw stays where it is; the update only replaces the program in ~/.npm-global, and migrates state if a new version needs it. Take a backup first:

openclaw backup create --output ~/backups/openclaw --verify
openclaw update --dry-run
openclaw update
openclaw --version
openclaw gateway status

Don't use sudo npm i -g openclaw: it installs a second copy outside ~/.npm-global that the service doesn't use. After an update, reload the dashboard tab; if it reports a protocol mismatch, open a fresh pairing link.

Use your own key or subscription#

The LLM integration is the default because the key never touches the VM. Other options:

  • Your Anthropic API key, still off the VM. Store it in the exe.dev integration instead of on the VM; nothing changes in OpenClaw. On your computer:

    printf '%s' "$ANTHROPIC_API_KEY" | ssh exe.dev integrations edit llm --anthropic=byok --anthropic-key=-
    
  • A key on the VM, or another provider. Remove the integration override, then run the interactive onboarding and pick your provider (Anthropic, OpenAI, OpenRouter, and many more):

    openclaw config unset models.providers.anthropic
    openclaw onboard
    
  • A Claude or ChatGPT subscription. Claude Code is preinstalled on the VM; log in with claude, then choose the Claude CLI route in openclaw onboard to run the agent on your subscription. For ChatGPT, either connect the subscription to the exe.dev integration (ssh exe.dev integrations setup chatgpt, see LLM integration) or sign in from openclaw onboard. Check your plan's terms for automated use first; OpenClaw's Anthropic page explains the trade-offs.

Troubleshooting#

Gateway did not become reachable after onboarding or a restart. The first start, and each restart, takes about a minute on a small VM. Check openclaw gateway status; if Runtime isn't running, read journalctl --user -u openclaw-gateway -n 100 --no-pager. A config error makes the service exit with status 78 and stay stopped until you fix it; openclaw doctor names the bad key.

The agent replies OpenClaw couldn't finish this reply. Look in openclaw logs --follow while you send a message. Blocked: resolves to private/internal/special-use IP address means request.allowPrivateNetwork is missing from models.providers.anthropic. A 401 or 403 from llm.int.exe.xyz means the LLM integration isn't attached to this VM; test it with curl -s https://llm.int.exe.xyz/v1/messages -H 'content-type: application/json' -H 'anthropic-version: 2023-06-01' -d '{"model":"claude-haiku-4-5","max_tokens":5,"messages":[{"role":"user","content":"hi"}]}' and see LLM integration.

The dashboard says Browser origin not allowed. allowedOrigins must be exactly https://<vm>.exe.xyz, with no trailing slash. If the VM was renamed or this is a copy, rerun the gateway config patch above.

The dashboard shows pairing required or Pairing link is no longer valid. The link was used or is older than ten minutes. Make a new one, or approve the browser with openclaw devices list and openclaw devices approve <requestId>. Private windows forget their pairing when closed.

502 Bad Gateway at https://my-openclaw.exe.xyz/. nginx is up but the Gateway isn't (often mid-restart). openclaw gateway status. If you see exe.dev's welcome page or a 404 instead, the default nginx site is still enabled: ls /etc/nginx/sites-enabled/.

proxy_attribution_required. gateway.trustedProxies is missing 127.0.0.1, or you curled nginx from the VM itself, which is expected to fail; use /healthz for local checks.

The Telegram bot doesn't answer. openclaw channels status --probe shows the channel's state and last error. getMe returned 401 means a wrong or revoked token: get a new one from @BotFather with /token and run openclaw channels add --channel telegram --token ... again. If the bot is silent for you but working, check that channels.telegram.allowFrom holds your numeric user ID (not your @username).

openclaw: command not found. The installer added ~/.npm-global/bin to ~/.bashrc; run source ~/.bashrc or open a new SSH session.

Node ... unsupported. node --version must be 24.16+ or 26.1+. Rerun the installer, which repairs Node, then openclaw doctor --fix.

Frequently asked questions#

Do I need an Anthropic or OpenAI API key to run OpenClaw on exe.dev?

No. This setup points OpenClaw's Anthropic provider at exe.dev's LLM integration, which adds the credentials outside the VM, so no key is stored where the agent can read it. Usage is billed to your exe.dev account. You can switch the integration to your own Anthropic key, or use a key or subscription directly; see "Use your own key or subscription" above.

How much does it cost to host OpenClaw on exe.dev?

The VM is part of your exe.dev plan (pricing); the Gateway needs about 1 GB of memory. Model calls are the variable part: besides your messages, OpenClaw wakes the agent for a heartbeat every 30 minutes and runs a nightly memory pass. openclaw config set agents.defaults.heartbeat.every 2h makes the heartbeat rarer, and 0m turns it off.

Is the OpenClaw dashboard exposed to the internet?

No. The Gateway listens on 127.0.0.1 only, and nginx hands it to exe.dev's proxy on port 8000, which lets through only people who are logged in to exe.dev and have access to the VM. OpenClaw then still requires its own token or a paired browser. Don't run share set-public on this VM.

Is OpenClaw the same as Clawdbot and Moltbot?

Yes. The project was called Clawdbot, then Moltbot, and is now OpenClaw; the command is openclaw and its data lives in ~/.openclaw. The installer and openclaw doctor migrate older installs.

Can I use WhatsApp, Discord, or Slack instead of Telegram?

Yes. openclaw channels add sets up any of them next to or instead of Telegram, and the same pairing and allowlist rules apply; see OpenClaw channels. Telegram is used here because it needs nothing but a bot token and makes only outbound connections.

What can the agent do on the VM?

Anything the exedev user can, and that user has passwordless sudo. That is the reason to give OpenClaw its own VM: keep other secrets and projects off it, back it up, and if it breaks something, restore or start a fresh VM. See OpenClaw's security guide for sandboxing and tool policies.

Does OpenClaw keep running when I close my laptop?

Yes. exe.dev VMs stay on, and the Gateway is a systemd service that does not depend on your SSH session. It restarts if it crashes and starts again after a reboot.

Updated Oct 14, 2026 · Markdown · manifest.json · Something wrong? Tell us