OpenNHP/opennhp · critical

server peer config invalid on initial load

Error message

server peer config invalid on initial load: %w

What it means

UdpAC.Start returns "server peer config invalid on initial load: %w" when loadPeers() fails while loading the server peer table for the first time at startup. Unlike reloads (which keep the previous good table), an empty initial table would make the AC silently drop AOP/ART traffic, so startup is refused deliberately.

Solutions

  1. Read the wrapped cause (loadErr) in the log to see the exact parse/validation failure
  2. Validate server.toml parses as TOML and contains at least one peer with a valid base64 public key before starting
  3. Re-run the deploy config rendering step (deploy/config-templates via envsubst, scripts/generate-nhp-keys.sh) to produce a complete server.toml
  4. Mount/copy the rendered file to the path the watcher expects and restart nhp-ac

Example fix

// before (server.toml missing peer key)
[[serverPeer]]
name = "nhp-server-1"
instanceUrlList = ["udp://10.0.0.5:5555"]
// after
[[serverPeer]]
name = "nhp-server-1"
publicKeyBase64 = "<base64 curve key from nhp_server_public_key>"
instanceUrlList = ["udp://10.0.0.5:5555"]
Defensive patterns

Strategy: validation

Validate before calling

// preflight before starting AC
var tbl map[string]any
if _, err := toml.DecodeFile("server.toml", &tbl); err != nil {
	return fmt.Errorf("server.toml unreadable: %w", err)
}
peers, _ := tbl["serverPeer"].([]map[string]any)
if len(peers) == 0 { return fmt.Errorf("server.toml has no [[serverPeer]] entries") }

Try / catch

if loadErr := a.loadPeers(); loadErr != nil {
	return fmt.Errorf("server peer config invalid on initial load: %w", loadErr)
} // fix the wrapped cause in server.toml, then restart

Prevention

When it happens

Trigger: server.toml (or etcd peer config) is missing, unparseable TOML, has no valid server peer entries, or peer public keys fail base64/validation at first boot; the wrapped loadErr from loadPeers is included via %w.

Common situations: Fresh deployment where server.toml was never rendered (envsubst template failure); malformed TOML after manual edit; peer key fields empty because key generation step in the deploy pipeline did not run.

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/5c05c41aee169145. Report an issue: GitHub.

Appendix: source

Thrown at endpoints/ac/udpac.go:163

	a.remoteConnectionMap = make(map[string]*UdpConn)
	a.serverPeerMap = make(map[string]*core.UdpPeer)
	a.tokenStore = common.NewTokenStore[*AccessEntry]()

	if a.etcdConn != nil {
		_ = a.loadRemoteConfig()
	} else {
		// load http config and turn on http server if needed
		_ = a.loadHttpConfig()

		// load peers. A non-nil error here means the initial
		// expandServerPeers parse failed and the running peerMap is
		// empty. Starting the daemon in that state lets it drop
		// AOL/AOP traffic silently (no peer matches), which is much
		// harder to diagnose than a startup refusal. Reloads still
		// keep the previous peer table on parse error — that's the
		// right call once a good table is live.
		if loadErr := a.loadPeers(); loadErr != nil {
			return fmt.Errorf("server peer config invalid on initial load: %w", loadErr)
		}
	}

	if a.config.FilterMode == FilterMode_EBPFXDP {
		// Snapshot the expanded peer list under serverPeerMutex so we
		// don't race the file/etcd watcher path that rewrites
		// a.config.Servers on reload (see config.go: updateServerPeers).
		// Keep the lock window minimal — copy the slice header, release,
		// then issue eBPF syscalls outside the lock so a slow kernel
		// syscall can't block a pending reload.
		a.serverPeerMutex.Lock()
		servers := make([]*core.UdpPeer, len(a.config.Servers))
		copy(servers, a.config.Servers)
		a.serverPeerMutex.Unlock()
		for _, server := range servers {
			// XDP rules key on the source IP; instances configured
			// with a DNS-only Host (Ip == "" — legitimate under the
			// new schema, see clusterconfig.Normalize) have nothing

View on GitHub (pinned to 6e04ca5ff0)