OpenNHP/opennhp · error

relay: server # missing publicKeyBase64

Error message

relay: server #%d missing publicKeyBase64

What it means

normalize validates every [[Servers]] entry and requires each server's publicKeyBase64; this error names the zero-based index of the offending server. The public key identifies the upstream NHP server for fingerprint dedup and crypto, so an empty one makes the server entry unusable.

Solutions

  1. Set publicKeyBase64 in the Nth [[Servers]] block to the base64 public key of that NHP server (from the server's keygen output or its config)
  2. Check for typos — the field must be exactly publicKeyBase64
  3. If rendering from templates, verify the server public key secret is present at deploy time

Example fix

// before (config.toml)
[[Servers]]
[[Servers.Instances]]
host = "10.0.0.5"
port = 10161
// after
[[Servers]]
pubKeyBase64 = "<server public key>"
[[Servers.Instances]]
host = "10.0.0.5"
port = 10161
Defensive patterns

Strategy: validation

Validate before calling

data, _ := toml.ParseFile(path)
servers, ok := data.Get("Servers").([]toml.Primitive) // or iterate array of tables
for i := range serversArray {
	if serversArray[i].PubKeyBase64 == "" {
		return fmt.Errorf("server #%d missing publicKeyBase64", i)
	}
}

Type guard

func serverKeyPresent(s relay.Server) bool { return s.PubKeyBase64 != "" }

Try / catch

if err := cfg.Normalize(); err != nil {
	if strings.Contains(err.Error(), "missing publicKeyBase64") {
		return fmt.Errorf("fill in each server's public key: %w", err)
	}
	return err
}

Prevention

When it happens

Trigger: normalize iterates cfg.Servers and hits a c.PubKeyBase64 == "" entry — a [[Servers]] block exists in config.toml but omits publicKeyBase64, misspells it (e.g. publicKey), or leaves it empty/commented.

Common situations: Copied a [[Servers]] block template and forgot to fill in the server's public key; key name drift after a config schema change; template rendering with a missing secret producing an empty string; deleted the key while rotating credentials.

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


AI-assisted analysis of OpenNHP/opennhp@6e04ca5ff0 (2026-09-07). Data as JSON: /api/errors/0e53aa571700eba3. Report an issue: GitHub.

Appendix: source

Thrown at endpoints/relay/config.go:229

	seenFP := make(map[string]int, len(cfg.Servers))
	// seenAddr catches a server+instance pair duplicated under the SAME
	// pubkey — the "operator copied a [[Servers]] block and forgot to
	// change the instance" mistake. The dedupe key is (fingerprint, addr),
	// NOT addr alone: resolveTarget routes by PeerPk, so two DISTINCT
	// pubkeys legitimately sharing one host:port (a SNI/header-routed
	// 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)
			}

View on GitHub (pinned to 6e04ca5ff0)