OpenNHP/opennhp · error
cluster : missing publicKeyBase64
Error message
cluster %q: missing publicKeyBase64
What it means
buildCluster in endpoints/agent/cluster.go rejects a ClusterConfig whose PubKeyBase64 is empty with "cluster %q: missing publicKeyBase64". Each server cluster needs the shared server public key to encrypt knock packets; without it the cluster cannot be constructed. Called from updateServerPeers during config load/reload.
Solutions
- Add the server's base64 public key to every cluster entry in the agent config
- Re-run key generation/deploy rendering (scripts/generate-nhp-keys.sh) if the key field came up empty from a template
- Add startup validation of cluster configs before use, listing which cluster is missing the key
- Check for field-name typos (publicKeyBase64 vs pubkey) against the ClusterConfig struct
Example fix
// before [[clusters]] name = "nhp-server" instances = ["udp://10.0.0.5:5555"] // after [[clusters]] name = "nhp-server" publicKeyBase64 = "<nhp_server_public_key>" instances = ["udp://10.0.0.5:5555"]
Defensive patterns
Strategy: validation
Validate before calling
for _, c := range clusterConfigs {
if c.PubKeyBase64 == "" {
return fmt.Errorf("cluster %q: publicKeyBase64 required", c.Name)
}
if _, err := base64.StdEncoding.DecodeString(c.PubKeyBase64); err != nil { return err }
} Try / catch
cl, err := buildCluster(cfg)
if err != nil {
return fmt.Errorf("skipping cluster: %w", err) // or fail startup
} Prevention
- Render cluster configs from templates so keys are never blank
- Decode base64 fields at parse time to catch empties early
- Keep field names in templates in sync with ClusterConfig struct tags
When it happens
Trigger: A clusters entry in agent config omits publicKeyBase64 (or names a different field, leaving PubKeyBase64 unset) when updateServerPeers parses and builds clusters at startup or on config reload.
Common situations: Hand-writing clusters in agent.toml/config.json and forgetting the key; template rendering with an unset env var (e.g. nhp_server_public_key missing from secrets) leaving the field blank; renaming fields across versions.
Understand the failure class
Background: "is required", "must be set", "missing required field": configuration validation errors across open-source libraries — this error's family across 36 libraries.
Related errors
- cluster ( ): no instances configured
- cluster ( )
- cluster instance # : must set either Host or Ip
- cluster instance # : invalid port
- no private key configured; check etc/config.toml
AI-assisted analysis of OpenNHP/opennhp@6e04ca5ff0 (2026-09-07).
Data as JSON: /api/errors/9051091a3a1d2c42.
Report an issue: GitHub.
Appendix: source
Thrown at endpoints/agent/cluster.go:133
func (sc *ServerCluster) FindInstanceByAddr(addr string) *ServerInstance {
if sc == nil {
return nil
}
for _, inst := range sc.instances {
if inst.hostPort == addr {
return inst
}
}
return nil
}
// buildCluster turns a parsed ClusterConfig into a runtime cluster.
// The returned cluster's representativePeer is NOT yet registered on a
// device — callers (updateServerPeers) are responsible for that, so
// they can also handle peer removal on reload.
func buildCluster(cfg *ClusterConfig) (*ServerCluster, error) {
if cfg.PubKeyBase64 == "" {
return nil, fmt.Errorf("cluster %q: missing publicKeyBase64", cfg.Name)
}
if len(cfg.Instances) == 0 {
return nil, fmt.Errorf("cluster %q (%s): no instances configured",
cfg.Name, cfg.PubKeyBase64)
}
if err := cfg.LoadBalance.Validate(); err != nil {
return nil, fmt.Errorf("cluster %q (%s): %w",
cfg.Name, cfg.PubKeyBase64, err)
}
sc := &ServerCluster{
PublicKeyBase64: cfg.PubKeyBase64,
Name: cfg.Name,
Sticky: cfg.StickyOrDefault(),
instances: make([]*ServerInstance, 0, len(cfg.Instances)),
}
for i, ic := range cfg.Instances {View on GitHub (pinned to 6e04ca5ff0)