OpenClaw Node Host Guide: Running Exec on a Second Machine, and What Changes Once It Is a Service
A node host lets your agent run shell commands on a machine that isn't the Gateway. The model stays put. Only system.run and system.which travel.
The pages that rank for this run from about 550 words (a quick-start CLI page) to roughly 3,000 (Steve Kinney's remote-node lesson and a long OpenClaw Playbook post that also covers cameras and location). All of them start a node and approve it, and the two long ones show how to point exec at it. None of them follow the node past the afternoon you set it up, into the week where it runs as a background service and quietly stops behaving like the terminal you tested it in. That week is most of this guide.
Who does what
The Gateway runs the model and routes tool calls. When an exec call is marked host=node, the Gateway forwards it to the node host, which runs the command and sends the output and exit code back. Approvals are enforced on the node, against the node's own policy. People who spent an afternoon tuning the Gateway's approvals tend to find that out late.
The macOS companion app exposes the same system commands, so the headless node is mostly for Linux and Windows boxes, usually a build server, sometimes the old NAS in the closet that nobody wants to touch.
Run it in the foreground first
openclaw node run --host <gateway-host> --port 18789 --display-name "build-node"--host defaults to 127.0.0.1. On a second machine that default is wrong by definition. Pick the display name carefully too, because you'll type it into tools.exec.node and every --node flag for as long as the box exists, and those accept a name as readily as an id or IP.
If the Gateway listens only on loopback, tunnel to it:
# Terminal A
ssh -N -L 18790:127.0.0.1:18789 user@gateway-host
# Terminal B
export OPENCLAW_GATEWAY_TOKEN="<gateway-token>"
openclaw node run --host 127.0.0.1 --port 18790 --auth-from-env--auth-from-env makes the node use the token from the environment instead of a pairing bootstrap. If both machines are already on a tailnet, the Tailscale guide is the cleaner route and you can skip the tunnel.
Approve it twice, then prove it
openclaw devices list
openclaw devices approve <deviceRequestId>
openclaw nodes pending
openclaw nodes approve <nodeRequestId>The first approval admits the machine. The second approves the commands it declared, and until it happens the node is connected and useless. The devices guide covers why those are two queues. Then test from the Gateway with no agent in the loop:
openclaw nodes invoke --node build-node --command system.which \
--params '{"bins":["git"]}'A path back means the plumbing works. Keep this command handy. You'll run it again later, and the second run matters more.
Point exec at the node
Exec's host defaults to auto, which resolves to sandbox when a sandbox runtime is active and gateway otherwise. It never resolves to node. You have to say so:
openclaw config set tools.exec.host node
# only needed once a second eligible node connects
openclaw config set 'agents.entries.main.tools.exec.node' "build-node"With one eligible node connected, OpenClaw picks it. With two, it won't guess. If the bound node goes offline it stays bound and shows as Unavailable, which is the behavior you want for a build box and a confusing one at 2 a.m.
Sandbox users, read this paragraph. While a sandbox runtime is active, auto keeps exec in the sandbox and rejects per-call host=node outright, so no amount of prompting moves the command. Set tools.exec.host explicitly. The sandbox guide has the rest of that setup. From chat, /exec host=node node=build-node sets the default for a session, but only for senders your channel allowlists or access groups authorize.
The node's approvals are the ones that count
system.run on the node is gated by exec approvals stored on the node itself, in $OPENCLAW_STATE_DIR/state/openclaw.sqlite. You can read and replace them from the Gateway without logging in:
openclaw approvals get --node build-node
openclaw approvals set --node build-node --file ./build-node-approvals.jsonThe exec approvals guide explains security, ask, and askFallback. My own rule for a new node is allowlist with ask set to on-miss for the first week, and nothing broader until I've read what it actually asked for. On Windows nodes, anything wrapped in cmd.exe /c prompts for approval even when an allowlist entry matches. Plan for that before you wire a Windows box into an unattended job.
What changes once it's a service
openclaw node install --host <gateway-host> --port 18789 --display-name "build-node"
openclaw node start
openclaw node statusThe foreground process you just tested ran inside your login shell. The service doesn't, and four things follow from that.
PATH belongs to the service
Node hosts ignore PATH overrides on exec calls, and the Gateway rejects them for host execution anyway. Whatever your dotfiles add (nvm, ~/.cargo/bin, a pyenv shim) is gone. Put extra entries in the systemd or launchd environment of the node service, or install tools in standard locations. NODE_OPTIONS and PYTHONPATH are stripped too, and shell-wrapper runs pass only TERM, LANG, LC_*, COLORTERM, NO_COLOR, and FORCE_COLOR from the request. This is where that saved system.which command earns its keep. Run it against the installed service with every binary your agent calls, not just git.
Linux kills it at logout
sudo loginctl enable-linger <user>Without lingering, systemd can stop your node when you close the SSH session you installed it from.
Long jobs block
A host=node call ignores background and yieldMs. It returns when system.run finishes, with no process session to poll, so a 40-minute test suite is a 40-minute exec call. Start long work with something that returns right away (a CI trigger, a transient systemd unit) and check on it with a second call.
It updates itself
Headless nodes check for updates hourly by default and activate a new version during idle time, at most once every 12 hours. That's fine on a laptop. On a box a CI job depends on, I turn it off and update the node by hand on the same day I upgrade the Gateway (the upgrade guide covers that side):
openclaw config set nodeHost.autoUpdate.enabled false
# or, in the service environment
OPENCLAW_NO_AUTO_UPDATE=1One more service-only failure. If workspace staging complains about permissions, the state directory or a parent is group- or world-writable. Run chmod go-w on the path it reports and restart the node.
Revoking and re-pairing
openclaw nodes remove --node build-node
# on the node: openclaw node restart (or rerun node run)
openclaw devices approve <deviceRequestId>
openclaw nodes approve <nodeRequestId>Changing --node-id won't get you a fresh start. It changes the client instance ID, and pairing follows the cryptographic device identity. Retiring the box instead? openclaw node uninstall removes the service, and the uninstall guide handles the leftover state directory.
Final verdict
Foreground run, two approvals, one system.which, then the service, then system.which again with the real binary list. People skip that second check. They shouldn't, because the process your agent talks to at 2 a.m. has its own environment and its own update schedule, and the only test that tells you anything about it is the one you run against the service.
Ready to build?
Get the OpenClaw Starter Kit — config templates, 5 production-ready skills, deployment checklist. Go from zero to running in under an hour.
$14 $6.99
Get the Starter Kit →Also in the OpenClaw store
Get the free OpenClaw quickstart guide
Step-by-step setup. Plain English. No jargon.