OpenNHP/opennhp · critical

failed to create device

Error message

failed to create device %v

What it means

After decoding the private key, Start calls core.NewDevice(NHP_AGENT, prk, nil); if that returns nil the agent cannot build its device (ECDH context / device identity) and Start fails. This wraps an internal device-construction failure, typically from invalid key length or cipher/crypto initialization.

Solutions

  1. Regenerate a fresh key pair with keygen --curve and use it
  2. Verify the decoded key is 32 bytes (curve25519)
  3. Check agent/server logs (log.Critical line) and the nhp/core NewDevice code for the exact nil-return condition

Example fix

// before
prk = []byte("shortkey")
// after
prk, _ := base64.StdEncoding.DecodeString("<32-byte base64 key from keygen>")
Defensive patterns

Strategy: try-catch

Validate before calling

prk, err := base64.StdEncoding.DecodeString(cfg.PrivateKeyBase64)
if err != nil || len(prk) != 32 {
    return fmt.Errorf("agent key must decode to 32 bytes, got %d", len(prk))
}

Try / catch

if err := agent.Start(); err != nil {
    if strings.Contains(err.Error(), "failed to create device") {
        log.Fatalf("device init failed (%v); regenerate key with keygen --curve", err)
    }
}

Prevention

When it happens

Trigger: Start with a private key that decodes from base64 but has an invalid byte length or otherwise fails ECDH initialization inside core.NewDevice.

Common situations: Truncated key from a partial copy-paste; key generated for a different cipher scheme with wrong byte size; corrupted key file.

Related errors


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

Appendix: source

Thrown at endpoints/agent/udpagent.go:399

		// real misconfiguration — fail loudly instead of silently starting
		// with a random key and empty identity.
		if !a.allowMissingConfig {
			log.Error("no private key configured in etc/config.toml")
			return fmt.Errorf("no private key configured; check etc/config.toml")
		}
		prk = core.NewECDH(core.ECC_CURVE25519).PrivateKey()
	} else {
		prk, err = base64.StdEncoding.DecodeString(a.config.PrivateKeyBase64)
		if err != nil {
			log.Error("private key parse error %v\n", err)
			return fmt.Errorf("private key parse error %v", err)
		}
	}

	a.device = core.NewDevice(core.NHP_AGENT, prk, nil)
	if a.device == nil {
		log.Critical("failed to create device %v\n", err)
		return fmt.Errorf("failed to create device %v", err)
	}

	// start device routines
	a.device.Start()

	// serverClusterMap must be non-nil before loadPeers runs (so
	// updateServerPeers' map-swap has a base value) and before
	// callers like AddServer / GetFirstServerPeer touch it on
	// agents that haven't loaded a server.toml yet.
	a.serverClusterMap = make(map[string]*ServerCluster)
	a.serverClusterByName = make(map[string]*ServerCluster)

	// load peers
	_ = a.loadPeers()

	a.remoteConnectionMap = make(map[string]*UdpConn)

	a.signals.stop = make(chan struct{})

View on GitHub (pinned to 6e04ca5ff0)