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

  1. 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.
  2. If an old key file exists, verify you own it and that it is a regular file, then remove or overwrite it explicitly.
  3. 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).
  4. 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

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


AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16). Data as JSON: /api/errors/e69ec3a064cf128f. Report an issue: GitHub.