juanfont/headscale · critical
reading or creating Noise protocol private key: %w
Error message
reading or creating Noise protocol private key: %w
What it means
readOrCreatePrivateKey failed for the Noise protocol key (hscontrol/app.go:955). The function ensures the key directory exists, reads the key file — creating and persisting a fresh key.MachinePrivate if it does not exist — and otherwise parses the existing content. Failure means: the directory could not be created, the file could not be read (permissions), a new key could not be written (read-only filesystem, wrong ownership), or the existing file content is not a valid machine private key.
Source
Thrown at hscontrol/app.go:120
var (
profilingEnabled = envknob.Bool("HEADSCALE_DEBUG_PROFILING_ENABLED")
profilingPath = envknob.String("HEADSCALE_DEBUG_PROFILING_PATH")
tailsqlEnabled = envknob.Bool("HEADSCALE_DEBUG_TAILSQL_ENABLED")
tailsqlStateDir = envknob.String("HEADSCALE_DEBUG_TAILSQL_STATE_DIR")
tailsqlTSKey = envknob.String("TS_AUTHKEY")
dumpConfig = envknob.Bool("HEADSCALE_DEBUG_DUMP_CONFIG")
)
func NewHeadscale(cfg *types.Config) (*Headscale, error) {
var err error
if profilingEnabled {
runtime.SetBlockProfileRate(1)
}
noisePrivateKey, err := readOrCreatePrivateKey(cfg.NoisePrivateKeyPath)
if err != nil {
return nil, fmt.Errorf("reading or creating Noise protocol private key: %w", err)
}
s, err := state.NewState(cfg)
if err != nil {
return nil, fmt.Errorf("init state: %w", err)
}
app := Headscale{
cfg: cfg,
noisePrivateKey: noisePrivateKey,
clientStreamsOpen: sync.WaitGroup{},
state: s,
}
if len(cfg.TrustedProxies) > 0 {
app.realIPMiddleware, err = trustedProxyRealIP(cfg.TrustedProxies)
if err != nil {
return nil, fmt.Errorf("building trusted_proxies middleware: %w", err)View on GitHub (pinned to 565fd254d0)
Solutions
- Check the wrapped message — it distinguishes 'ensuring private key directory', 'reading private key file', and 'saving private key to disk at path %q'
- Fix permissions: the headscale process user must be able to create the parent dir and read/write the key file
- If the existing key file is corrupt, back it up, delete it, and restart headscale — a new key is generated automatically (nodes must then re-register)
- On read-only mounts, pre-generate the key out-of-band and mount it with correct ownership
Example fix
# before: key file owned by root, headscale runs as non-root ls -l /var/lib/headscale/noise_private_key # -rw------- 1 root root # after chown headscale:headscale /var/lib/headscale/noise_private_key chmod 600 /var/lib/headscale/noise_private_key
Defensive patterns
Strategy: validation
Validate before calling
// Validate key path expectations before startup
p := cfg.NoisePrivateKeyPath
if err := util.EnsureDir(filepath.Dir(p)); err != nil { return err }
if b, err := os.ReadFile(p); err == nil {
if _, perr := key.ParseMachinePrivate(string(b)); perr != nil { // or equivalent parse
return fmt.Errorf("noise key file corrupt: %w", perr)
}
} Try / catch
if _, err := readOrCreatePrivateKey(cfg.NoisePrivateKeyPath); err != nil {
if errors.Is(err, os.ErrPermission) {
// fix ownership/mode of the key file and its directory, then restart
}
// parse errors on an existing file: rotate the key (nodes must re-register)
} Prevention
- Pre-create the key directory with correct ownership before first start
- Never hand-edit key files; if corrupt, delete and let headscale regenerate (accept re-registration)
- On read-only mounts, generate keys out-of-band and mount them read-only with strict modes
When it happens
Trigger: noise_private_key_path pointing to an unwritable directory (read-only container fs, root-owned dir with headscale running as non-root), an existing key file with corrupt/truncated/hand-edited content, or a file permissions error (mode is deliberately strict on read).
Common situations: Kubernetes/Docker deployments mounting a read-only secret at the key path without the key present; ops staff editing the key file and introducing whitespace/newlines that break parsing; volume permission mismatches after switching the headscale user; SELinux denying reads.
Related errors
- reading or creating DERP server private key: %w
- ensuring private key directory: %w
- saving private key to disk at path %q: %w
- reading private key file: %w
- creating directory failed with permission error
AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15).
Data as JSON: /api/errors/f93e8be935b0c923.
Report an issue: GitHub.