OpenNHP/opennhp · error
: [[Servers]][ ] missing PubKeyBase64
Error message
%s: [[Servers]][%d] missing PubKeyBase64
What it means
Normalize requires every [[Servers]] cluster entry to carry PubKeyBase64 — the server's base64 public key used to build peer tables and encrypt traffic. An entry without a key is unusable and rejected with its index.
Solutions
- Set PubKeyBase64 to the target server's base64 public key (from its keygen output)
- Ensure the templating/secret pipeline actually injects the key (check for empty env var / missing secret)
- Regenerate and redeploy the key pair if the server's public key changed
- Validate the TOML after rendering so empty key fields fail the deploy before Normalize does
Example fix
// before (server.toml) [[Servers]] Name = "nhp-server-1" // after [[Servers]] Name = "nhp-server-1" PubKeyBase64 = "<base64 public key>"
Defensive patterns
Strategy: validation
Validate before calling
for i, c := range clusters {
if c.PubKeyBase64 == "" {
return fmt.Errorf("cluster %d (%s) has empty PubKeyBase64 — check secret injection", i, c.Name)
}
}
err := clusterconfig.Normalize(clusters, opts) Try / catch
if err := clusterconfig.Normalize(clusters, opts); err != nil {
if strings.Contains(err.Error(), "missing PubKeyBase64") {
return fmt.Errorf("server public key missing — did generate-nhp-keys.sh run? %w", err)
}
return err
} Prevention
- Run scripts/generate-nhp-keys.sh before rendering configs so keys exist
- Fail template rendering if ${SERVER_PUB_KEY} substitutes to an empty string
- Render-time check: reject configs where any PubKeyBase64 is blank
When it happens
Trigger: Calling Normalize with clusters[i].PubKeyBase64 == "", usually from a TOML [[Servers]] block that omits the PubKeyBase64 key or sets it to an empty string.
Common situations: Hand-written server.toml missing the key line; a template variable like ${SERVER_PUB_KEY} left unsubstituted/empty because the secret was absent; copying a cluster block as a template placeholder and not filling it in.
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
- private key parse error
- failed to create device
- server peer config invalid on initial load
- config load error
- relay: failed to parse config
AI-assisted analysis of OpenNHP/opennhp@6e04ca5ff0 (2026-09-07).
Data as JSON: /api/errors/f9a7a2b3f7424e4a.
Report an issue: GitHub.
Appendix: source
Thrown at nhp/common/clusterconfig/clusterconfig.go:147
// invocations for assertions. A nil deprecate is treated as no-op.
func Normalize(clusters []*ClusterConfig, opts Options, deprecate func(string, ...any)) error {
if deprecate == nil {
deprecate = func(string, ...any) {}
}
label := opts.ConsumerLabel
if label == "" {
label = "cluster"
}
if len(clusters) == 0 {
return fmt.Errorf("%s: no [[Servers]] configured", label)
}
for i, c := range clusters {
if c == nil {
return fmt.Errorf("%s: [[Servers]][%d] is nil", label, i)
}
if c.PubKeyBase64 == "" {
return fmt.Errorf("%s: [[Servers]][%d] missing PubKeyBase64", label, i)
}
if opts.RequireName {
if c.Name == "" {
return fmt.Errorf("%s: [[Servers]][%d] (%s) missing Name — clusters are referenced from resource.toml by Name",
label, i, c.PubKeyBase64)
}
if len(c.Name) > NameMaxLen {
return fmt.Errorf("%s: [[Servers]][%d] Name %q exceeds %d chars",
label, i, c.Name, NameMaxLen)
}
if !clusterNameRegex.MatchString(c.Name) {
return fmt.Errorf("%s: [[Servers]][%d] Name %q invalid — allowed chars: [a-zA-Z0-9._-]",
label, i, c.Name)
}
}
legacy := c.hasLegacyFields()
hasInstances := len(c.Instances) > 0View on GitHub (pinned to 6e04ca5ff0)