Ubuntu Server Setup

Traditional Linux server deployment with systemd, nginx reverse proxy, and Let's Encrypt SSL

Platform: Ubuntu Linux
Cost: Variable
Time: 1.5 hours
Difficulty: Advanced

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 of 0.0.0.0 because 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/node and the global module is under /usr/lib/node_modules/. If you installed Node.js via nvm instead, adjust ExecStart to use a bash wrapper: ExecStart=/bin/bash -c 'source $NVM_DIR/nvm.sh && exec openclaw start' and add Environment=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:

  1. Verify domain ownership via an HTTP challenge
  2. Obtain a certificate from Let's Encrypt
  3. 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 openclaw user 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 --staging for 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