OpenNHP/opennhp · error
cluster ( ): no instances configured
Error message
cluster %q (%s): no instances configured
What it means
buildCluster returns "cluster %q (%s): no instances configured" when a ClusterConfig has a valid public key but zero instances. A cluster with no endpoints has nothing to knock against, so it is rejected rather than producing a cluster that always fails at knock time.
Solutions
- Add at least one reachable server instance URL (udp://host:port) to the cluster's instances list
- Check the rendered config for an empty instances value caused by an unset template variable
- Verify the instances field name spelling matches the parser so entries are not silently dropped
- Fail fast in config loading with the cluster name, which this error already provides — fix the offending cluster block
Example fix
// before [[clusters]] name = "nhp-server" publicKeyBase64 = "abc..." // after [[clusters]] name = "nhp-server" publicKeyBase64 = "abc..." instances = ["udp://nhp1.example.com:5555", "udp://nhp2.example.com:5555"]
Defensive patterns
Strategy: validation
Validate before calling
if len(cfg.Instances) == 0 {
return fmt.Errorf("cluster %q: at least one instance URL required", cfg.Name)
}
for _, u := range cfg.Instances {
if _, err := url.Parse(u); err != nil { return err }
} Try / catch
cl, err := buildCluster(cfg)
if err != nil {
return fmt.Errorf("cluster %s rejected: %w", cfg.Name, err)
} Prevention
- Never template an empty instances list; fail the render if the host list is empty
- Validate instance URL syntax at parse time
- Lint agent config files in CI before deployment
When it happens
Trigger: A clusters entry defines name and publicKeyBase64 but an empty/missing instances list when updateServerPeers builds clusters; an instances array that parsed to zero elements (empty array, or all entries filtered out as invalid URLs earlier in parsing).
Common situations: Copy-pasting a cluster block and forgetting the instances array; provisioning template rendering instances = [] because a host list variable was empty; typo in the instances key so it lands in an unknown field.
Understand the failure class
Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.
Related errors
- cluster : missing publicKeyBase64
- cluster ( )
- cluster instance # : must set either Host or Ip
- cluster instance # : invalid port
- no private key configured; check etc/config.toml
AI-assisted analysis of OpenNHP/opennhp@6e04ca5ff0 (2026-09-07).
Data as JSON: /api/errors/c61c2ad1255f512c.
Report an issue: GitHub.
Appendix: source
Thrown at endpoints/agent/cluster.go:136
}
for _, inst := range sc.instances {
if inst.hostPort == addr {
return inst
}
}
return nil
}
// buildCluster turns a parsed ClusterConfig into a runtime cluster.
// The returned cluster's representativePeer is NOT yet registered on a
// device — callers (updateServerPeers) are responsible for that, so
// they can also handle peer removal on reload.
func buildCluster(cfg *ClusterConfig) (*ServerCluster, error) {
if cfg.PubKeyBase64 == "" {
return nil, fmt.Errorf("cluster %q: missing publicKeyBase64", cfg.Name)
}
if len(cfg.Instances) == 0 {
return nil, fmt.Errorf("cluster %q (%s): no instances configured",
cfg.Name, cfg.PubKeyBase64)
}
if err := cfg.LoadBalance.Validate(); err != nil {
return nil, fmt.Errorf("cluster %q (%s): %w",
cfg.Name, cfg.PubKeyBase64, err)
}
sc := &ServerCluster{
PublicKeyBase64: cfg.PubKeyBase64,
Name: cfg.Name,
Sticky: cfg.StickyOrDefault(),
instances: make([]*ServerInstance, 0, len(cfg.Instances)),
}
for i, ic := range cfg.Instances {
host := ic.Host
ip := ic.Ip
if host == "" && ip == "" {View on GitHub (pinned to 6e04ca5ff0)