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
- Read the wrapped cause (loadErr) in the log to see the exact parse/validation failure
- Validate server.toml parses as TOML and contains at least one peer with a valid base64 public key before starting
- Re-run the deploy config rendering step (deploy/config-templates via envsubst, scripts/generate-nhp-keys.sh) to produce a complete server.toml
- 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
- Render server.toml via envsubst templates in CI, never by hand
- Check the wrapped loadErr log line for the exact field at fault
- Add a config preflight check to deployment scripts before launch
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
- config load error
- relay: failed to parse config
- load db peer config err (optional)
- load relay peer config err (optional)
- private key parse error
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 nothingView on GitHub (pinned to 6e04ca5ff0)