OpenClaw on NVIDIA Jetson (Orin/Nano)
This guide installs OpenClaw on a JetPack 6.2.2 (Jetson Linux 36.5), runs the Gateway as a systemd system service under a locked-down user, and enables Linuxbrew so the gateway can install skills/tools.
It also includes the fixes we needed in practice:
-
Control UI WebSocket disconnects caused by missing gateway token (“token_missing”).
-
“pairing required” / scope-upgrade dead ends (use devices rotate / revoke stale devices).
-
Avoiding
gateway.trustedProxiesfoot-guns unless you truly run a reverse proxy.
Contents
0) Base packages
sudo apt-get update
sudo apt-get install -y \
curl ca-certificates gnupg \
build-essential python3 pkg-config \
git
1) Install Node.js 22
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
sudo apt-get install -y nodejs
node -v
npm -v
# Optional: pin npm
sudo npm install -g npm@11.10.1
npm -v
2) Install OpenClaw
# If sharp/libvips ever causes install issues:
# SHARP_IGNORE_GLOBAL_LIBVIPS=1 sudo npm install -g openclaw@latest
sudo npm install -g openclaw@latest
openclaw --version
OPENCLAW_BIN="$(command -v openclaw)"
echo "OPENCLAW_BIN=$OPENCLAW_BIN"
3) Create system user + state dir
sudo adduser \
--system \
--home /opt/openclaw/data \
--group \
--shell /usr/sbin/nologin \
openclawuser
sudo mkdir -p /opt/openclaw/data
sudo chown -R openclawuser:openclawuser /opt/openclaw/data
sudo chmod 750 /opt/openclaw/data
4) Install Homebrew (Linuxbrew)
Run as your normal user (e.g.,
orin), not root.
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
echo 'eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"' >> ~/.bashrc
eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"
brew --version
5) Allow openclawuser to run brew
sudo groupadd -f brew
sudo usermod -aG brew orin
sudo usermod -aG brew openclawuser
sudo chgrp -R brew /home/linuxbrew/.linuxbrew
sudo chmod -R g+w /home/linuxbrew/.linuxbrew
sudo find /home/linuxbrew/.linuxbrew -type d -exec chmod g+s {} \;
If your current shell doesn’t see the new group yet: log out/in or run
newgrp brew.
6) Brew caches/logs
sudo mkdir -p /opt/openclaw/data/.cache/Homebrew /opt/openclaw/data/.logs/Homebrew
sudo chown -R openclawuser:openclawuser /opt/openclaw/data/.cache /opt/openclaw/data/.logs
sudo chmod -R 750 /opt/openclaw/data/.cache /opt/openclaw/data/.logs
Optional (only if you get git “dubious ownership” errors from brew):
sudo -u openclawuser bash -lc '
set -euo pipefail
cd /opt/openclaw/data || exit 1
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/home/linuxbrew/.linuxbrew/bin:/home/linuxbrew/.linuxbrew/sbin
export HOME=/opt/openclaw/data
# Allow git operations on brew repo owned by orin:brew
git config --global --add safe.directory /home/linuxbrew/.linuxbrew/Homebrew || true
# Ensure Homebrew core repo has origin configured (if it is a git repo)
if [ -d /home/linuxbrew/.linuxbrew/Homebrew/.git ]; then
git -C /home/linuxbrew/.linuxbrew/Homebrew remote add origin https://github.com/Homebrew/brew 2>/dev/null || true
git -C /home/linuxbrew/.linuxbrew/Homebrew remote set-url origin https://github.com/Homebrew/brew
fi
brew update || true
'
7) Quick brew test
sudo -u openclawuser sh -lc '
cd /opt/openclaw/data || exit 1
export HOME=/opt/openclaw/data
export PATH=/usr/bin:/bin:/usr/sbin:/sbin:/home/linuxbrew/.linuxbrew/bin:/home/linuxbrew/.linuxbrew/sbin
export HOMEBREW_CACHE=/opt/openclaw/data/.cache/Homebrew
export HOMEBREW_LOGS=/opt/openclaw/data/.logs/Homebrew
brew --version
brew install hello
'
8) OpenClaw onboarding (skip daemon)
This creates config/state under /opt/openclaw/data but does not install OpenClaw’s own user-service.
sudo -u openclawuser sh -lc "
cd /opt/openclaw/data || exit 1
export OPENCLAW_HOME=/opt/openclaw/data
export HOME=/opt/openclaw/data
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/home/linuxbrew/.linuxbrew/bin:/home/linuxbrew/.linuxbrew/sbin
exec '$OPENCLAW_BIN' onboard --skip-daemon
"
9) systemd service (system)
Create /etc/systemd/system/openclaw.service:
OPENCLAW_BIN="$(command -v openclaw)"
sudo tee /etc/systemd/system/openclaw.service > /dev/null <<EOF
[Unit]
Description=OpenClaw Gateway Service (system user + brew install)
Wants=network-online.target
After=network-online.target
[Service]
Type=simple
User=openclawuser
Group=openclawuser
SupplementaryGroups=brew
WorkingDirectory=/opt/openclaw/data
Environment=OPENCLAW_HOME=/opt/openclaw/data
Environment=HOME=/opt/openclaw/data
Environment=GIT_CONFIG_GLOBAL=/opt/openclaw/data/.gitconfig
# Minimal PATH + brew
Environment=PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/home/linuxbrew/.linuxbrew/bin:/home/linuxbrew/.linuxbrew/sbin
# Brew caches/logs inside OpenClaw state dir
Environment=HOMEBREW_CACHE=/opt/openclaw/data/.cache/Homebrew
Environment=HOMEBREW_LOGS=/opt/openclaw/data/.logs/Homebrew
# Loopback gateway (recommended)
ExecStart=$OPENCLAW_BIN gateway --bind loopback --port 18789
Restart=always
RestartSec=5
UMask=0077
NoNewPrivileges=yes
PrivateTmp=yes
ProtectSystem=strict
ProtectHome=no
ReadWritePaths=/opt/openclaw/data /home/linuxbrew/.linuxbrew
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now openclaw
10) First connect: Control UI token
Important: the Control UI must provide the gateway token on first connect. If it doesn’t, the gateway logs token_missing and closes the WebSocket.
Get the token:
TOKEN="$(sudo -u openclawuser env OPENCLAW_HOME=/opt/openclaw/data HOME=/opt/openclaw/data \
openclaw config get gateway.auth.token)"
echo "Token length: ${#TOKEN}"
Open the dashboard:
If prompted, paste the token into the Control UI settings.
11) Verify + health checks
Service status
sudo systemctl status openclaw --no-pager -l
Optional health check: confirm port is listening (no restart needed)
sudo ss -lntp | grep 18789 || true
# Verify openclawuser can see node
sudo -u openclawuser sh -lc '
cd /opt/openclaw/data || exit 1
export HOME=/opt/openclaw/data
export PATH=/usr/bin:/bin:/usr/sbin:/sbin:/home/linuxbrew/.linuxbrew/bin:/home/linuxbrew/.linuxbrew/sbin
node -v
'
# Verify openclaw binary
sudo -u openclawuser sh -lc '
cd /opt/openclaw/data || exit 1
export HOME=/opt/openclaw/data
export PATH=/usr/bin:/bin:/usr/sbin:/sbin:/home/linuxbrew/.linuxbrew/bin:/home/linuxbrew/.linuxbrew/sbin
openclaw --version
'
# Verify brew (and brew doctor)
sudo -u openclawuser sh -lc '
cd /opt/openclaw/data || exit 1
export HOME=/opt/openclaw/data
export PATH=/usr/bin:/bin:/usr/sbin:/sbin:/home/linuxbrew/.linuxbrew/bin:/home/linuxbrew/.linuxbrew/sbin
export HOMEBREW_CACHE=/opt/openclaw/data/.cache/Homebrew
export HOMEBREW_LOGS=/opt/openclaw/data/.logs/Homebrew
brew doctor || true
'
# If you see "fatal: detected dubious ownership" from brew/git under openclawuser:
# sudo -u openclawuser env HOME=/opt/openclaw/data \
# git config --global --add safe.directory /home/linuxbrew/.linuxbrew/Homebrew || true
After any restart, wait for port bind (avoids transient ECONNREFUSED/1006)
sudo systemctl restart openclaw
until sudo ss -lntp | grep -q ':18789'; do sleep 1; done
Gateway + status probe
TOKEN="$(sudo -u openclawuser env OPENCLAW_HOME=/opt/openclaw/data HOME=/opt/openclaw/data \
openclaw config get gateway.auth.token)"
sudo -u openclawuser env OPENCLAW_HOME=/opt/openclaw/data HOME=/opt/openclaw/data \
openclaw gateway status --url ws://127.0.0.1:18789 --token "$TOKEN"
sudo -u openclawuser env OPENCLAW_HOME=/opt/openclaw/data HOME=/opt/openclaw/data \
openclaw status --url ws://127.0.0.1:18789 --token "$TOKEN"
12) Operations: tokens, devices, pairing, security
Tips:
- If you pass
--url ws://127.0.0.1:18789, also pass--token "$TOKEN"(or setOPENCLAW_GATEWAY_TOKEN).
- After any
systemctl restart openclaw, wait for the port to be listening before running probes to avoid transient1006/ECONNREFUSED.
Helpers
# Reusable vars
URL="ws://127.0.0.1:18789"
# Fetch current gateway token (do NOT print or paste it into logs/chats)
TOKEN="$(sudo -u openclawuser env OPENCLAW_HOME=/opt/openclaw/data HOME=/opt/openclaw/data \
openclaw config get gateway.auth.token)"
# Wait until gateway port is listening (use after restarts)
wait_gateway() {
until sudo ss -lntp | grep -q ':18789'; do sleep 1; done
}
A) Rotate gateway token (do this if it leaks)
sudo -u openclawuser env OPENCLAW_HOME=/opt/openclaw/data HOME=/opt/openclaw/data \
openclaw doctor --generate-gateway-token
sudo systemctl restart openclaw
wait_gateway
TOKEN="$(sudo -u openclawuser env OPENCLAW_HOME=/opt/openclaw/data HOME=/opt/openclaw/data \
openclaw config get gateway.auth.token)"
After rotating, update the token stored in your browser Control UI settings (the UI stores it after first successful connect).
B) Reverse proxy safety: gateway.trustedProxies
Loopback-only setup (recommended): keep this empty.
sudo -u openclawuser env OPENCLAW_HOME=/opt/openclaw/data HOME=/opt/openclaw/data \
openclaw config set gateway.trustedProxies '[]'
sudo systemctl restart openclaw
wait_gateway
Only set gateway.trustedProxies if you actually run a reverse proxy in front of the gateway, and then set it to the proxy IPs/CIDRs you control.
C) Optional hardening: deny risky commands
Example: disable Canvas eval/snapshot:
sudo -u openclawuser env OPENCLAW_HOME=/opt/openclaw/data HOME=/opt/openclaw/data \
openclaw config set gateway.nodes.denyCommands '["canvas.eval","canvas.snapshot"]'
sudo systemctl restart openclaw
wait_gateway
D) Pairing / device scope dead-ends (“pairing required”)
List paired devices (includes clientId, clientMode, and lastUsedAtMs so you can identify CLI vs Control UI profiles):
sudo -u openclawuser env OPENCLAW_HOME=/opt/openclaw/data HOME=/opt/openclaw/data \
openclaw devices list --json --url "$URL" --token "$TOKEN"
If a device (commonly clientId: cli) is missing scopes and gets stuck in pairing, rotate scopes:
sudo -u openclawuser env OPENCLAW_HOME=/opt/openclaw/data HOME=/opt/openclaw/data \
openclaw devices rotate --device <deviceId> --role operator \
--scope operator.read \
--scope operator.write \
--scope operator.admin \
--scope operator.approvals \
--scope operator.pairing \
--url "$URL" --token "$TOKEN"
E) Device cleanup (remove old browser profiles)
Browsers/profiles can create multiple openclaw-control-ui devices over time. Use the lastUsedAtMs field from devices list --json to keep the active one and revoke older ones.
# Revoke a stale Control UI device
sudo -u openclawuser env OPENCLAW_HOME=/opt/openclaw/data HOME=/opt/openclaw/data \
openclaw devices revoke --device <oldDeviceId> --role operator \
--url "$URL" --token "$TOKEN"
F) Credential permissions hardening
# Inspect permissions (and parent path)
sudo ls -ld /opt/openclaw/data/.openclaw/credentials
sudo namei -l /opt/openclaw/data/.openclaw/credentials
# Lock down to owner-only
sudo chown -R openclawuser:openclawuser /opt/openclaw/data/.openclaw/credentials
sudo chmod 700 /opt/openclaw/data/.openclaw/credentials
sudo find /opt/openclaw/data/.openclaw/credentials -type d -exec chmod 700 {} \;
sudo find /opt/openclaw/data/.openclaw/credentials -type f -exec chmod 600 {} \;
sudo systemctl restart openclaw
wait_gateway
G) Security audit (recommended)
sudo -u openclawuser env OPENCLAW_HOME=/opt/openclaw/data HOME=/opt/openclaw/data \
openclaw security audit
sudo -u openclawuser env OPENCLAW_HOME=/opt/openclaw/data HOME=/opt/openclaw/data \
openclaw security audit --deep
Optional auto-fix (use with care):
sudo -u openclawuser env OPENCLAW_HOME=/opt/openclaw/data HOME=/opt/openclaw/data \
openclaw security audit --fix
H) Shareable diagnostics (read-only)
# Full local diagnosis
sudo -u openclawuser env OPENCLAW_HOME=/opt/openclaw/data HOME=/opt/openclaw/data \
openclaw status --all --token "$TOKEN"
# Adds gateway health probes
sudo -u openclawuser env OPENCLAW_HOME=/opt/openclaw/data HOME=/opt/openclaw/data \
openclaw status --deep --token "$TOKEN"
# Gateway runbook checks
sudo -u openclawuser env OPENCLAW_HOME=/opt/openclaw/data HOME=/opt/openclaw/data \
openclaw gateway status --url "$URL" --token "$TOKEN"
# Live health snapshot from the running gateway
sudo -u openclawuser env OPENCLAW_HOME=/opt/openclaw/data HOME=/opt/openclaw/data \
openclaw health --verbose --url "$URL" --token "$TOKEN"
I) Quick troubleshooting loop
sudo systemctl status openclaw --no-pager -l
sudo journalctl -u openclaw --no-pager -n 200
sudo -u openclawuser env OPENCLAW_HOME=/opt/openclaw/data HOME=/opt/openclaw/data \
openclaw logs --follow
sudo -u openclawuser env OPENCLAW_HOME=/opt/openclaw/data HOME=/opt/openclaw/data \
openclaw doctor
References
-
Control UI token / connect params: (OpenClaw)
-
Devices (list/json/rotate/revoke): (OpenClaw)
-
Gateway protocol (token auth + device token rotate/revoke semantics): (OpenClaw)
-
Trusted proxy auth + when to use it: (OpenClaw)
-
trustedProxiesand local-client/proxy header behavior: (OpenClaw) -
Troubleshooting / expected “good” outputs: (OpenClaw)