OpenNHP/opennhp · error
relay: server # instance # address already claimed by…
Error message
relay: server #%d instance #%d address %s already claimed by server #%d instance #%d under the same publicKeyBase64 (fingerprint %s)
What it means
Within a single server identity (same public key fingerprint), each instance address must be unique; normalize rejects an address already claimed by an earlier instance of the same server. Duplicates under one pubkey are treated as the classic copy-paste mistake, while different server identities on the same address are intentionally allowed. The error reports both conflicting server/instance indices and the address.
Solutions
- Change the duplicate instance's host and/or port to the actual distinct upstream endpoint, or delete the redundant block
- If multiple entries are meant to load-balance one service, list the real distinct host:port of each backend instance
- Grep the config under the affected [[Servers]] block for repeated host/port pairs to find the duplicate before deploying
Example fix
// before [[Servers.Instances]] host = "10.0.0.5" port = 10161 [[Servers.Instances]] host = "10.0.0.5" port = 10161 // after [[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
type key struct{ fp, addr string }
seen := map[key]bool{}
for _, s := range cfg.Servers {
fp := fingerprint(s.PubKeyBase64)
for _, inst := range s.Instances {
addr := fmt.Sprintf("%s:%d", inst.Host, inst.Port)
k := key{fp, addr}
if seen[k] {
return fmt.Errorf("duplicate instance address %s for same pubkey", addr)
}
seen[k] = true
}
} Try / catch
if err := cfg.Normalize(); err != nil {
if strings.Contains(err.Error(), "already claimed by server") {
return fmt.Errorf("remove or change the duplicated [[Servers.Instances]] entry: %w", err)
}
return err
} Prevention
- When adding instances, change host AND port, not just one
- Generate instance lists programmatically from distinct backend endpoints rather than copy-paste
- Add a CI check that fails on duplicate fp@addr pairs in relay configs
When it happens
Trigger: Two [[Servers.Instances]] blocks under the SAME [[Servers]] pubkey resolve to the same host:port, so the fp+"@"+addr key already exists in seenAddr during normalize (LoadConfig startup or direct test invocation).
Common situations: Copying an [[Servers.Instances]] block to add a second instance and forgetting to change host or port; load-balancing intent expressed by duplicating the same endpoint instead of listing distinct instances; template loops rendering identical 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 # and # share the same publicKeyBase64…
- relay: failed to parse config
- relay: privateKeyBase64 must be set in config
- relay: no upstream configured; add at least one [[Servers]]…
- relay: server # missing publicKeyBase64
AI-assisted analysis of OpenNHP/opennhp@6e04ca5ff0 (2026-09-07).
Data as JSON: /api/errors/8c54d316e4767145.
Report an issue: GitHub.
Appendix: source
Thrown at endpoints/relay/config.go:257
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).
addrKey := fp + "@" + addr
if dup, ok := seenAddr[addrKey]; ok {
return fmt.Errorf("relay: server #%d instance #%d address %s already claimed by server #%d instance #%d under the same publicKeyBase64 (fingerprint %s)",
i, j, addr, dup.server, dup.instance, fp)
}
seenAddr[addrKey] = addrOrigin{server: i, instance: j}
if inst.Weight <= 0 {
inst.Weight = 1
}
}
switch c.LoadBalance {
case "":
c.LoadBalance = LBWeightedRandom
case LBRandom, LBWeightedRandom, LBRoundRobin:
// known scheme, keep as-is
default:
// Typos like "weighted_random" or "roundrobin" are harmless in
// phase 1 (the value is unused with a single instance) but
// would silently degrade phase-2 load balancing to whatever
// the default policy is. Reject at load time so the operator
// hears about it now, not after a later upgrade.View on GitHub (pinned to 6e04ca5ff0)