Setup documentation for deploying OpenClaw on a VPS. Potential video content.
- Fresh Ubuntu server (tested on Ubuntu 24.04)
- SSH access with key authentication
- Anthropic API key
- Dedicated VPS β never run on your daily driver machine
β οΈ SECURITY ALERT (Feb 2026): A high-severity vulnerability was disclosed enabling one-click RCE via cross-site WebSocket hijacking. Always runnpm install -g openclaw@latestto get the patched version. If you have an existing install, update immediately.
π‘ What are SSH keys? Instead of typing a password every time you connect, SSH keys let your computer prove its identity using cryptography. You have a "private key" (stays on YOUR computer, never share) and a "public key" (goes on servers you want to access). It's like a house key that only works with your specific lock.
# Generate SSH key (skip if you already have one)
# -t ed25519: Use the ed25519 algorithm (modern, secure, fast)
# -C "comment": A label to help you remember what this key is for
ssh-keygen -t ed25519 -C "openclaw-server"
# This creates two files in ~/.ssh/:
# id_ed25519 = your PRIVATE key (NEVER share this!)
# id_ed25519.pub = your PUBLIC key (safe to share, goes on servers)
# Display your public key (copy this entire line including "ssh-ed25519...")
cat ~/.ssh/id_ed25519.pubIn Hetzner Cloud Console β Create Server β SSH Keys β Add your public key.
# Connect to server using SSH
# "root" = username, YOUR_SERVER_IP = the IP address Hetzner gave you
# Your private key is used automatically to prove your identity
ssh root@YOUR_SERVER_IPπ‘ Why all this security stuff? A server on the internet gets attacked constantly β bots scan for weak passwords, open ports, and vulnerable software 24/7. These steps create layers of defense so even if one thing fails, attackers can't get in.
# Update package list and upgrade all installed packages to latest versions
# "apt update" = refresh the list of available software
# "apt upgrade" = install newer versions of what you have
# "-y" = answer "yes" automatically to prompts
sudo apt update && sudo apt upgrade -y
# Install essential security and utility tools:
# - ufw: "Uncomplicated Firewall" β blocks unwanted network traffic
# - fail2ban: Automatically bans IPs that try too many failed logins
# - git, curl, wget: Tools for downloading code and files
# - build-essential: Compilers needed to install some software
# - zsh: A nicer shell (optional but recommended)
sudo apt install -y ufw fail2ban git curl wget build-essential zshπ‘ What does fail2ban do? It watches your login logs. If someone tries to guess your password and fails too many times (e.g., 5 failed attempts), fail2ban automatically blocks their IP address for a period of time. It runs in the background β you installed it, it's already protecting you, no extra config needed for basic protection.
# Ensure fail2ban starts on boot and is running
sudo systemctl enable fail2ban
sudo systemctl start fail2ban
# Verify it's active (should show "active (running)" in green)
sudo systemctl status fail2banπ‘ What's a firewall? Think of it as a bouncer for your server. It blocks ALL incoming connections except the ones you explicitly allow. Without it, any service running on your server could be accessed by anyone on the internet.
# Allow SSH connections (port 22) β this is how YOU connect to the server
# If you block this, you'll lock yourself out!
sudo ufw allow ssh
# Turn on the firewall
# IMPORTANT: Make sure you allowed SSH first, or you'll be locked out
sudo ufw enable
# That's it! Only SSH is allowed now. OpenClaw's port (18789) stays blocked,
# which is exactly what we want β we'll access it securely via SSH tunnel later.π‘ Why disable passwords? Passwords can be guessed. Bots try thousands of common passwords every day. SSH keys are virtually impossible to guess (they're like a 3000-character random password). Once your key is working, disable passwords to eliminate that attack vector entirely.
# Modify SSH config to disable password authentication
# "sed -i" = edit file in place
# 's/old/new/' = replace "old" with "new"
# We run it twice to catch both commented (#) and uncommented versions
sudo sed -i 's/#PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config
sudo sed -i 's/PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config
# Restart SSH service to apply changes
sudo systemctl restart ssh
# Verify the change worked (should show "PasswordAuthentication no")
grep PasswordAuthentication /etc/ssh/sshd_config
# Check firewall status (should only show SSH/22 allowed)
sudo ufw status
β οΈ Never expose port 18789 to the internet. Use SSH tunnel to access remotely (see Section 8).
Nicer terminal with autocomplete, syntax highlighting (shows valid commands in green, invalid in red), and quick directory jumping.
# Install Oh My Zsh (will prompt to set zsh as default)
sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"
# Install plugins
git clone https://github.com/zsh-users/zsh-autosuggestions.git ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions
git clone https://github.com/zsh-users/zsh-syntax-highlighting.git ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting
# Enable plugins (edit ~/.zshrc, find plugins= line)
nano ~/.zshrc
# Change to: plugins=(git z zsh-autosuggestions zsh-syntax-highlighting)
# Right click to paste, ctrl+o to save, enter, ctrl+x
# Apply
source ~/.zshrcWhat you get:
zβ jump to directories by partial name (e.g.,z projβ/home/user/projects)- Autosuggestions β gray text shows command history as you type, press β to accept
- Syntax highlighting β valid commands green, invalid red, paths underlined
# Download and run the NodeSource setup script
# This adds their package repository so we can install a modern Node.js version
# (Ubuntu's default repos often have outdated versions)
# curl -fsSL = download silently, follow redirects, show errors
# | sudo -E bash - = pipe it to bash running as sudo
curl -fsSL https://deb.nodesource.com/setup_24.x | sudo -E bash -
# Now install Node.js from the repository we just added
sudo apt install -y nodejs
# Verify it worked (should show v24.x.x)
node --versionπ‘ Why run as root? On a dedicated single-purpose VPS, running as root is simpler and avoids issues with systemd user services. The real security comes from network isolation (Tailscale + UFW), not user separation. This is also what most popular OpenClaw setup guides recommend.
# Install OpenClaw globally using npm
# "-g" = global install, so you can run "openclaw" from anywhere
# "@latest" = get the newest version
npm install -g openclaw@latest
# Run the setup wizard
# "--install-daemon" = also install it as a background service that starts on boot
openclaw onboard --install-daemonDuring onboard:
- Enabled hooks: boot-md, session-memory
- Skipped skills (configure later)
- Set up Anthropic auth (Claude token)
# Run OpenClaw's built-in security check and auto-fix issues
openclaw security audit --fix
# For a deeper scan (checks exposed ports, auth issues, permissions)
openclaw security audit --deep
# Verify the gateway is binding to localhost only
# netstat shows network connections; -tlnp = TCP, listening, numeric, process info
# We grep for the OpenClaw port to see what address it's bound to
netstat -tlnp | grep 18789π‘ 127.0.0.1 vs 0.0.0.0 β why it matters:
127.0.0.1:18789= β GOOD β only accepts connections from this machine itself (localhost)0.0.0.0:18789= β BAD β accepts connections from ANYWHERE on the internetIf you see
0.0.0.0, anyone who finds your server's IP could potentially access OpenClaw. With127.0.0.1, the only way in is through an SSH tunnel (which requires your SSH key).
Done via TUI (not CLI commands):
- Give it the Discord bot token
- Add server (guild) ID
- Add specific text channel IDs
- Add your user ID to allowlist β CRITICAL: never leave empty
π‘ Why is the allowlist critical? The allowlist controls WHO can give commands to your OpenClaw instance through Discord. If you leave it empty or set it wrong, random people in your Discord server could potentially make your AI agent do things. Always add your Discord user ID to the allowlist so only YOU can control it.
If you get a 401 error, re-run onboarding:
openclaw onboardConfirm existing config, re-authenticate with Anthropic.
Don't expose port 18789 to the internet. Use SSH tunnel instead.
π‘ What's an SSH tunnel? It's like a secret passage. Your computer connects to the server via SSH (which is allowed through the firewall), and then "forwards" traffic through that secure connection. So when you visit
localhost:18789on YOUR computer, it secretly travels through the SSH connection to reach the server'slocalhost:18789. The port never needs to be opened to the public internet.
# Run this on YOUR computer (not the server)
# -L = "Local port forwarding"
# 18789:localhost:18789 means:
# "When I connect to port 18789 on my machine,
# forward it to localhost:18789 on the remote server"
# root@YOUR_HETZNER_IP = connect as the root user
ssh -L 18789:localhost:18789 root@YOUR_HETZNER_IP
# While this SSH session is running, you can open your browser and go to:
# http://localhost:18789
# It will actually show the OpenClaw interface from your server!
# When you close the SSH session, the tunnel closes too.π‘ What's Tailscale? It creates a private network between your devices using WireGuard VPN. Your server gets a private IP (like
100.x.x.x) that only YOUR devices can access. No port forwarding, no SSH tunnel needed β just connect directly using the Tailscale IP. It's like your server and laptop are on the same home network, even though one is in a data center.
# On the Hetzner server β install Tailscale
curl -fsSL https://tailscale.com/install.sh | sh
# Start Tailscale and authenticate (opens a browser link)
sudo tailscale up
# Check your Tailscale IP
tailscale ip -4
# Should show something like 100.64.0.1Basic access (Tailscale IP):
# Install Tailscale on your laptop too, then access via the Tailscale IP:
# http://100.64.0.1:18789
# Works from anywhere, no SSH tunnel needed, still secureBetter: Tailscale Serve (HTTPS with ts.net URL):
π‘ What's Tailscale Serve? Instead of using a raw IP + port, Tailscale Serve exposes your service at a clean HTTPS URL like
https://srv1234567.ts.net. This adds TLS encryption and is easier to bookmark. Your service becomes completely invisible to the public internet β only devices on your Tailnet can access it.
# Set up Tailscale Serve (replace 18789 with your actual port if different)
# --bg = run in background
sudo tailscale serve --bg http://localhost:18789
# Check what URL you got
tailscale serve status
# Output shows something like: https://srv1234567.tail8328fe.ts.net
# Now you can access OpenClaw from any device on your Tailnet at that URL
# No port number needed, HTTPS includedExtra: Block the port explicitly in UFW:
# Even though the port isn't exposed by default, explicitly deny it
# This provides defense-in-depth in case something changes
sudo ufw deny 18789Verify your setup is actually secure:
- β
Should work: Access your
ts.netURL from a device with Tailscale connected - β Should NOT work: Access
http://YOUR_VPS_PUBLIC_IP:18789from any browser
If the public IP loads, your firewall isn't configured correctly.
# Start the gateway (if not using daemon mode)
openclaw gateway
# Useful gateway commands:
openclaw gateway status # Check if gateway is running
openclaw gateway restart # Restart the gateway
openclaw logs # View gateway logs
openclaw doctor # Diagnose issues
openclaw channels status --probe # Test Discord/channel connectivityAccess the dashboard:
# Get the correct tokenized URL
openclaw dashboard
# If accessing remotely, open SSH tunnel first (on your LOCAL machine):
ssh -N -L 18789:127.0.0.1:18789 root@YOUR_SERVER_IP
# Then open the URL in your browserπ‘ What's this? Even with Tailscale, you want a password on your dashboard. The gateway token acts as a shared secret β anyone with the token can access your OpenClaw instance. Without it, anyone on your Tailnet could control your agent.
Easiest method β get the correct tokenized URL:
openclaw dashboardThis outputs the full URL with token. Copy and paste it into your browser.
For Docker installs (Hostinger one-click, etc.):
# Find your gateway token in the container environment
docker inspect $(docker ps -q) | grep -i OPENCLAW_GATEWAY_TOKEN
# Output: "OPENCLAW_GATEWAY_TOKEN=abc123def456ghi789jkl"
# Your tokenized URL becomes:
# https://YOUR_TAILSCALE_URL?token=abc123def456ghi789jkl
# Example: https://srv1234567.ts.net?token=abc123def456ghi789jklFor native npm installs:
# Check if token is configured (token is auto-generated during onboard)
cat ~/.openclaw/openclaw.json | grep gateway_token
# The onboard wizard generates a token automatically.
# Your tokenized URL is shown at the end of onboarding.
# If you need to regenerate, edit the config or run:
openssl rand -hex 32Bookmark your tokenized URL. No token = no access.
π‘ Why? Security patches drop constantly. Unattended-upgrades keeps your server patched automatically β one less thing to remember. Critical for a server that runs 24/7.
# Install unattended-upgrades
sudo apt install unattended-upgrades -y
# Enable automatic security updates
sudo dpkg-reconfigure -plow unattended-upgrades
# Select "Yes" when prompted
# Verify it's configured
cat /etc/apt/apt.conf.d/20auto-upgrades
# Should show: APT::Periodic::Unattended-Upgrade "1";Your server now patches itself. Sleep better.
This is the most common issue when accessing the dashboard. The token in your URL doesn't match what the gateway expects.
Fix it in one command:
openclaw dashboardThis outputs the correct tokenized URL. Copy and paste it into your browser.
If accessing remotely (SSH tunnel):
# On your LOCAL machine, open the tunnel:
ssh -N -L 18789:127.0.0.1:18789 root@YOUR_SERVER_IP
# Then open the URL from `openclaw dashboard` in your browser:
# http://localhost:18789/?token=YOUR_TOKEN_HEREIf you have OpenClaw running locally too:
If port 18789 is already in use on your local machine (e.g., you're running OpenClaw locally), use a different local port:
# Use port 18790 locally instead
ssh -N -L 18790:127.0.0.1:18789 root@YOUR_SERVER_IP
# Then access via the different port:
# http://localhost:18790/?token=YOUR_TOKEN_HEREIf the gateway isn't running:
# Check if it's running
netstat -tlnp | grep 18789
# Start it if needed
openclaw gateway# Run diagnostics
openclaw doctor
# Check logs
openclaw logs
# Kill any zombie processes and restart
pkill -f openclaw-gate
openclaw gateway# Check channel connectivity
openclaw channels status --probe
# Make sure you configured the channel allowlist
openclaw configure --section discordπ‘ Pro tip: If you left the Discord channel allowlist empty during onboarding, the bot won't respond to any channels. You must add specific channel IDs.
π‘ Why each item matters β if any of these fail, you're exposed:
Before going live:
- Port 18789 NOT in UFW rules (
sudo ufw status) β the port shouldn't appear in the list; it should be blocked from the internet - Gateway shows
127.0.0.1:18789(not0.0.0.0) β ensures OpenClaw only accepts local connections, not internet traffic - Discord user allowlist configured β prevents random Discord users from controlling your AI agent
- Accessing via SSH tunnel or Tailscale β you have a secure way to reach the gateway without exposing it to the internet
- Gateway token configured β dashboard requires password to access
- Unattended-upgrades enabled β server patches itself automatically
- Public IP access fails β test that
http://YOUR_IP:18789does NOT load from external browser - Dedicated VPS only β never run on your daily driver machine
# --- Status Checks ---
tailscale ip -4 # Check Tailscale IP
tailscale serve status # Check serve URL
sudo ufw status # Check firewall rules
netstat -tlnp | grep 18789 # Check what's listening on the port
# --- OpenClaw ---
openclaw dashboard # Get correct tokenized dashboard URL
openclaw gateway # Start the gateway
openclaw gateway status # Check gateway running
openclaw logs # View logs
openclaw doctor # Diagnose issues
openclaw channels status --probe # Test Discord connectivity
# --- Docker (if using) ---
docker ps # List running containers
docker logs $(docker ps -q) # View container logs
docker inspect $(docker ps -q) | grep -i OPENCLAW_GATEWAY_TOKEN # Get token
# --- Security ---
sudo systemctl status fail2ban # Check fail2ban running
cat /etc/apt/apt.conf.d/20auto-upgrades # Check auto-updates configured