OpenClaw on NVIDIA Jetson (Orin/Nano)

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.trustedProxies foot-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 set OPENCLAW_GATEWAY_TOKEN).
  • After any systemctl restart openclaw, wait for the port to be listening before running probes to avoid transient 1006 / 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)

  • trustedProxies and local-client/proxy header behavior: (OpenClaw)

  • Troubleshooting / expected “good” outputs: (OpenClaw)