OpenNHP/opennhp · error
relay: server # (fingerprint ) has no [[Servers.Instances]]
Error message
relay: server #%d (fingerprint %s) has no [[Servers.Instances]]
What it means
Each [[Servers]] entry must contain at least one [[Servers.Instances]] endpoint; normalize rejects a server with an empty Instances list, reporting the server index and its fingerprint. The relay needs concrete host:port endpoints to forward traffic to.
Solutions
- Add at least one [[Servers.Instances]] block with host and port after the corresponding [[Servers]] block
- Check the header is exactly [[Servers.Instances]] and appears after its parent [[Servers]] entry
- Verify with a TOML parser that Instances is non-empty for each server entry
Example fix
// before [[Servers]] pubKeyBase64 = "KEY_A" // after [[Servers]] pubKeyBase64 = "KEY_A" [[Servers.Instances]] host = "10.0.0.5" port = 10161
Defensive patterns
Strategy: validation
Validate before calling
for i, s := range cfg.Servers {
if len(s.Instances) == 0 {
return fmt.Errorf("server #%d has no instances", i)
}
} Type guard
func hasInstances(s relay.Server) bool { return len(s.Instances) > 0 } Try / catch
if err := cfg.Normalize(); err != nil {
if strings.Contains(err.Error(), "has no [[Servers.Instances]]") {
return errors.New("each [[Servers]] needs at least one [[Servers.Instances]] block")
}
return err
} Prevention
- Use the exact header [[Servers.Instances]] immediately after its [[Servers]] block
- Validate the parsed TOML tree in CI to confirm instances attach to the right server
- Never commit [[Servers]] blocks without an instance entry
When it happens
Trigger: A [[Servers]] block with publicKeyBase64 set is followed by no [[Servers.Instances]] sub-table (or it is misspelled, e.g. [[Server.Instances]]), so c.Instances has length 0 during normalize.
Common situations: Operator wrote [[Server.Instances]] or [Servers.Instances] (wrong bracket count / placement) so TOML does not attach it to the right server; deleted the only instance during cleanup; template rendering dropped the instance section; ordering mistake placing [[Servers.Instances]] before any [[Servers]].
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 # missing publicKeyBase64
- relay: server # instance # missing host
- relay: server # instance # missing or invalid port
AI-assisted analysis of OpenNHP/opennhp@6e04ca5ff0 (2026-09-07).
Data as JSON: /api/errors/c3f06bcafd9428a1.
Report an issue: GitHub.
Appendix: source
Thrown at endpoints/relay/config.go:241
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).
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)
}View on GitHub (pinned to 6e04ca5ff0)