netbirdio/netbird · error
write public key file (%s): %w
Error message
write public key file (%s): %w
What it means
Returned by the signer CLI's create-root-key command when os.WriteFile cannot persist the generated public key PEM to the path given with --pub-key-file (mode 0600). It wraps the underlying *fs.PathError. Note the private key file is written before this step, so hitting this error leaves a freshly written private key on disk with no matching public key beside it.
Source
Thrown at client/cmd/signer/rootkey.go:68
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
- Verify the parent directory of --pub-key-file exists and is writable (mkdir -p); the tool does not create directories.
- Keep both key files in the same prepared directory so one permission fix covers both writes.
- Free disk space if ENOSPC, or remount the volume read-write if EROFS.
- If a partial run left a private key behind, delete it before re-running so you do not accumulate an orphaned key.
Example fix
# before signer create-root-key --priv-key-file ./keys/root.priv --pub-key-file /etc/signer/root.pub --expiration 8760h # -> write public key file (/etc/signer/root.pub): open ...: no such file or directory # after mkdir -p ./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
// ensure BOTH output paths are writable before generating anything
paths := []string{privKeyFile, pubKeyFile}
for _, p := range paths {
if dir := filepath.Dir(p); dir != "" {
if err := os.MkdirAll(dir, 0o755); err != nil {
log.Fatalf("prepare dir for %s: %v", p, err)
}
}
f, err := os.OpenFile(p, os.O_WRONLY|os.O_CREATE, 0o600)
if err != nil {
log.Fatalf("path not writable: %s (%v)", p, err)
}
f.Close()
} Try / catch
if err := handleGenerateRootKey(cmd, priv, pub, exp); err != nil {
var pathErr *fs.PathError
if errors.As(err, &pathErr) {
// both key writes produce *fs.PathError; distinguish ENOSPC/EROFS/EACCES
// and remember the priv file may exist from a partial run
}
} Prevention
- Keep --priv-key-file and --pub-key-file in the same directory you prepared — one permission fix then covers both writes.
- Free disk space check before key ceremonies (signing keys are small, but full disks break atomic-looking flows).
- After any failed run, ls both paths and remove orphans so a later success never mixes keys from different runs.
- In CI, create the artifacts directory in a setup step rather than relying on the signer to imply it.
When it happens
Trigger: Running `signer create-root-key` where the --pub-key-file path is in a missing/unwritable directory, on a read-only filesystem, with no space left, or where the target is a directory or an unwritable existing file — while the --priv-key-file path is writable.
Common situations: The two flags pointing at different directories where only the private key's directory was prepared; typos in the public key path; CI artifacts directory not created before the step runs; disk filling up between the two writes.
Related errors
- write private 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/dec635f176d12139.
Report an issue: GitHub.