All tutorials
13 min read

How to Install and Secure OpenClaw on a VPS (Ubuntu 24.04)

Install OpenClaw (formerly Clawdbot and Moltbot) on an Ubuntu 24.04 VPS as an unprivileged user, keep port 18789 on loopback, and connect Telegram.

TL;DR:

  • To install OpenClaw on Ubuntu 24.04, install Node.js 24 LTS, create an unprivileged openclaw user, run the official installer as that user, and install the gateway as a systemd user service.
  • Written against OpenClaw 2026.9.8 (released October 3, 2026), which needs Node 24.16+ or 26.1+.
  • OpenClaw's docs list 1 GB RAM with swap as the minimum and 2 GB or more as recommended when the model runs through an API.
  • The gateway uses port 18789 and binds to 127.0.0.1 by default. Keep 18789 closed in UFW and reach the dashboard through an SSH tunnel.
  • OpenClaw's Compose file publishes port 18789 on every interface, and Docker's rules bypass UFW, so the Docker path needs DOCKER-USER rules first.

OpenClaw, formerly Clawdbot and Moltbot, is an open-source (MIT) AI agent gateway that connects chat apps such as Telegram and Discord to a model provider and runs tools on its host. This tutorial shows how to install OpenClaw on an Ubuntu 24.04 VPS under a separate system user, keep the gateway on the loopback address behind the Uncomplicated Firewall (UFW), and connect Telegram. It also covers updates, backups and Docker. A VPS keeps the agent reachable while your laptop is off.

Applies to: Ubuntu 24.04 LTS · OpenClaw 2026.9.8 · Node.js 24.21 LTS · checked October 2026

Prerequisites

  • A VPS running Ubuntu 24.04 LTS with at least 1 vCPU, 1 GB RAM (2 GB recommended) and root SSH access. Arct Cloud Linux images include root SSH and accept an SSH public key at deploy; see Linux VPS plans.
  • A sudo user that logs in with an SSH key, called deploy here. To create a key, see How to Generate an SSH Key.
  • An API key from a supported model provider, such as Anthropic or OpenAI.
  • A Telegram account, if you want to message the agent from your phone.

If the server has only root, log in as root, create deploy, and copy your SSH key to it. adduser asks for the password that sudo uses:

adduser deploy
usermod -aG sudo deploy
rsync --archive --chown=deploy:deploy ~/.ssh /home/deploy

Log out and reconnect with ssh deploy@YOUR_SERVER_IP. Commands run as deploy until Step 5. Replace values in capitals, such as YOUR_SERVER_IP, with your own.

How much RAM does OpenClaw need?

OpenClaw needs 1 GB RAM with swap at minimum and 2 GB or more when the model runs through an API, per its Raspberry Pi and DigitalOcean install pages.

SetupRAMSource
Gateway with an API model1 GB plus 2 GB swap minimum; 2 GB or more recommendedOpenClaw install docs
Docker image built from source6 GB for the build; the pre-built image skips itOpenClaw Docker docs

Local models need RAM for the model on top; see best Ollama models for CPU servers. Sustained full-CPU load is not permitted on Arct Cloud plans, so keep local models to on-demand, interactive replies and send scheduled or batch work to an API model. For an API model, Cost Optimized cvm.nano (1 vCPU, 2 GB RAM, 25 GB NVMe) matches the recommended size, and cvm.pico (1 vCPU, 1 GB RAM, 15 GB NVMe) meets the minimum with the Step 2 swap file. Browser skills, Docker and extra agents add memory, so size up to 4 GB for those, such as cvm.micro (2 vCPU, 4 GB RAM, 40 GB NVMe).

Step 1: Update the server

Install updates, Git, UFW, the build tools the OpenClaw installer would add itself, and dbus-user-session for systemd user services.

sudo apt update && sudo apt upgrade -y
sudo apt install -y curl git ufw build-essential python3 cmake dbus-user-session

If the upgrade installed a new kernel, reboot with sudo reboot and reconnect.

Step 2: Add a swap file

A swap file keeps a 1 GB or 2 GB server from killing processes when memory runs out. Check for existing swap with swapon --show, and skip this step if it lists a device. OpenClaw's 1 GB guide uses 2 GB of swap:

sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab

free -h now shows 2.0Gi in the Swap: row. Skip this step on 4 GB servers.

Step 3: Turn on the UFW firewall

UFW blocks every incoming connection you do not allow. Allow SSH before you enable it, or the session drops. Leave port 18789 closed.

sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow OpenSSH
sudo ufw enable

Type y to confirm. sudo ufw status now lists only OpenSSH (IPv4 and IPv6).

Step 4: Install Node.js 24 LTS

OpenClaw 2026.9.8 requires Node 24.16+ or 26.1+. Install Node 24 LTS from NodeSource, the line the OpenClaw installer uses on Linux:

curl -fsSL https://deb.nodesource.com/setup_24.x | sudo -E bash -
sudo apt-get install -y nodejs
node -v

The output looks similar to this:

v24.21.0

OpenClaw's docs recommend Node 26, which starts the gateway faster and uses less memory than Node 24. To use it, replace setup_24.x with setup_26.x. A system-wide Node lets the OpenClaw installer run without sudo in Step 6.

Step 5: Create an unprivileged openclaw user

OpenClaw's agent can run shell commands, so run it as a separate system user without sudo rights. Lingering starts that user's services at boot.

sudo useradd --create-home --shell /bin/bash openclaw
sudo loginctl enable-linger openclaw
sudo -iu openclaw

You now have a shell as openclaw. A sudo -i shell does not set XDG_RUNTIME_DIR, which systemctl --user needs, so add it to the shell profile:

echo 'export XDG_RUNTIME_DIR="/run/user/$(id -u)"' >> ~/.bashrc
source ~/.bashrc
systemctl --user is-system-running

The last command prints running or degraded, so the user service manager answers. Steps 6 to 12 run as openclaw; return to this shell with sudo -iu openclaw.

Step 6: Run the OpenClaw installer

The official installer at https://openclaw.ai/install.sh checks Node and Git and installs the openclaw npm package. Preview it with a dry run, then install without onboarding:

curl -fsSL https://openclaw.ai/install.sh | bash -s -- --dry-run
curl -fsSL https://openclaw.ai/install.sh | bash -s -- --no-onboard

The system npm prefix is not writable for openclaw, so the installer switches npm to ~/.npm-global and adds its bin directory to ~/.bashrc. Reload the shell and check the version:

source ~/.bashrc
openclaw --version

At the time of writing, the installer pulls OpenClaw 2026.9.8. Your output may show a newer version.

Step 7: Run OpenClaw onboarding

Onboarding saves the model provider, creates the agent workspace and generates a gateway token.

openclaw onboard

Choose Quick start, select your model provider, and paste the API key. OpenClaw tests the key with a real completion before saving it. On a server without a display, the wizard then runs the gateway in the foreground and prints a dashboard link. Press Ctrl+C to stop it; the configuration stays saved.

Quick start gives the default agent, main, full tool access. As openclaw, it can run commands and edit that user's files, but it cannot use sudo.

Step 8: Install the gateway as a systemd service

Install the gateway, the long-running OpenClaw process, as a systemd user service so it restarts after crashes and reboots:

openclaw gateway install
systemctl --user enable --now openclaw-gateway.service
openclaw gateway status

A healthy gateway reports Runtime: running and Connectivity probe: ok and listens on port 18789. Follow its logs with openclaw logs --follow.

Step 9: Open the dashboard through an SSH tunnel

The dashboard, called the Control UI, is an admin surface for chat, configuration and tool approvals, so it stays on 127.0.0.1. On your computer, open a tunnel as your sudo user and leave it running:

ssh -N -L 18789:127.0.0.1:18789 deploy@YOUR_SERVER_IP

Open http://127.0.0.1:18789/ in your browser. When it asks for the Gateway secret, print the token on the server and paste it in:

openclaw gateway auth-token --show

Treat the token as a password. If the dashboard asks for device approval, run openclaw devices list and openclaw devices approve YOUR_REQUEST_ID on the server.

Step 10: Connect a Telegram bot (optional)

In Telegram, message @BotFather, send /newbot, and copy the bot token it returns. Read the token without saving it to shell history, then add the channel:

read -rsp "Bot token: " TG_TOKEN && echo
openclaw channels add --channel telegram --token "$TG_TOKEN"
unset TG_TOKEN
openclaw channels status --probe

Send any message to your bot. Under the default pairing policy, the bot replies with a pairing code and does not run the agent until you approve it. Codes expire after 1 hour:

openclaw pairing list telegram
openclaw pairing approve telegram YOUR_PAIRING_CODE

Discord uses the same command with --channel discord and a bot token, after you enable the Message Content Intent and invite the bot to your server; see OpenClaw's Discord setup.

Step 11: Lock down OpenClaw security settings

The gateway binds to loopback by default. Set it explicitly, tighten file permissions, and run the security audit:

openclaw config set gateway.bind loopback
openclaw gateway restart
chmod 700 ~/.openclaw
chmod 600 ~/.openclaw/openclaw.json
openclaw security audit --deep

The audit reports open DM or group policies, exposed binds, short tokens and loose permissions. openclaw security audit --fix switches open group policies to allowlists and tightens file permissions.

  • Keep Telegram's DM policy on pairing. The open policy lets anyone who finds the bot trigger the agent's tools.
  • For a bot that other people message, start from OpenClaw's hardened baseline, which denies shell, file and automation tools by default.

Rotate the gateway token

Rotate the token if it was shared or pasted somewhere public. Set a new random value, restart, and print it:

openclaw config set gateway.auth.token "$(openssl rand -hex 32)"
openclaw gateway restart
openclaw gateway auth-token --show

Paste the new value into Gateway secret on each client, then confirm the old token no longer connects.

Step 12: Verify the gateway is not public

On the server, check the service, the listening address and the health endpoint:

openclaw gateway status
ss -tln | grep 18789
curl -fsS http://127.0.0.1:18789/healthz

The ss output looks similar to this. The local address must be 127.0.0.1 or [::1], never 0.0.0.0 or *:

LISTEN 0      511        127.0.0.1:18789      0.0.0.0:*

From your computer, with the tunnel closed, confirm the port is unreachable:

curl -m 5 http://YOUR_SERVER_IP:18789/healthz

The request times out. If it returns a response, check gateway.bind and sudo ufw status.

Install OpenClaw with Docker instead

OpenClaw publishes an official image, ghcr.io/openclaw/openclaw, and a Docker Compose setup script. This path replaces Steps 4 to 12; Steps 1 to 3 still apply. Install Docker Engine and the Compose plugin first and run these commands as deploy, added to the docker group.

Block published ports with DOCKER-USER rules

The Compose file publishes ports 18789, 18790 and 3978 on every interface, and Docker's rules bypass UFW. Before you start the container, append OpenClaw's documented DOCKER-USER block to /etc/ufw/after.rules:

sudo tee -a /etc/ufw/after.rules > /dev/null <<'EOF'
*filter
:DOCKER-USER - [0:0]
-A DOCKER-USER -m conntrack --ctstate ESTABLISHED,RELATED -j RETURN
-A DOCKER-USER -s 127.0.0.0/8 -j RETURN
-A DOCKER-USER -s 10.0.0.0/8 -j RETURN
-A DOCKER-USER -s 172.16.0.0/12 -j RETURN
-A DOCKER-USER -s 192.168.0.0/16 -j RETURN
-A DOCKER-USER -s 100.64.0.0/10 -j RETURN
-A DOCKER-USER -p tcp --dport 80 -j RETURN
-A DOCKER-USER -p tcp --dport 443 -j RETURN
-A DOCKER-USER -m conntrack --ctstate NEW -j DROP
-A DOCKER-USER -j RETURN
COMMIT
EOF
sudo ufw reload
sudo iptables -S DOCKER-USER
sudo ip6tables -S DOCKER-USER

These rules drop new internet connections to published container ports except 80 and 443. If you enable IPv6 in Docker, add the same rules to /etc/ufw/after6.rules with IPv6 private ranges.

Run the Docker setup script

Clone the release tag that matches the image, pin the image, and run the setup script:

git clone --branch v2026.9.8 --depth 1 https://github.com/openclaw/openclaw.git
cd openclaw
export OPENCLAW_IMAGE="ghcr.io/openclaw/openclaw:2026.9.8"
./scripts/docker/setup.sh

The amd64 image is about 1.2 GB compressed. The script prompts for provider keys, writes a gateway token to .env, and starts the stack with restart: unless-stopped, so the gateway returns after a reboot. The container runs as the non-root node user and keeps state in ~/.openclaw on the host. Check it:

docker compose ps
curl -fsS http://127.0.0.1:18789/healthz

Compose publishes 18789 on every interface, so ss -tln shows 0.0.0.0:18789 on this path, and the DOCKER-USER rules drop new connections to it from the internet. If /etc/docker/daemon.json sets the loopback default from the Docker tutorial, ss -tln shows 127.0.0.1:18789 instead. In both cases, from your computer, with the tunnel closed, confirm that curl -m 5 http://YOUR_SERVER_IP:18789/healthz times out. Leave gateway.bind on lan, the value the setup script sets: loopback limits the gateway to the container's own network.

Open the dashboard and connect Telegram in Docker

Open the dashboard with the ssh -L tunnel command from Step 9 and the OPENCLAW_GATEWAY_TOKEN value from .env in the repository directory. Run OpenClaw commands through the openclaw-cli service. To connect Telegram:

read -rsp "Bot token: " TG_TOKEN && echo
docker compose run --rm openclaw-cli channels add --channel telegram --token "$TG_TOKEN"
unset TG_TOKEN

Send any message to the bot, then approve the pairing code:

docker compose run --rm openclaw-cli pairing list telegram
docker compose run --rm openclaw-cli pairing approve telegram YOUR_PAIRING_CODE

Update OpenClaw

Back up first (next section), then preview and apply the update as openclaw:

openclaw update --dry-run
openclaw update
openclaw --version

openclaw update checks the new release while the old gateway keeps serving, then restarts and verifies the service.

For Docker, run these as deploy. Replace YOUR_NEW_VERSION with a release number from OpenClaw's releases, such as 2026.9.8, without the leading v:

cd ~/openclaw
OC_VERSION="YOUR_NEW_VERSION"
git fetch --depth 1 origin tag "v$OC_VERSION"
git checkout "v$OC_VERSION"
export OPENCLAW_IMAGE="ghcr.io/openclaw/openclaw:$OC_VERSION"
OPENCLAW_SKIP_ONBOARDING=1 ./scripts/docker/setup.sh
docker compose run --rm openclaw-cli doctor --json

The setup script reads OPENCLAW_IMAGE only from the current shell. Without it, the script builds an image from source, which needs 6 GB RAM. These commands follow OpenClaw's Docker update steps.

Back up OpenClaw

OpenClaw keeps its state in SQLite databases under ~/.openclaw. Never copy the live .sqlite files; the backup command captures them safely and verifies the archive:

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

The archive holds provider keys, channel tokens and chat history. Store it encrypted, off the server. For whole-server copies, see VPS backups.

Stop or restart the OpenClaw gateway

Manage the service as openclaw:

openclaw gateway stop
openclaw gateway start
openclaw gateway restart

openclaw gateway stop leaves the unit enabled, so the gateway starts again at the next boot. To keep it off across reboots, disable the unit:

systemctl --user disable --now openclaw-gateway.service

For Docker, run docker compose stop openclaw-gateway in the repository directory.

Troubleshoot common OpenClaw errors

SymptomCauseFix
openclaw: command not found~/.npm-global/bin is not on PATHRun source ~/.bashrc or reopen sudo -iu openclaw
$XDG_RUNTIME_DIR not defined from systemctl --userThe sudo -i shell has no runtime directoryRepeat the Step 5 export; check loginctl show-user openclaw --property=Linger prints Linger=yes
unauthorized in the dashboardThe browser sent an old tokenPaste the output of openclaw gateway auth-token --show
administratively prohibited when the tunnel opensSSH port forwarding is disabledPut AllowTcpForwarding local in /etc/ssh/sshd_config.d/10-forwarding.conf (sshd reads that directory first), run sudo sshd -t, then sudo systemctl restart ssh
Killed or exit code 137The server ran out of memoryAdd the Step 2 swap file or move to more RAM

For other problems, run openclaw doctor.

FAQ

What is the gateway for OpenClaw?

The gateway is OpenClaw's always-on process. It holds chat channel connections, routes messages to the model, runs tools, and serves the dashboard and API on port 18789, bound to 127.0.0.1 by default.

How do I enable the OpenClaw gateway?

Run openclaw gateway install as the user that owns OpenClaw. On Linux it writes and starts a systemd user service named openclaw-gateway. Enable lingering for that user so the gateway survives logouts and reboots.

How do I stop the OpenClaw gateway?

Run openclaw gateway stop as the service user. The unit stays enabled and starts at the next boot, so run systemctl --user disable --now openclaw-gateway.service to keep it off. For Docker, run docker compose stop.

How much RAM do I need to install OpenClaw on a VPS?

OpenClaw runs on 1 GB RAM with a swap file, and its docs recommend 2 GB or more when the model runs through an API. Building the Docker image from source needs 6 GB; the pre-built image skips that build.

Is OpenClaw free?

Yes. OpenClaw is open source under the MIT license. You pay for the server and for the model provider's API usage.

Is OpenClaw safe to run on a VPS?

Yes, with the gateway on loopback, a private token and the agent running as a user without sudo. Keep channels on pairing or allowlists, because anyone who can message the agent can trigger its tools.

Can I use a local LLM with OpenClaw?

Yes. Onboarding detects tool-capable models already installed in Ollama or LM Studio on the same server. CPU inference suits occasional, interactive replies; scheduled jobs and batch work belong on an API model.

Next steps

Cost Optimized plans run on AMD EPYC Milan 7003 Series processors up to 3.7 GHz with NVMe storage. Compare plans.

This work is licensed under CC BY-NC-SA 4.0.