Skip to content

Pairing Remote Devices

Your netclaw daemon runs on one machine but you want to use the CLI from another — a laptop, a second server, a container you can’t shell into. Pairing is a two-command handshake: generate a time-limited code on the daemon, exchange it from the remote device, and the client gets a bearer token for all future requests.

  • Netclaw installed on both machines (netclaw init completed on the daemon host). The remote device only needs the netclaw binary — you don’t need to run netclaw init on it. Pairing replaces init for client-only machines.
  • The daemon’s exposure mode set to something other than local — remote pairing doesn’t work over loopback. The default daemon port is 5199; make sure your firewall allows it.
  • Network connectivity between the two machines (same tailnet, tunnel, or direct)

By default, the daemon only listens on loopback. You need to change this before any remote device can connect.

During netclaw init, Step 9 handles this:

Network exposure mode selection during netclaw init

ModeConfig valueRequiresWho can reach it
LocallocalNothingLoopback only (default)
Tailscale Servetailscale-servetailscaled runningSame tailnet
Tailscale Funneltailscale-funneltailscaled runningPublic internet
Cloudflare Tunnelcloudflare-tunnelcloudflared runningInternet via Cloudflare Tunnel

To change the mode after init, edit ~/.netclaw/config/netclaw.json:

{
"Daemon": {
"ExposureMode": "tailscale-serve"
}
}

For tunnel setup details — Tailscale Serve commands, Cloudflare Tunnel configuration, and how each mode binds the daemon — see Exposure Modes.

Restart the daemon after changing exposure mode (netclaw daemon stop && netclaw daemon start, or systemctl restart netclaw if you’re using systemd).

On the machine running the daemon:

Terminal window
netclaw daemon pair

Output:

Pairing code: ABCD-EF23
Expires at: 14:32:15 (local time)
On the remote device, run:
netclaw pair http://my-server:5199

Codes expire after 5 minutes and are single-use. Generating a new code replaces any previous one — only one active at a time.

The character set (23456789ABCDEFGHJKLMNPQRSTUVWXYZ) deliberately excludes 0/O/1/I/L to avoid misreads.

If the daemon runs in a container, the pairing code is logged at Information level:

Terminal window
docker logs <container-name> | grep "Pairing code"

No need to exec into the container.

On the remote machine:

Terminal window
netclaw pair http://my-server:5199

For Tailscale and Cloudflare Tunnel modes, the endpoint URL differs — check Exposure Modes for the correct format for each mode.

The CLI prompts for two things:

Pairing code (XXXX-XXXX): ABCD-EF23
Device name [my-laptop]:

Device name defaults to the machine’s hostname. Hit Enter to accept the default, or type a custom name.

On success:

  • The CLI saves the bearer token to ~/.netclaw/config/secrets.json
  • The daemon endpoint is written to ~/.netclaw/client/config.json
  • netclaw chat, netclaw status, and all other remote commands work from here

If a device with the same name already exists on the daemon, the exchange returns HTTP 409. Revoke the old device first (see below).

There’s no limit to how many devices you can pair — add as many as you need.

From the newly paired device:

Terminal window
netclaw status

If you get system status back, pairing worked. The CLI attaches the bearer token automatically from here on.

Run these from the daemon host.

Terminal window
netclaw daemon devices

Shows Name, Created, and Last Used for each paired device. Prints No paired devices. if none exist.

Terminal window
netclaw daemon devices revoke <name>

After revocation, the device gets 401 on its next request. Use this when a device is lost, retired, or you need to re-pair with the same name.

  • Raw tokens never hit disk on the daemon side — it stores a SHA256 hash with a per-device salt in ~/.netclaw/config/devices.json (file permissions 600 on Linux)
  • Clients keep the raw token in ~/.netclaw/config/secrets.json
  • Loopback connections skip bearer auth entirely — if you’re on the daemon host, you don’t need to pair

When the CLI connects, it checks these in order:

  1. NETCLAW_DAEMON_ENDPOINT environment variable
  2. ~/.netclaw/client/config.json (written by netclaw pair)
  3. Default: http://127.0.0.1:5199

Override with the env var when you need to switch between multiple daemons without re-pairing.

The pairing endpoint has three layers of brute-force protection:

LayerBehavior
Rate limiter5 attempts/minute per IP
Fail2ban-style guard10 failures in 15 min blocks the IP for 15 min
No-code-pending gateReturns 404 when no code is active

With a 32-character alphabet, 8-character codes, 5-minute expiry, and 5 attempts/minute — brute force isn’t happening.

”Authentication failed: the daemon rejected the bearer token”

Section titled “”Authentication failed: the daemon rejected the bearer token””

The token was revoked or the daemon’s device store was reset. Re-pair:

Terminal window
netclaw pair <endpoint>

Run netclaw daemon pair on the daemon host to get a fresh code.

Codes last 5 minutes. Generate a new one with netclaw daemon pair and try again.

A device with that name is already paired. Either pick a different name during pairing, or revoke the existing one first:

Terminal window
netclaw daemon devices revoke <name>

Check the basics:

  1. Is the daemon running? (netclaw daemon start on the host)
  2. Is the exposure mode set to something other than local?
  3. Can you reach the endpoint from the remote machine? (curl http://my-server:5199/api/health/ready)
  4. Is a firewall blocking port 5199?

Run netclaw doctor on the daemon host — it includes exposure-mode health checks.

Wait 15 minutes, or fix the issue from a different IP. The fail2ban guard auto-expires after 15 minutes.

  • netclaw chat — start talking to your agent from the paired device
  • netclaw status — check daemon connectivity and endpoint URL
  • netclaw doctor — diagnose exposure mode and connectivity issues
  • Exposure Modes — all Daemon.ExposureMode options and tunnel setup
  • netclaw init — the setup wizard handles bootstrap pairing during non-local setup