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

  1. Merge duplicate blocks: keep one [[Servers]] and put all its endpoints in multiple [[Servers.Instances]] entries under it
  2. If the servers are genuinely distinct, generate/obtain the correct distinct public key for the second entry
  3. 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

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


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)