If you landed here you probably just finished npm i -g openclaw@latest on a VPS or Raspberry Pi and realised that openclaw gateway dies as soon as your SSH session closes. This post shows exactly how to make OpenClaw an always-on Linux service managed by systemd, with automatic restarts, clean logs, and resource limits you can trust in production.

Why bother with systemd instead of tmux?

Screen/tmux is great for a weekend hack. But the second you put an AI agent on the internet you need:

  • Automatic restarts on crash or OOM.
  • Persistent logs rotated by the OS.
  • A predictable boot sequence so the bot comes back after reboots.
  • Resource accounting (CPU, memory, file descriptors).

systemd gives you all of that out of the box and it ships with every mainstream Linux distribution released in the last decade. No extra dependencies, no Docker overhead.

Prerequisites and directory layout

Assumptions for the rest of this guide:

  • Ubuntu 22.04 LTS or any distro with systemd 247+ (commands are identical on Fedora/Debian/Arch).
  • OpenClaw v1.12.0 installed globally with Node.js 22.3.0.
  • You created an agent in ~/openclaw via openclaw gateway --init.
  • Non-root user called openclaw owns the directory.

Change the paths/usernames if yours differ, but keep the principle of running long-lived services as unprivileged users.

The fast lane: openclaw gateway --install-daemon

Since v1.9.0 OpenClaw ships a helper flag that writes a unit file, reloads systemd and starts the service. Try it first:

# as root or via sudo openclaw gateway --install-daemon \ --cwd /home/openclaw/openclaw \ --user openclaw \ --port 3000

Under the hood this does roughly what we will do manually in the next section, but with sane defaults. Run systemctl status openclaw afterwards to confirm it’s active. If the flag fits your needs, you can skip to the logging section. I still recommend reading the manual way once so you understand what the helper generated.

Rolling your own openclaw.service

Put the following snippet in /etc/systemd/system/openclaw.service. Every line is there for a reason; modify, don’t delete blindly.

[Unit] Description=OpenClaw AI Agent Gateway After=network-online.target Wants=network-online.target [Service] Type=simple User=openclaw WorkingDirectory=/home/openclaw/openclaw # Launch gateway, not the daemon — the gateway spawns the background daemon if missing ExecStart=/usr/bin/env openclaw gateway --port 3000 --no-interactive # Hard restart whenever the process exits, except on clean stop Restart=always RestartSec=5s # Security hardening AmbientCapabilities= CapabilityBoundingSet= NoNewPrivileges=true PrivateTmp=true ProtectHome=true ProtectSystem=full # Resource limits (adjust for your model size/traffic) MemoryMax=1G CPUQuota=80% LimitNOFILE=16384 # Log location is handled by journald, but tag with IDENT SyslogIdentifier=openclaw [Install] WantedBy=multi-user.target

Key points:

  • Type=simple – OpenClaw does not fork; systemd waits for it.
  • Restart=always – ensures crashes or OOMs trigger a restart. If you want to avoid restart loops when the binary is missing, consider Restart=on-failure.
  • MemoryMax and CPUQuota are enforced by cgroups. If your agent streams video frames you’ll need to raise them.
  • ProtectSystem=full makes root FS read-only inside the service except for /var and the user’s home, reducing blast radius if a dependency is exploited.

Enable and start:

sudo systemctl daemon-reload sudo systemctl enable --now openclaw

systemctl status openclaw should show Active: active (running).

Log management with journalctl

No more JSON spam lost in ~/nohup.out. Everything is now inside the journal:

# Follow live logs journalctl -u openclaw -f # Show previous boot, limited to warnings and errors journalctl -u openclaw -p warning -b # Rotate logs bigger than 100M if you run ancient distros without automatic rotation sudo journalctl --vacuum-size=100M

By default, systemd tags logs with the SyslogIdentifier we set ("openclaw"). If you already ship system logs to Loki/ELK, they will pick it up automatically.

Common crash signatures

  • ENOMEM / OOMKilled – raise MemoryMax or reduce the LLM model size.
  • EADDRINUSE – something else occupies port 3000. Check sudo lsof -i :3000.
  • Module not found: node:fs – you’re accidentally using Node 16. Upgrade to Node 22 and rebuild native deps.

Fine-tuning restart behaviour

Blindly restarting forever is dangerous. A tight loop can burn CPU credits and hit API rate limits. Use the throttling knobs:

# restart at most 5 times within a 10-second window StartLimitBurst=5 StartLimitIntervalSec=10

Place those two lines in the [Unit] section. After the 5th failure systemd marks the service as failed and stops until you intervene:

systemctl reset-failed openclaw && systemctl start openclaw

Resource limits that actually stick

OpenClaw lives inside Node.js which loves to request the entire world’s memory unless you tell V8 to chill. Combine cgroup limits with v8 flags:

ExecStart=/usr/bin/env node --max-old-space-size=768 $(which openclaw) gateway --port 3000 --no-interactive

Now Node will crash if it tries to allocate more than ~768 MB and systemd will restart it. Adjust the number to something slightly below MemoryMax; leave headroom for native libraries.

LimitNOFILE for many sockets

If your agent proxies Slack + Discord + WhatsApp at scale you’ll hit the classic "EMFILE: too many open files". The recipe:

LimitNOFILE=65535

Verify at runtime:

cat /proc/$(pidof -s node)/limits | grep "Max open files"

Upgrading OpenClaw with zero-or-low downtime

Stop the service, update, and start again. Because we run behind systemd, the commands are predictable:

# 1. Pull latest release sudo -u openclaw npm i -g openclaw@latest # 2. Optional: backup config cp -r /home/openclaw/openclaw /home/openclaw/openclaw.bak.$(date +%s) # 3. Restart service sudo systemctl restart openclaw

If you have an HTTP load balancer in front, you can run two agents on different ports and orchestrate restarts one at a time, but that’s beyond this post.

What the built-in daemon actually does

Some confusion: OpenClaw ships two binaries:

  • openclaw gateway – The web UI + REST API.
  • openclaw daemon – A lightweight process that keeps the gateway running. It’s mainly used on macOS where users double-click an app bundle and forget about terminal windows. On Linux we let systemd play that role so we run the gateway directly.

If you wonder why gateway spawns another node sub-process, that’s historical; the team is refactoring it away in v2.0.0 (GitHub issue #3147).

Integrating with earlier clawdbot setups

Before the rename, many tutorials placed executables in /usr/local/bin/clawdbot. The binary now symlinks to openclaw, but your systemd file might reference the old name. Fix by replacing:

ExecStart=/usr/local/bin/clawdbot gateway

with

ExecStart=/usr/bin/env openclaw gateway

and reload.

Handy one-liners for daily ops

  • systemctl reload openclaw – rereads .env without dropping connections if the gateway supports SIGHUP (added in v1.11.0).
  • systemctl cat openclaw – print the final unit file, including drop-ins, to audit changes.
  • systemd-cgtop – live view of CPU/mem usage for all services, useful when you run multiple LLM backends.

Next step: add a watchdog and metrics

You now have a resilient OpenClaw service that boots with the machine, restarts on failure, and keeps its logs tidy. The logical next moves are:

  1. Enable WatchdogSec=30s and make your agent emit sd_notify("WATCHDOG=1") to verify the event loop isn’t stuck.
  2. Expose Prometheus metrics via --metrics-port 9100 and hook up node_exporter.
  3. Set up a systemd-timer to prune old conversations from persistent memory weekly.

If you run into weird edge-cases, search "openclaw systemd" on GitHub; the community has a surprisingly large collection of gists for exotic distros and selinux profiles. Happy hacking.