OpenNHP/opennhp · error

peer not found in peer pool

Error message

peer not found in peer pool (type=%s, pubkey=%s)

What it means

validatePeer successfully decrypted the initiator's static public key from the header, but a lookup of that key in the device's peer pool found no registered peer (and no PeerLookupFallback accepted it). The device refuses to process messages from unknown public keys — this is the core Zero Trust allowlist behavior. The error carries the expected peer device type (derived from the message type, e.g. agent for KNK, AC for ART) and the base64 public key.

Solutions

  1. Add the sender's public key to the receiver's peer table (server.toml/resource.toml peer entries) and redeploy configs from deploy/config-templates.
  2. If keys were rotated (--regenerate), redeploy peer tables to ALL nodes in lockstep.
  3. For agents on the AC/DB, wire option.PeerLookupFallback so dynamically registered (NHP_REG) agents are accepted.
  4. Confirm the agent actually completed registration (NHP_REG/NHP_OTP) before sending operational messages.
  5. Check the logged pubkey against the sender's actual key file — a stale agent key file is the usual mismatch.

Example fix

// before (ac etc/server.toml) — peer missing
[[peer]]
# only ac and relay listed, agent key absent
// after
[[peer]]
deviceType = 1 # NHP_AGENT
pubKey = "<base64 of nhp_agent_public_key>"
Defensive patterns

Strategy: fallback

Validate before calling

pk, _ := base64.StdEncoding.DecodeString(agentPubKeyB64)
if dev.LookupPeer(pk) == nil && dev.Option.PeerLookupFallback == nil {
	return fmt.Errorf("peer %s is not registered on this node; register or add to peer table first", agentPubKeyB64)
}

Type guard

func peerKnown(dev *core.Device, pk []byte) bool {
	return dev.LookupPeer(pk) != nil
}

Try / catch

err := client.SendKnock(server)
if err != nil && strings.Contains(err.Error(), "peer not found in peer pool") {
	// re-register then retry once
	if rerr := client.Register(); rerr == nil {
		err = client.SendKnock(server)
	}
}

Prevention

When it happens

Trigger: An agent sends KNK/ACC without ever having been registered (no NHP_REG/OTP) or after its entry was removed; a server/AC/relay sends a message but its public key is absent from the receiver's peer table (config.toml/server.toml peers not deployed); keys rotated with --regenerate so the pool has old keys; DisableXPeerValidation is false but no PeerLookupFallback is configured on the AC/DB for dynamically registered agents.

Common situations: Deploying only some hosts' peer tables after generate-nhp-keys.sh; registering an agent against server A but knocking server B; SQLite-registered agent missing because PeerLookupFallback wasn't wired; peer deleted by expiry/cleanup on the receiving node.

Understand the failure class

Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.

Related errors


AI-assisted analysis of OpenNHP/opennhp@6e04ca5ff0 (2026-09-07). Data as JSON: /api/errors/f743bc9e7402aa46. Report an issue: GitHub.

Appendix: source

Thrown at nhp/core/responder.go:504

		peerDeviceTypeName := DeviceTypeToString(peerDeviceType)
		log.Debug("validatePeer: looking up %s peer pubkey=%s in peer pool", peerDeviceTypeName, peerPkBase64)

		peer = ppd.device.LookupPeer(peerPk)
		if peer == nil {
			// Fallback: check dynamically-registered peers (e.g., agents
			// registered via NHP-REG stored in SQLite).
			ppd.device.optionMutex.Lock()
			fallback := ppd.device.option.PeerLookupFallback
			ppd.device.optionMutex.Unlock()
			if fallback != nil && fallback(peerPk, ppd.HeaderType) {
				log.Info("validatePeer: %s peer accepted via fallback, pubkey=%s",
					peerDeviceTypeName, peerPkBase64)
				// Skip expiry/address checks for fallback peers.
				goto peerAccepted
			}
			log.Error("validatePeer: %s peer not found in peer pool, pubkey=%s",
				peerDeviceTypeName, peerPkBase64)
			err = fmt.Errorf("peer not found in peer pool (type=%s, pubkey=%s)", peerDeviceTypeName, peerPkBase64)
			return err
		}

		if peer.IsExpired() {
			log.Error("validatePeer: %s peer expired, pubkey=%s", peerDeviceTypeName, peerPkBase64)
			err = fmt.Errorf("peer expired (type=%s, pubkey=%s)", peerDeviceTypeName, peerPkBase64)
			return err
		}

		if !ppd.ConnData.CheckRecvAddress(ppd.LocalInitTime, ppd.ConnData.RemoteAddr) {
			log.Error("validatePeer: %s peer address mismatch on connection, pubkey=%s, remoteAddr=%s",
				peerDeviceTypeName, peerPkBase64, ppd.ConnData.RemoteAddr)
			err = fmt.Errorf("peer does not match its previous address on this connection (type=%s, pubkey=%s)", peerDeviceTypeName, peerPkBase64)
			return err
		}
		ppd.ConnData.UpdateRecvAddress(ppd.LocalInitTime, ppd.ConnData.RemoteAddr)
		peer.UpdateRecv(ppd.LocalInitTime)
	peerAccepted:

View on GitHub (pinned to 6e04ca5ff0)