You delete the member, close the page, and a day later the same device is back. That is not a bug in the console. It is what happens when a record is deleted while the device is still online and still asking to join.

Two operations live next to each other and do different jobs. One decides access, and the other only tidies the list.

Start From the Reappearing Record

Open the network and look at the entry that returned. Note its node ID, its last address and the time it reappeared. Compare that with the device you meant to retire. A display name is whatever somebody typed, and the address can change on the next join.

The node ID is the stable identifier. A network ID names the network, so it tells you which list you are reading and nothing about which device is in it. Record both, then match the node ID against the machine you meant to retire.

Get that comparison wrong and you remove a working machine while the unwanted one keeps asking. The list then looks correct for a day. That is long enough for nobody to notice, which is exactly the problem.

Revoke Before You Delete

Change the authorization state through the controller's supported workflow first. From that point, a join attempt appears as unauthorized instead of quietly restoring access, and the request is visible rather than invisible.

Deletion comes after, never instead. Removing the record of an authorized member leaves nothing to revoke. The device asks again, and the controller treats it as new.

For a machine you still control, leaving the network is a useful part of retirement. It is not a substitute for the administrator decision on a lost device. A client you no longer control can ignore whatever it was told.

Where the Two Operations Live

On a hosted controller, the member list offers both actions and labels them clearly enough to tell apart. On a self-hosted controller, the same distinction appears in the API and in the local configuration, which is where the confusion usually starts.

# list members with authorization state
curl -s -H "X-ZT1-Auth: $TOKEN" \
  "http://127.0.0.1:9993/controller/network/$NETWORK_ID/member" | jq 'to_entries[] | {id: .key, authorized: .value.authorized}'

# deauthorize one member by node id, then confirm
curl -s -X POST -H "X-ZT1-Auth: $TOKEN" -d '{"authorized": false}' \
  "http://127.0.0.1:9993/controller/network/$NETWORK_ID/member/$NODE_ID"

Those two calls are enough to show whether the member was ever actually unauthorized. A record that keeps returning with authorized: true was never revoked; it was only deleted.

Confirm From the Resource, Not the List

A tidy member list is not an access check. Try the resource the device used before, from that device or from a machine holding its old address, and confirm the connection fails. Then confirm an approved replacement still works.

Include routes and service permissions in that test. ZeroTier can authorize a member and still leave it unable to reach a managed route. An application permission can fail in the same way. Both look identical from the member list.

zerotier-cli listnetworks
ip route show | grep zt
ping -c 3 10.144.0.5

For a self-hosted controller, keep a consistent backup of its identity and configuration. A restore that rolls back to an earlier state can bring an old authorization back with it, and the member list will look exactly as it did before the retirement.

Retirement is finished when the old identity cannot reach anything and the record is gone. Do those in that order and the member stops coming back.

ဆက်စပ်ဆောင်းပါးများ