Deploy OpenClaw on Ubuntu Server
A traditional Linux deployment with systemd process management, nginx reverse proxy, and Let's Encrypt SSL. This guide covers the full stack from a bare Ubuntu server to a hardened, production-ready OpenClaw instance accessible over HTTPS.
Estimated time: 1.5 hours | Cost: Variable (depends on hosting) | Difficulty: Advanced
Prerequisites
Before you begin, make sure you have:
- An Ubuntu 22.04 LTS server -- any provider works: VPS (Hetzner, OVH, Vultr), bare metal, or a local VM
- SSH access to the server as root or a user with sudo privileges
- An API key for your chosen LLM provider (Anthropic, OpenAI, etc.)
- (Optional) A domain name pointed at your server's IP address via an A record, if you want HTTPS with Let's Encrypt
Minimum hardware: 1 vCPU, 1 GB RAM, 20 GB disk. For production workloads with multiple concurrent agents, 2 vCPUs and 2 GB RAM is recommended.
Step 1: System Setup
SSH into your server and run the initial system configuration.
Update system packages
sudo apt update && sudo apt upgrade -y
Create a dedicated user
Never run application services as root. Create an openclaw user with a home directory but no interactive login password:
# Create the user with a home directory
sudo adduser --disabled-password --gecos "OpenClaw Service" openclaw
# Grant sudo access (needed only during setup, can be revoked later)
sudo usermod -aG sudo openclaw
# Copy your SSH key so you can log in as this user
sudo rsync --archive --chown=openclaw:openclaw ~/.ssh /home/openclaw
Configure locale and timezone
Consistent locale and timezone settings prevent subtle bugs in log timestamps and string handling:
# Set timezone (replace with your preferred zone)
sudo timedatectl set-timezone UTC
# Ensure UTF-8 locale is generated and set
sudo locale-gen en_US.UTF-8
sudo update-locale LANG=en_US.UTF-8
Switch to the openclaw user for the remaining setup:
su - openclaw
Step 2: Install Dependencies
Node.js 20 LTS via NodeSource
NodeSource provides official prebuilt Node.js packages for Ubuntu. This is the recommended method for server deployments where you need a single, stable Node.js version managed by apt:
# Install the NodeSource signing key and repository
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
# Install Node.js (includes npm)
sudo apt install -y nodejs
# Verify the installation
node --version # Should print v20.x.x
npm --version # Should print 10.x.x
Git, nginx, and certbot
# Install supporting packages
sudo apt install -y git nginx certbot python3-certbot-nginx
Verify nginx is running:
sudo systemctl status nginx
You should see active (running). Nginx starts automatically after installation on Ubuntu.
Step 3: Install OpenClaw
Switch to the openclaw user if you have not already, then install the CLI globally:
su - openclaw
# Install OpenClaw globally for this user
npm install -g openclaw
# Verify the installation
openclaw --version
Configure the OpenClaw directory
mkdir -p ~/.openclaw
Create the environment file with your API keys:
cat > ~/.openclaw/.env << 'EOF'
# LLM Provider API Keys
ANTHROPIC_API_KEY=your-anthropic-api-key-here
# OPENAI_API_KEY=your-openai-api-key-here
# OpenClaw Configuration
OPENCLAW_PORT=3111
OPENCLAW_HOST=127.0.0.1
OPENCLAW_LOG_LEVEL=info
EOF
# Lock down permissions -- only the openclaw user can read this file
chmod 600 ~/.openclaw/.env
Note: We bind to
127.0.0.1(localhost) instead of0.0.0.0because nginx will handle external traffic and proxy it to OpenClaw. This prevents direct access to port 3111 from the internet.
Test that OpenClaw starts correctly:
cd ~/.openclaw && openclaw start --dry-run
Step 4: Systemd Service
A systemd unit keeps OpenClaw running across reboots and automatically restarts it after crashes.
sudo tee /etc/systemd/system/openclaw.service > /dev/null << 'EOF'
[Unit]
Description=OpenClaw AI Agent Framework
Documentation=https://docs.openclaw.dev
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=openclaw
Group=openclaw
WorkingDirectory=/home/openclaw/.openclaw
# Environment and startup
EnvironmentFile=/home/openclaw/.openclaw/.env
ExecStart=/usr/bin/node /usr/lib/node_modules/openclaw/bin/openclaw.js start
# Restart policy
Restart=always
RestartSec=5
StartLimitIntervalSec=60
StartLimitBurst=5
# Security hardening
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=read-only
ReadWritePaths=/home/openclaw/.openclaw
PrivateTmp=true
# Logging
StandardOutput=journal
StandardError=journal
SyslogIdentifier=openclaw
[Install]
WantedBy=multi-user.target
EOF
Note: Because Node.js was installed via NodeSource (system-wide via apt), the binary is at
/usr/bin/nodeand the global module is under/usr/lib/node_modules/. If you installed Node.js via nvm instead, adjustExecStartto use a bash wrapper:ExecStart=/bin/bash -c 'source $NVM_DIR/nvm.sh && exec openclaw start'and addEnvironment=NVM_DIR=/home/openclaw/.nvm.
Enable and start the service:
sudo systemctl daemon-reload
sudo systemctl enable openclaw
sudo systemctl start openclaw
# Verify it is running
sudo systemctl status openclaw
View live logs at any time with:
journalctl -u openclaw -f
Step 5: Nginx Reverse Proxy
OpenClaw runs a web dashboard on port 18789 for chat, configuration, and execution approvals. Nginx acts as the public-facing entry point, handling SSL termination, connection buffering, and WebSocket upgrades so you don't expose the dashboard port directly to the internet.
Create the nginx site configuration:
sudo tee /etc/nginx/sites-available/openclaw > /dev/null << 'EOF'
server {
listen 80;
server_name yourdomain.com; # Replace with your domain or server IP
location / {
proxy_pass http://127.0.0.1:3111;
proxy_http_version 1.1;
# Required headers for proper proxying
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# WebSocket support
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# Timeout settings for long-running agent connections
proxy_read_timeout 86400s;
proxy_send_timeout 86400s;
}
}
EOF
Enable the site and reload nginx:
# Enable the site by creating a symlink
sudo ln -sf /etc/nginx/sites-available/openclaw /etc/nginx/sites-enabled/
# Remove the default site (optional, prevents conflicts on port 80)
sudo rm -f /etc/nginx/sites-enabled/default
# Test the configuration for syntax errors
sudo nginx -t
# Reload nginx to apply changes
sudo systemctl reload nginx
At this point, visiting http://yourdomain.com (or http://YOUR_SERVER_IP) in a browser should reach the OpenClaw web interface.
Step 6: SSL with Let's Encrypt
If you have a domain name pointed at your server, certbot can obtain a free TLS certificate and automatically reconfigure nginx:
sudo certbot --nginx -d yourdomain.com
Certbot will:
- Verify domain ownership via an HTTP challenge
- Obtain a certificate from Let's Encrypt
- Modify the nginx config to listen on 443 with TLS and redirect HTTP to HTTPS
Automatic renewal is handled by a systemd timer that certbot installs. Verify it is active:
sudo systemctl status certbot.timer
Test renewal manually (dry run, does not actually renew):
sudo certbot renew --dry-run
Certificates auto-renew every 60-90 days. No manual intervention is needed.
Step 7: Security Hardening
UFW firewall
If you did not configure the firewall earlier, lock down all ports except the ones you need:
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp # SSH
sudo ufw allow 80/tcp # HTTP (for Let's Encrypt challenges and redirect)
sudo ufw allow 443/tcp # HTTPS
sudo ufw enable
sudo ufw status verbose
fail2ban
Automatically ban IPs that repeatedly fail SSH authentication:
sudo apt install -y fail2ban
sudo tee /etc/fail2ban/jail.local > /dev/null << 'EOF'
[DEFAULT]
bantime = 3600
findtime = 600
maxretry = 3
[sshd]
enabled = true
port = ssh
logpath = /var/log/auth.log
maxretry = 3
EOF
sudo systemctl enable fail2ban
sudo systemctl start fail2ban
Disable root login and password authentication
sudo sed -i 's/^PermitRootLogin yes/PermitRootLogin no/' /etc/ssh/sshd_config
sudo sed -i 's/^#PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config
sudo systemctl restart sshd
Warning: Verify you can SSH in as the
openclawuser from a separate terminal before making these changes. If you lock yourself out, you will need your provider's console access to recover.
Unattended security upgrades
sudo apt install -y unattended-upgrades
sudo dpkg-reconfigure -plow unattended-upgrades
Log rotation for OpenClaw
sudo tee /etc/logrotate.d/openclaw > /dev/null << 'EOF'
/home/openclaw/.openclaw/logs/*.log {
daily
rotate 7
compress
delaycompress
missingok
notifempty
create 640 openclaw openclaw
}
EOF
Monitoring
Journalctl is your primary log interface for the systemd service:
# Live tail
journalctl -u openclaw -f
# Last 100 lines
journalctl -u openclaw -n 100 --no-pager
# Logs from the past hour
journalctl -u openclaw --since "1 hour ago"
# Only errors
journalctl -u openclaw -p err
Prometheus node_exporter (optional) exposes system metrics (CPU, memory, disk, network) for scraping by a Prometheus instance:
sudo apt install -y prometheus-node-exporter
sudo systemctl enable prometheus-node-exporter
The exporter listens on port 9100. If you run Prometheus elsewhere, point a scrape target at http://YOUR_SERVER_IP:9100/metrics. Do not expose port 9100 publicly -- either use a VPN, SSH tunnel, or restrict access with UFW to your Prometheus server's IP.
Troubleshooting
Nginx returns 502 Bad Gateway
This means nginx cannot reach the OpenClaw backend on port 3111.
# Is the OpenClaw service running?
sudo systemctl status openclaw
# Is anything listening on port 3111?
sudo ss -tlnp | grep 3111
# Check OpenClaw logs for startup errors
journalctl -u openclaw -n 50 --no-pager
Common causes: the service crashed and hit its restart limit, or the OPENCLAW_PORT in .env does not match the proxy_pass port in nginx.
Certbot fails to obtain a certificate
- Verify your domain's A record points to the server's IP:
dig +short yourdomain.com - Ensure port 80 is open:
sudo ufw status | grep 80 - Make sure nginx is running and the server block has the correct
server_name - Let's Encrypt has rate limits. If you have hit the limit (5 duplicate certificates per week), wait or use
--stagingfor testing.
Service will not start
# Check for detailed error messages
journalctl -u openclaw -n 50 --no-pager
# Verify the ExecStart path is correct
which openclaw
ls -la /usr/lib/node_modules/openclaw/bin/
# Test running OpenClaw manually as the openclaw user
sudo -u openclaw bash -c 'cd ~/.openclaw && source .env && openclaw start --dry-run'
If systemd reports start-limit-hit, reset the failure counter:
sudo systemctl reset-failed openclaw
sudo systemctl start openclaw