netbirdio/netbird · warning
peer already registered
Error message
peer already registered
What it means
Sentinel error from the signal server's peer registry. Registry.Register uses sync.Map LoadOrStore: if a Peer with the same ID already exists and the incoming stream's StreamID is NOT greater than the stored one, the registration is rejected with this error so a stale stream cannot evict a live one. A strictly newer StreamID instead replaces and cancels the old peer.
Source
Thrown at signal/peer/peer.go:17
package peer
import (
"context"
"sync"
"time"
"errors"
log "github.com/sirupsen/logrus"
"github.com/netbirdio/netbird/shared/signal/proto"
"github.com/netbirdio/netbird/signal/metrics"
)
var (
ErrPeerAlreadyRegistered = errors.New("peer already registered")
)
// Peer representation of a connected Peer
type Peer struct {
// a unique id of the Peer (e.g. sha256 fingerprint of the Wireguard public key)
Id string
StreamID int64
// a gRpc connection stream to the Peer
Stream proto.SignalExchange_ConnectStreamServer
// sendMu serializes writes to Stream. gRPC forbids concurrent SendMsg on
// the same ServerStream, and a peer can be the target of many senders at
// once.
sendMu sync.Mutex
// registration time
RegisteredAt time.TimeView on GitHub (pinned to 93e97f4bf1)
Solutions
- Let the previous stream close and deregistration finish before reconnecting.
- On the agent side, treat it as transient: the built-in reconnect loop eventually registers with a higher StreamID.
- Ensure only one agent process runs per WireGuard key.
Defensive patterns
Strategy: try-catch
Type guard
func isPeerAlreadyRegistered(err error) bool {
return errors.Is(err, peer.ErrPeerAlreadyRegistered)
} Try / catch
if err := registry.Register(p); err != nil {
if errors.Is(err, peer.ErrPeerAlreadyRegistered) {
// stale or duplicate stream: do not fail the server; the newer
// registration wins and the client reconnect loop will retry
log.Debugf("stale registration rejected: %v", err)
return nil
}
return err
} Prevention
- Treat this sentinel as an expected reconnect race, not a server fault; never alert on it at high severity.
- Close previous streams before opening a new one for the same peer ID.
- Run only one agent per WireGuard key to avoid permanent stream-stealing.
When it happens
Trigger: A peer reconnects to the signal server while its previous stream is still registered and the new stream carries an equal or older StreamID; two concurrent ConnectStream calls for the same peer fingerprint (sha256 of the WireGuard public key).
Common situations: Reconnect storms after network flaps where the old stream has not been deregistered yet; accidentally running two agents with the same key; test harnesses opening parallel streams with the same peer ID.
Related errors
- peer registered again
- signal receive stream stalled
- client not started
- an earlier read of the policy table has not returned
- sync response persistence is disabled
AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16).
Data as JSON: /api/errors/38391f0a33f82beb.
Report an issue: GitHub.