OpenNHP/opennhp · error
relay: server # missing publicKeyBase64
Error message
relay: server #%d missing publicKeyBase64
What it means
normalize validates every [[Servers]] entry and requires each server's publicKeyBase64; this error names the zero-based index of the offending server. The public key identifies the upstream NHP server for fingerprint dedup and crypto, so an empty one makes the server entry unusable.
Solutions
- Set publicKeyBase64 in the Nth [[Servers]] block to the base64 public key of that NHP server (from the server's keygen output or its config)
- Check for typos — the field must be exactly publicKeyBase64
- If rendering from templates, verify the server public key secret is present at deploy time
Example fix
// before (config.toml) [[Servers]] [[Servers.Instances]] host = "10.0.0.5" port = 10161 // after [[Servers]] pubKeyBase64 = "<server public key>" [[Servers.Instances]] host = "10.0.0.5" port = 10161
Defensive patterns
Strategy: validation
Validate before calling
data, _ := toml.ParseFile(path)
servers, ok := data.Get("Servers").([]toml.Primitive) // or iterate array of tables
for i := range serversArray {
if serversArray[i].PubKeyBase64 == "" {
return fmt.Errorf("server #%d missing publicKeyBase64", i)
}
} Type guard
func serverKeyPresent(s relay.Server) bool { return s.PubKeyBase64 != "" } Try / catch
if err := cfg.Normalize(); err != nil {
if strings.Contains(err.Error(), "missing publicKeyBase64") {
return fmt.Errorf("fill in each server's public key: %w", err)
}
return err
} Prevention
- Keep a checklist when adding server entries: pubkey, host, port
- Never commit placeholder [[Servers]] blocks; fill keys at render time
- Validate the full config in CI with normalize before shipping
When it happens
Trigger: normalize iterates cfg.Servers and hits a c.PubKeyBase64 == "" entry — a [[Servers]] block exists in config.toml but omits publicKeyBase64, misspells it (e.g. publicKey), or leaves it empty/commented.
Common situations: Copied a [[Servers]] block template and forgot to fill in the server's public key; key name drift after a config schema change; template rendering with a missing secret producing an empty string; deleted the key while rotating credentials.
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
- relay: privateKeyBase64 must be set in config
- relay: no upstream configured; add at least one [[Servers]]…
- relay: server # publicKeyBase64 invalid
- relay: server # and # share the same publicKeyBase64…
- relay: server # (fingerprint ) has no [[Servers.Instances]]
AI-assisted analysis of OpenNHP/opennhp@6e04ca5ff0 (2026-09-07).
Data as JSON: /api/errors/0e53aa571700eba3.
Report an issue: GitHub.
Appendix: source
Thrown at endpoints/relay/config.go:229
seenFP := make(map[string]int, len(cfg.Servers))
// seenAddr catches a server+instance pair duplicated under the SAME
// pubkey — the "operator copied a [[Servers]] block and forgot to
// change the instance" mistake. The dedupe key is (fingerprint, addr),
// NOT addr alone: resolveTarget routes by PeerPk, so two DISTINCT
// pubkeys legitimately sharing one host:port (a SNI/header-routed
// front-end, or port-multiplexed identities) is a valid topology and
// must not be a hard config-load failure. Only same-pubkey + same-addr
// is the unambiguous copy-paste error.
type addrOrigin struct {
server int
instance int
}
seenAddr := make(map[string]addrOrigin)
for i := range cfg.Servers {
c := &cfg.Servers[i]
if c.PubKeyBase64 == "" {
return fmt.Errorf("relay: server #%d missing publicKeyBase64", i)
}
fp, err := utils.PubKeyFingerprintFromBase64(c.PubKeyBase64)
if err != nil {
return fmt.Errorf("relay: server #%d publicKeyBase64 invalid: %w", i, err)
}
if dup, ok := seenFP[fp]; ok {
return fmt.Errorf("relay: server #%d and #%d share the same publicKeyBase64 (fingerprint %s)", dup, i, fp)
}
seenFP[fp] = i
if len(c.Instances) == 0 {
return fmt.Errorf("relay: server #%d (fingerprint %s) has no [[Servers.Instances]]", i, fp)
}
for j := range c.Instances {
inst := &c.Instances[j]
if inst.Host == "" {
return fmt.Errorf("relay: server #%d instance #%d missing host", i, j)
}View on GitHub (pinned to 6e04ca5ff0)