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
~/openclawviaopenclaw gateway --init. - Non-root user called
openclawowns 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
MemoryMaxor 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.envwithout 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:
- Enable
WatchdogSec=30sand make your agent emitsd_notify("WATCHDOG=1")to verify the event loop isn’t stuck. - Expose Prometheus metrics via
--metrics-port 9100and hook upnode_exporter. - Set up a
systemd-timerto 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.