netbirdio/netbird · error
failed to write signature file: %w
Error message
failed to write signature file: %w
What it means
The second os.WriteFile inside writeOutputFiles failed (revocation.go:216): the signature bytes could not be written to the derived path rlPath + ".sig" with 0600 permissions. Because the list file is written first, hitting this error leaves a replaced list paired with a stale or missing signature — an inconsistent pair that fails verify-revocation-list until both are regenerated together.
Source
Thrown at client/cmd/signer/revocation.go:217
cmd.Printf("Expires At: %s\n", rl.ExpiresAt.Format(time.RFC3339))
cmd.Printf("Number of revoked keys: %d\n", len(rl.Revoked))
if len(rl.Revoked) > 0 {
cmd.Println("\nRevoked Keys:")
for keyID, revokedTime := range rl.Revoked {
cmd.Printf(" - %s (revoked at: %s)\n", keyID, revokedTime.Format(time.RFC3339))
}
}
return nil
}
func writeOutputFiles(rlPath, sigPath string, rlBytes, sigBytes []byte) error {
if err := os.WriteFile(rlPath, rlBytes, 0o600); err != nil {
return fmt.Errorf("failed to write revocation list file: %w", err)
}
if err := os.WriteFile(sigPath, sigBytes, 0o600); err != nil {
return fmt.Errorf("failed to write signature file: %w", err)
}
return nil
}
View on GitHub (pinned to 93e97f4bf1)
Solutions
- Fix permissions on the stale .sig (chown/chmod) or remove it so it can be recreated
- Free space or remount read-write, then re-run the same create/extend command so list and signature are written as a matched pair
- Always verify the pair afterwards: signer verify-revocation-list ... before publishing
- Treat the on-disk pair as untrusted after this error until regenerated — the list may be newer than the signature
Example fix
# before: extend fails with 'failed to write signature file: open rl.json.sig: permission denied' # after chmod u+w rl.json.sig # or: rm rl.json.sig signer extend-revocation-list --key-id 1a2b3c4d5e6f7080 --revocation-list-file rl.json --private-root-key root.pem signer verify-revocation-list --revocation-list-file rl.json --signature-file rl.json.sig --public-root-key root-public.pem
Defensive patterns
Strategy: validation
Validate before calling
func preflightWrites(paths ...string) error {
for _, p := range paths {
if err := ensureWritableDir(p); err != nil {
return err
}
if info, err := os.Stat(p); err == nil {
if info.Mode().Perm()&0o200 == 0 {
return fmt.Errorf("%s not writable by uid %d", p, os.Getuid())
}
}
}
return nil
}
// covers both rlPath and rlPath+".sig" — the second write is the easy one to miss Prevention
- Preflight BOTH target paths; a stale root-owned .sig is the classic second-write failure
- After any failed write, re-run the sign step so list and signature stay a matched pair
- Always verify with verify-revocation-list after recovery before publishing
When it happens
Trigger: The <list>.sig path is unwritable (an old .sig owned by another user with 0600 perms is the classic case), the directory went read-only between the two writes, or the disk filled exactly before the second write.
Common situations: A .sig created by root in an earlier run, then extend run as a non-root user; permission hardening applied between runs; ENOSPC on nearly-full volumes.
Related errors
- write private key file (%s): %w
- write public key file (%s): %w
- write public keys file (%s): %w
- failed to write output files: %w
- failed to read signature file: %w
AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16).
Data as JSON: /api/errors/a950980370727308.
Report an issue: GitHub.