netbirdio/netbird · error
write private key file (%s): %w
Error message
write private key file (%s): %w
What it means
Returned by the signer CLI's create-root-key command when os.WriteFile cannot persist the generated private key PEM to the path given with --priv-key-file (mode 0600). The error wraps the underlying *fs.PathError, so the real cause (ENOENT, EACCES, EROFS, ENOSPC) appears after the colon. The key pair is generated in memory first, so nothing has been written when this fires.
Source
Thrown at client/cmd/signer/rootkey.go:63
panic(err)
}
if err := createRootKeyCmd.MarkFlagRequired("pub-key-file"); err != nil {
panic(err)
}
if err := createRootKeyCmd.MarkFlagRequired("expiration"); err != nil {
panic(err)
}
}
func handleGenerateRootKey(cmd *cobra.Command, privKeyFile, pubKeyFile string, expiration time.Duration) error {
rk, privPEM, pubPEM, err := reposign.GenerateRootKey(expiration)
if err != nil {
return fmt.Errorf("generate root key: %w", err)
}
// Write private key
if err := os.WriteFile(privKeyFile, privPEM, 0o600); err != nil {
return fmt.Errorf("write private key file (%s): %w", privKeyFile, err)
}
// Write public key
if err := os.WriteFile(pubKeyFile, pubPEM, 0o600); err != nil {
return fmt.Errorf("write public key file (%s): %w", pubKeyFile, err)
}
cmd.Printf("%s\n\n", rk.String())
cmd.Printf("✅ Root key pair generated successfully.\n")
return nil
}
View on GitHub (pinned to 93e97f4bf1)
Solutions
- Check that the parent directory of --priv-key-file exists and is writable by the current user (mkdir -p, chown/chmod as needed); the tool never creates directories.
- If an old key file exists, verify you own it and that it is a regular file, then remove or overwrite it explicitly.
- Re-run pointing at an absolute path in a writable location such as $HOME or a mounted workdir (e.g., --priv-key-file ./keys/root.priv).
- If on a read-only root filesystem (container), mount an emptyDir/volume and write the key there.
Example fix
# before signer create-root-key --priv-key-file /etc/signer/root.priv --pub-key-file /etc/signer/root.pub --expiration 8760h # -> write private key file (/etc/signer/root.priv): open ...: permission denied # after mkdir -p ./keys && sudo chown $(id -u) ./keys signer create-root-key --priv-key-file ./keys/root.priv --pub-key-file ./keys/root.pub --expiration 8760h
Defensive patterns
Strategy: validation
Validate before calling
// before invoking the signer CLI, verify the target path is writable
func canWrite(path string) bool {
if fi, err := os.Stat(path); err == nil && !fi.Mode().IsRegular() {
return false // exists but is a dir/symlink target we cannot trust
}
dir := filepath.Dir(path)
info, err := os.Stat(dir)
return err == nil && info.IsDir() && info.Mode().Perm()&0200 != 0
}
// usage: create dir + pre-check both key paths
os.MkdirAll(filepath.Dir(privKeyFile), 0o700)
if !canWrite(privKeyFile) { log.Fatal("private key path not writable") } Try / catch
if err := cmd.Execute(); err != nil {
var pathErr *fs.PathError
if errors.As(err, &pathErr) && errors.Is(pathErr.Err, fs.ErrPermission) {
// surface a fix hint: target dir not writable by this uid
}
// EROFS/ENOSPC map to distinct host-level remediation
} Prevention
- Pre-create the target directory with mkdir -p before running create-root-key; the tool never creates parents.
- Run the signer as the user who owns the destination directory; avoid interleaving sudo and non-sudo runs that leave root-owned files behind.
- Prefer a dedicated keys directory on a writable volume (workdir, mounted volume) over system paths in containers.
- Treat a failed private-key write as abortive: delete any partial artifacts and re-run, never continue with half a key pair.
When it happens
Trigger: Running `signer create-root-key --priv-key-file <path>` where <path> is in a directory that does not exist, is not writable by the current user, is on a read-only filesystem, or the disk is full. Also fires when the path points at a directory or an existing file that cannot be truncated/re-created (e.g., owned by root and run as non-root).
Common situations: Passing a bare filename while assuming the tool creates parent directories (it does not); running the tool as a normal user with a target under /etc or another root-owned dir; container or CI environments with read-only volumes; stale root-owned key files from a previous sudo run blocking the overwrite.
Related errors
- write public key file (%s): %w
- create output file: %w
- remove empty output file: %w
- --expiration must be a positive duration (e.g., 720h, 365d,
- failed to create artifact key: %w
AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16).
Data as JSON: /api/errors/e69ec3a064cf128f.
Report an issue: GitHub.