Remote setup (WSS)
Connect zerocode on your workstation to a daemon running on another machine (Raspberry Pi, home server, VPS, etc.).
For the full knob-by-knob guide covering all three topologies (direct, daemon-to-relay, and client-through-relay), see Secure transport (end-to-end config).
The WSS plane is mutually authenticated (mTLS). Every client presents a certificate; there is no server-only / unauthenticated path. The easy way to get a client certificate is enrollment (below) - you do not hand-manage certs. If you would rather mint one yourself, see the bring-your-own steps in “On your workstation” further down.
--tls-skip-verifyonly relaxes server certificate verification for a self-signed daemon cert; the client certificate is still required either way.
You need two things: a certificate and a bearer token. The client
certificate secures the transport: it proves which device is on the other end
of the TLS connection. It does not say who you are. Every remote connection
must also present a bearer token in the initialize handshake, and that
token is what identifies you (see
Bearer token below). Enrollment
gives you only the certificate. A client with a certificate but no token
completes the TLS handshake and is then refused with AUTH_REQUIRED
(-32010).
Enrollment (recommended)
The first time you connect a certless client interactively, zerocode enrolls automatically:
zerocode --connect wss://<remote-host>:9781
It prompts for the daemon’s one-time pairing code (printed in the daemon’s
log on start), shows a short-auth-string (SAS) to confirm against the daemon
console (so a man-in-the-middle CA is caught), then fetches and caches a client
certificate under <config-dir>/tls. Later runs reuse that certificate
without prompting again, and it auto-renews at ~50% of its lifetime. To enroll
non-interactively use zerocode --enroll --connect wss://<remote-host>:9781.
Enrollment does not give you a bearer token. The enrollment pairing code is separate from the gateway pairing code, and it is used up by enrollment. You still need a token before the daemon accepts a session; see Bearer token.
A certless client that reaches the WSS plane without enrolling gets an actionable “enroll first” message (and the daemon logs the rejected un-migrated client) - never a silent hang. A revoked certificate is refused at the handshake (driven by the issued-cert ledger), so revoking a lost device takes effect on its next connection.
allow_unpaired_enrollment in the [enroll] section is reserved for a future
code-less migration flow. This release rejects any non-empty value at daemon
startup; leave it empty and use the printed pairing code.
On the remote host (daemon side)
-
Enable WSS. Set the
wssconfig through the Config pane (or the gateway /zeroclaw config set):[wss] enabled = trueLeave
cert_path/key_pathempty (the default): the daemon auto-generates its own CA and server certificate under<data_dir>/tls/on first boot, so you do not need to runopensslyourself. Set them only to bring your own server certificate, and use absolute paths either way; the config does not expand~. -
Open the firewall port:
sh
sudo ufw allow 9781/tcpThe default WSS port is 9781. Change it with
port = <number>in the[wss]section. -
Start (or restart) the daemon:
sh
zeroclaw daemonYou should see a log line confirming the WSS listener started on
0.0.0.0:9781.
On your workstation (zerocode side)
Enrollment (above) is the fastest way to get a client certificate. If you would rather bring your own client certificate instead of enrolling interactively:
-
Issue a client certificate on the daemon host, from the daemon’s mTLS CA:
zeroclaw security issue-client-cert --name my-laptop --out-dir /tmp/my-laptop-tlsThis writes
ca.crt,client.crt, andclient.keyto--out-dir. Add--forceto overwrite an existing certificate for that name. -
Copy the three files to the workstation’s
<config-dir>/tls/(thenzerocode --connect wss://<remote-ip>:9781finds them automatically), or point at them explicitly:sh
zerocode --connect wss://<remote-ip>:9781 \ --tls-ca-cert /path/ca.crt \ --tls-client-cert /path/client.crt \ --tls-client-key /path/client.keyBecause
ca.crtis the same CA that signed the daemon’s server certificate, this verifies the server too;--tls-skip-verifyis only needed if you skip--tls-ca-certagainst a self-signed daemon cert you have not pinned.
Either way, the certificate only gets you through TLS. Add a bearer token next.
Bearer token (required for every connection)
The daemon refuses any remote initialize that has no auth_token, even
when the client certificate is valid. Give zerocode a token once, and it
presents the token on every connection and reconnect.
-
Pair with the gateway to get a token. Use the dashboard’s Pairing page, or, on the daemon host (the gateway listens on
127.0.0.1:42617by default), POST the gateway pairing code from the daemon log to/pair:curl -X POST http://127.0.0.1:42617/pair -H 'X-Pairing-Code: <code>'The response carries a
zc_...token. Use the gateway’s pairing code here, not the enrollment code you gave zerocode above. -
Give zerocode the token. The environment variable is the recommended way because it keeps the secret out of the config file:
export ZEROCLAW_AUTH_TOKEN=zc_... zerocode --connect wss://<remote-host>:9781Or set it in zerocode’s config, either as a path to a file that only you can read or inline:
[connection.wss] uri = "wss://<remote-host>:9781" auth_token_file = "/abs/path/zerocode-bearer" # or: auth_token = "zc_..."Precedence is
ZEROCLAW_AUTH_TOKEN, thenauth_token_file, thenauth_token. A token file that other accounts can read is skipped with a warning.
To use an OIDC access token instead of a pairing token, also set
auth_provider = "oidc.<alias>" under [connection.wss]. For file-permission
rules, OIDC, and what the daemon does with the token, see
Authentication & principals.
After that, zerocode --connect wss://<remote-host>:9781 (or plain zerocode
with uri in config) needs no more flags, and zerocode reconnects
automatically if the connection drops.
Config reference
The wss section:
| Field | Default | Description |
|---|---|---|
enabled | false | Enable the WSS listener |
bind | 0.0.0.0 | Bind address |
port | 9781 | Listen port |
cert_path | (none) | Absolute path to PEM certificate |
key_path | (none) | Absolute path to PEM private key |