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

  1. Change the duplicate instance's host and/or port to the actual distinct upstream endpoint, or delete the redundant block
  2. If multiple entries are meant to load-balance one service, list the real distinct host:port of each backend instance
  3. 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 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


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)