Last updated on

Setting Up OpenClaw Node Across 2 VPS via Tailscale


Updated: 2026-02-26 (GMT+7) — Added Gateway config for running behind Tailscale Serve (gateway.trustedProxies), clarified correct pairing flow via openclaw devices ..., and improved systemd/token notes.

Context

We have two servers:

  • VPS 1 (Master / Gateway host): runs OpenClaw Gateway.
  • VPS 2 (Node / Worker): Ubuntu Server that runs a headless OpenClaw Node Host for remote execution.
  • Network: both servers are connected via Tailscale (same tailnet).

To avoid exposing public ports, we use Tailscale Serve on the Master:

  • Master: Gateway binds to loopback (127.0.0.1) for safety, then Tailscale Serve exposes the Control UI + WebSocket over HTTPS/WSS.
  • Node: runs an OpenClaw Node Host (systemd user service) and connects to Master via a MagicDNS Serve URL over TLS (port 443).

Example placeholder URL (not a real host): https://gateway-master.tailnet-xyz.ts.net


Part 1: Configure the Master (Gateway)

1) OpenClaw Gateway config

Edit ~/.openclaw/openclaw.json on the Master:

{
  gateway: {
    bind: "loopback", // Safety: listen on localhost only
    port: 18789,

    // Important when running behind a reverse proxy (Tailscale Serve -> localhost)
    trustedProxies: ["127.0.0.1", "::1"],

    auth: {
      mode: "token",
      token: "YOUR_GATEWAY_TOKEN"
    },

    tailscale: {
      mode: "serve",
      resetOnExit: false
    },

    // Optional: tighten origins for Control UI
    controlUi: {
      allowedOrigins: ["https://gateway-master.tailnet-xyz.ts.net"]
    }
  }
}

Why trustedProxies?

With tailscale serve, requests reach the Gateway via a local proxy (127.0.0.1) plus X-Forwarded-* headers. Trusting loopback proxy IPs avoids warnings like “Proxy headers detected from untrusted address…” and helps keep proxied client detection/pairing behavior consistent.

2) Restart Gateway + get Serve URL

openclaw gateway restart

tailscale serve status
# Example: https://gateway-master.tailnet-xyz.ts.net

Part 2: Configure the Node (Worker)

This is the part where stale state/config often causes the node to stubbornly connect to 127.0.0.1.

1) Install OpenClaw CLI

npm install -g openclaw

We recommend hard-coding the remote gateway connection parameters in the unit file to override any old config/state.

Create/edit: ~/.config/systemd/user/openclaw-node.service

[Unit]
Description=OpenClaw Node Host (Remote)
After=network-online.target
Wants=network-online.target

[Service]
# Force remote gateway via Tailscale Serve (WSS)
ExecStart=/usr/bin/node /usr/lib/node_modules/openclaw/dist/index.js node run --host "gateway-master.tailnet-xyz.ts.net" --port 443 --tls --display-name "Worker-Node-1"

Restart=always
RestartSec=5
KillMode=process

Environment=HOME=/home/username
Environment="PATH=/usr/bin:/usr/local/bin:/bin"

# Token must be provided via ENV
# IMPORTANT: do NOT wrap token with extra quotes like 'TOKEN'
Environment="OPENCLAW_GATEWAY_TOKEN=YOUR_GATEWAY_TOKEN"

# Not recommended by default (insecure; disables TLS verification)
# Environment="NODE_TLS_REJECT_UNAUTHORIZED=0"

[Install]
WantedBy=default.target

Enable and start:

systemctl --user daemon-reload
systemctl --user enable --now openclaw-node.service
systemctl --user status openclaw-node.service --no-pager

Part 3: Pairing (Approve the node)

Right after the node starts, you will typically see:

  • disconnected (1008): pairing required

This is expected: a new node must be approved once on the Gateway.

1) List pending requests (on Master)

openclaw devices list

You should see Pending (1) with Role: node.

Note: this flow is approved via Devices CLI (openclaw devices ...), not openclaw nodes pending/approve.

2) Approve pairing

Approve latest:

openclaw devices approve --latest

Or approve by requestId:

openclaw devices approve <REQUEST_ID>

Verify:

openclaw devices list

3) Restart node + confirm connected

On the Worker:

systemctl --user restart openclaw-node
journalctl --user -u openclaw-node.service -n 80 --no-pager

On the Master:

openclaw nodes status --connected

4) End-to-end test

On the Master:

openclaw nodes run --node "Worker-Node-1" --raw "uname -a && whoami && pwd"

Troubleshooting (field notes)

  1. Node keeps connecting to 127.0.0.1:18789
  • Usually caused by stale state/config.
  • Durable fix: hard-code --host ... --port 443 --tls in the systemd unit.
  1. Token mismatch caused by systemd quoting
  • Wrong (token includes quotes):
    • Environment="OPENCLAW_GATEWAY_TOKEN='TOKEN'"
  • Correct:
    • Environment="OPENCLAW_GATEWAY_TOKEN=TOKEN"
  1. Avoid NODE_TLS_REJECT_UNAUTHORIZED=0
  • Not recommended by default (disables TLS verification).
  • Use only as a last-resort debugging step and remove afterwards.
  1. Do not run a Gateway on the Worker VPS
  • The Worker should run only the node host (openclaw-node.service).
  • Running a local gateway on the Worker can confuse CLI/status and lead to localhost mis-targeting.

Notes by SensaKai Team — updated 2026-02-26.