OpenNHP/opennhp · error
relay: server # and # share the same publicKeyBase64…
Error message
relay: server #%d and #%d share the same publicKeyBase64 (fingerprint %s)
What it means
normalize fingerprints each server's public key and rejects configs where two [[Servers]] entries share the same fingerprint — a server identity can only be declared once. The error reports both duplicate indices and the shared fingerprint to locate the copy-paste.
Solutions
- Merge duplicate blocks: keep one [[Servers]] and put all its endpoints in multiple [[Servers.Instances]] entries under it
- If the servers are genuinely distinct, generate/obtain the correct distinct public key for the second entry
- Run a quick check like `grep publicKeyBase64 config.toml | sort | uniq -d` to find the duplicated key before deploying
Example fix
// before [[Servers]] pubKeyBase64 = "KEY_A" [[Servers.Instances]] host = "10.0.0.5" port = 10161 [[Servers]] pubKeyBase64 = "KEY_A" [[Servers.Instances]] host = "10.0.0.6" port = 10161 // after [[Servers]] pubKeyBase64 = "KEY_A" [[Servers.Instances]] host = "10.0.0.5" port = 10161 [[Servers.Instances]] host = "10.0.0.6" port = 10161
Defensive patterns
Strategy: validation
Validate before calling
seen := map[string]bool{}
for _, s := range cfg.Servers {
if seen[s.PubKeyBase64] {
return errors.New("duplicate server publicKeyBase64 in config")
}
seen[s.PubKeyBase64] = true
} Try / catch
if err := cfg.Normalize(); err != nil {
if strings.Contains(err.Error(), "share the same publicKeyBase64") {
return fmt.Errorf("merge duplicate [[Servers]] blocks into multiple instances: %w", err)
}
return err
} Prevention
- To add endpoints for the same server, add [[Servers.Instances]], not another [[Servers]]
- Run `grep publicKeyBase64 config.toml | sort | uniq -d` in CI
- Deduplicate keys when merging config fragments
When it happens
Trigger: Two [[Servers]] blocks in config.toml carry identical publicKeyBase64 values, so the second one's fingerprint is already in seenFP when normalize iterates it (via LoadConfig at startup or direct test invocation).
Common situations: Copying a [[Servers]] block to add another instance of the same server instead of adding a second [[Servers.Instances]] under the existing block; merging configs that both contain the same server; accidentally reusing one server's key for multiple entries.
Understand the failure class
Background: Conflicting config options: "cannot be used together" — configuration validation errors across open-source libraries — this error's family across 162 libraries.
Related errors
- relay: server # missing publicKeyBase64
- relay: server # publicKeyBase64 invalid
- relay: server # instance # address already claimed by…
- relay: failed to parse config
- relay: privateKeyBase64 must be set in config
AI-assisted analysis of OpenNHP/opennhp@6e04ca5ff0 (2026-09-07).
Data as JSON: /api/errors/d5cd8c6228bc1807.
Report an issue: GitHub.
Appendix: source
Thrown at endpoints/relay/config.go:236
// 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)
}
if inst.Port <= 0 {
return fmt.Errorf("relay: server #%d instance #%d missing or invalid port", i, j)
}
addr := fmt.Sprintf("%s:%d", inst.Host, inst.Port)
// Scope to this server's pubkey: same identity reusing an
// address is the copy-paste error we reject; a sibling
// identity on the same address is allowed (see seenAddr docs).View on GitHub (pinned to 6e04ca5ff0)