LaunchAgents, PM2, systemd, SSH tunnels — build a production-grade OpenClaw deployment that survives reboots and network drops
Turn what you learned into a concrete stack decision.
01AI Agent ToolboxBuild autonomous AI agents that can read, write, and actWant the shortlist in your inbox?
Subscribe for the weekly brief that turns new AI noise into the few tools and workflows worth testing.
Curated bundles that help you move from this guide into a working stack.
Guide
OpenClaw Setup: With a Dedicated Machine vs. Without
Two ways to run your personal AI agent — pick the one that fits your setup
Guide
OpenClaw Skills and Plugins
Extend your agent's capabilities with web search, browser automation, memory, and more
Guide
Obsidian Just Became the Best Knowledge Base for AI Agents
Obsidian 1.12 ships a full CLI with 100+ commands — your vault is now programmable from the terminal
Getting OpenClaw installed is the easy part. Building an infrastructure that's reliable — one that survives reboots, network drops, memory pressure, and zombie processes — is what separates a toy setup from a production agent.
This guide covers both deployment paths at depth: macOS dedicated machine (with LaunchAgent), and cloud VPS (with PM2 or systemd). It assumes you've done the basic install and want to harden the setup.
macOS's launchd is the native way to manage persistent background processes. It's more reliable than nohup or tmux because the OS manages the lifecycle — it restarts your process after crashes and on boot.
Keep everything organized:
~/.openclaw/
├── workspace/ # Agent working directory
├── skills/ # Installed skill definitions
├── logs/ # stdout and stderr logs
│ ├── openclaw.log
│ └── openclaw.error.log
└── config.json # Agent configuration
Create a plist at ~/Library/LaunchAgents/ai.openclaw.agent.plist:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>ai.openclaw.agent</string>
<key>ProgramArguments</key>
<array>
<string>/usr/local/bin/node</string>
<string>/usr/local/lib/node_modules/openclaw/bin/openclaw</string>
<string>start</string>
</array>
<key>WorkingDirectory</key>
<string>/Users/YOUR_USERNAME/.openclaw/workspace</string>
<!-- Load secrets from a separate env file instead of hardcoding here.
Create ~/.openclaw/.env with your keys (chmod 600) and source it,
or use the EnvironmentVariables dict below for non-sensitive values only. -->
<key>EnvironmentVariables</key>
<dict>
<key>NODE_ENV</key>
<string>production</string>
</dict>
<key>StandardOutPath</key>
<string>/Users/YOUR_USERNAME/.openclaw/logs/openclaw.log</string>
<key>StandardErrorPath</key>
<string>/Users/YOUR_USERNAME/.openclaw/logs/openclaw.error.log</string>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>ThrottleInterval</key>
<integer>10</integer>
</dict>
</plist>
Key fields explained:
KeepAlive: true — launchd restarts the process if it diesThrottleInterval: 10 — waits 10 seconds before restarting after a crash (prevents crash loops)RunAtLoad: true — starts immediately when the plist is loadedSecurity note: Never put API keys directly in the plist file — it's a plain text XML file. Instead, create a
~/.openclaw/.envfile withchmod 600containing your secrets (ANTHROPIC_API_KEY,TELEGRAM_BOT_TOKEN, etc.) and have your start script source it, or export them in a login shell profile that launchd inherits.
# Load (also starts it immediately)
launchctl load ~/Library/LaunchAgents/ai.openclaw.agent.plist
# Unload (stops and unregisters)
launchctl unload ~/Library/LaunchAgents/ai.openclaw.agent.plist
# Force restart
launchctl kickstart -k gui/$(id -u)/ai.openclaw.agent
# Check status
launchctl print gui/$(id -u)/ai.openclaw.agent
# Tail logs
tail -f ~/.openclaw/logs/openclaw.log
tail -f ~/.openclaw/logs/openclaw.error.log
macOS will put the machine to sleep even if a background service is running. Prevent this:
# Disable sleep entirely (for a dedicated machine, this is fine)
sudo pmset -a sleep 0
sudo pmset -a disksleep 0
sudo pmset -a displaysleep 10 # Let the display sleep, but not the machine
Or use Amphetamine (free app) for a GUI approach.
Your agent won't start unless someone is logged in (for user-context LaunchAgents). Enable auto-login:
System Settings > Users & Groups > Automatically log in as: [your user]
For headless, you may want to hide the login window with a screen lock tool, but auto-login must be on.
Set up a health check that pings your Telegram if OpenClaw stops responding:
# ~/.openclaw/scripts/health-check.sh
#!/bin/bash
response=$(curl -s http://localhost:18789/health 2>/dev/null)
if [ -z "$response" ]; then
curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \
-d "chat_id=${TELEGRAM_CHAT_ID}&text=OpenClaw health check failed. Process may be down."
fi
Schedule it as a separate LaunchAgent running every 5 minutes.
For OpenClaw with cloud model providers (not local models), the bottleneck is network latency and disk I/O, not CPU. A $6/mo Hetzner CX11 (2 vCPU, 2GB RAM) is sufficient for a single agent.
Recommended: Hetzner Cloud (EU), DigitalOcean (global), or Vultr (global). All support Ubuntu 22.04 and have one-click SSH setup.
# On the VPS, as root
adduser openclaw # Create dedicated user
usermod -aG sudo openclaw
rsync --archive --chown=openclaw:openclaw ~/.ssh /home/openclaw/
# Disable root SSH login
sed -i 's/PermitRootLogin yes/PermitRootLogin no/' /etc/ssh/sshd_config
systemctl restart sshd
# Basic firewall
ufw allow OpenSSH
ufw allow 18789 # OpenClaw API port (only if you need external access)
ufw enable
# Install Bun (faster than Node for OpenClaw)
curl -fsSL https://bun.sh/install | bash
source ~/.bashrc
# Install OpenClaw
bun install -g openclaw
# Initialize
openclaw init
PM2 is a Node.js process manager with built-in restart, log management, and clustering.
# Install PM2
bun install -g pm2
# Create an ecosystem config
cat > ~/.openclaw/ecosystem.config.js << 'EOF'
module.exports = {
apps: [{
name: 'openclaw',
script: 'openclaw',
args: 'start',
cwd: '/home/openclaw/.openclaw/workspace',
env: {
NODE_ENV: 'production',
ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY,
TELEGRAM_BOT_TOKEN: process.env.TELEGRAM_BOT_TOKEN,
},
exp_backoff_restart_delay: 100, // Exponential backoff on crashes
max_memory_restart: '300M', // Restart if memory exceeds 300MB
log_file: '/home/openclaw/.openclaw/logs/combined.log',
error_file: '/home/openclaw/.openclaw/logs/error.log',
merge_logs: true,
time: true, // Timestamps in logs
}]
};
EOF
# Start it
pm2 start ~/.openclaw/ecosystem.config.js
# Save PM2 process list so it survives reboots
pm2 save
# Generate and install systemd startup script
pm2 startup systemd -u openclaw --hp /home/openclaw
# Follow the instructions it prints — usually: sudo env PATH=... pm2 startup ...
Common PM2 commands:
pm2 status # Show all processes
pm2 logs openclaw # Tail logs
pm2 restart openclaw # Restart
pm2 stop openclaw # Stop
pm2 monit # Real-time CPU/memory dashboard
For more control, write a systemd service directly:
sudo nano /etc/systemd/system/openclaw.service
[Unit]
Description=OpenClaw AI Agent
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=openclaw
WorkingDirectory=/home/openclaw/.openclaw/workspace
ExecStart=/home/openclaw/.bun/bin/openclaw start
Restart=always
RestartSec=10
StandardOutput=journal
StandardError=journal
SyslogIdentifier=openclaw
# Load secrets from an env file (not committed to git)
EnvironmentFile=/home/openclaw/.openclaw/.env
# Resource limits
MemoryMax=512M
CPUQuota=80%
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable openclaw
sudo systemctl start openclaw
# Monitor
journalctl -u openclaw -f # Follow logs
systemctl status openclaw # Current status
For the OpenClaw API (port 18789), don't expose it publicly. Use SSH tunneling:
# From your local machine — tunnel remote port 18789 to local 18789
ssh -L 18789:localhost:18789 openclaw@your-vps-ip -N
Now you can access http://localhost:18789 locally, which is actually your VPS.
Never hardcode API keys. Use an env file:
# ~/.openclaw/.env (chmod 600)
ANTHROPIC_API_KEY=sk-ant-...
TELEGRAM_BOT_TOKEN=...
TELEGRAM_CHAT_ID=...
NODE_ENV=production
chmod 600 ~/.openclaw/.env
Reference it in your systemd service with EnvironmentFile= or in PM2's ecosystem config with env_file.
| Concern | macOS LaunchAgent | PM2 (VPS) | systemd (VPS) |
|---|---|---|---|
| Auto-restart on crash | Yes (KeepAlive) | Yes | Yes |
| Boot persistence | Yes (RunAtLoad) | Yes (pm2 save) | Yes (systemctl enable) |
| Log management | File-based | PM2 log rotate | journald |
| Resource limits | Basic | Max memory | Full cgroups |
| Secret injection | plist EnvironmentVariables | env file | EnvironmentFile |
| Remote management | SSH or Apple Remote | PM2 web / SSH | SSH + systemctl |
| Monitoring | Manual | pm2 monit | journalctl |
Before calling your deployment production-ready:
chmod 600, not in codeWant a review of your OpenClaw deployment setup or help hardening your infrastructure? Book a 30-minute call and we'll go through it together.