OpenNHP/opennhp · error

relay: privateKeyBase64 must be set in config

Error message

relay: privateKeyBase64 must be set in config

What it means

Config.normalize validates the relay configuration and migrates legacy fields; this error is returned when PrivateKeyBase64 is empty. The relay needs its own private key to participate in the NHP protocol, so a config without it is unusable and startup is aborted.

Solutions

  1. Generate keys with the daemon's keygen command (e.g. ./nhp-relayd keygen --curve) and paste the base64 private key into privateKeyBase64 in config.toml
  2. Check the field is spelled privateKeyBase64 and not commented out in config.toml
  3. If rendering configs from templates, verify the source secret/env var is populated before deployment

Example fix

// before (config.toml)
# privateKeyBase64 = ""
// after
privateKeyBase64 = "<base64 key from keygen>"
Defensive patterns

Strategy: validation

Validate before calling

data, err := toml.ParseFile(path)
if err != nil { return err }
if v, ok := data.Get("privateKeyBase64").(string); !ok || v == "" {
	return errors.New("relay config: privateKeyBase64 missing or empty")
}

Type guard

func hasPrivateKey(cfg *relay.Config) bool { return cfg != nil && cfg.PrivateKeyBase64 != "" }

Try / catch

if err := cfg.Normalize(); err != nil {
	if strings.Contains(err.Error(), "privateKeyBase64 must be set") {
		return fmt.Errorf("run keygen and set privateKeyBase64 in config: %w", err)
	}
	return err
}

Prevention

When it happens

Trigger: normalize (called from LoadConfig at startup, or directly in unit tests on a hand-built Config) encounters cfg.PrivateKeyBase64 == "" — the key was never written to config.toml, is commented out, or the key name is misspelled so TOML leaves the field at its zero value.

Common situations: Fresh deployment where the operator skipped the keygen step; copying an example config without filling in privateKeyBase64; renaming the key in config.toml (e.g. privateKey) so it no longer maps to the struct field; an empty value produced by a config-rendering template with a missing env var.

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/921d57f55df3268c. Report an issue: GitHub.

Appendix: source

Thrown at endpoints/relay/config.go:178

	if err := toml.Unmarshal(data, cfg); err != nil {
		return nil, fmt.Errorf("relay: failed to parse config %s: %w", path, err)
	}

	if err := cfg.normalize(); err != nil {
		return nil, err
	}

	log.Info("[Relay] loaded config from %s with %d server(s)", path, len(cfg.Servers))
	return cfg, nil
}

// normalize validates the configuration and applies legacy-field migration so
// that the rest of the relay only has to look at Config.Servers. It is
// exported as a method (not a function) to make it directly testable on a
// hand-built Config in unit tests without round-tripping through TOML.
func (cfg *Config) normalize() error {
	if cfg.PrivateKeyBase64 == "" {
		return fmt.Errorf("relay: privateKeyBase64 must be set in config")
	}

	hasLegacy := cfg.NHPServerHost != "" ||
		cfg.NHPServerPort != 0 ||
		cfg.NHPServerPublicKeyBase64 != ""

	switch {
	case hasLegacy && len(cfg.Servers) == 0:
		// Auto-migrate: promote the legacy fields into a single server
		// so existing demo configs keep working through phase 1.
		log.Warning("[Relay] nhpServerHost/nhpServerPort/nhpServerPublicKeyBase64 are deprecated; " +
			"migrate to [[Servers]] / [[Servers.Instances]] in config.toml")
		cfg.Servers = []Server{{
			PubKeyBase64: cfg.NHPServerPublicKeyBase64,
			Instances: []ServerInstance{{
				Host: cfg.NHPServerHost,
				Port: cfg.NHPServerPort,
			}},

View on GitHub (pinned to 6e04ca5ff0)