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
- 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
- Check the field is spelled privateKeyBase64 and not commented out in config.toml
- 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
- Always run the daemon's keygen step as part of provisioning
- Render configs from secrets templates and fail the deploy if any secret is empty
- Lint configs with a script asserting required top-level keys are non-empty
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
- relay: no upstream configured; add at least one [[Servers]]…
- relay: server # missing publicKeyBase64
- relay: server # (fingerprint ) has no [[Servers.Instances]]
- 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/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)