nats-io/nats-server · error
raft: storage directory is not writable
Error message
raft: storage directory is not writable
What it means
After confirming cfg.Store is a directory, the Raft setup performs a write probe with os.CreateTemp in that directory. If creating the temp file fails (permissions, read-only filesystem, disk full), setup returns this error, indicating the storage directory is unusable for the WAL.
Source
Thrown at server/raft.go:441
}
if expected < nrs+ngwps {
expected = nrs + ngwps
s.Debugf("Adjusting expected peer set size to %d with %d known", expected, len(knownPeers))
}
}
// Check the store directory. If we have a memory based WAL we need to make sure the directory is setup.
if stat, err := os.Stat(cfg.Store); os.IsNotExist(err) {
if err := os.MkdirAll(cfg.Store, defaultDirPerms); err != nil {
return fmt.Errorf("raft: could not create storage directory - %v", err)
}
} else if stat == nil || !stat.IsDir() {
return fmt.Errorf("raft: storage directory is not a directory")
}
tmpfile, err := os.CreateTemp(cfg.Store, "_test_")
if err != nil {
return fmt.Errorf("raft: storage directory is not writable")
}
tmpfile.Close()
os.Remove(tmpfile.Name())
return writePeerState(s.diskIOSemaphore(), cfg.Store, &peerState{knownPeers, expected, extUndetermined})
}
// initRaftNode will initialize the raft node, to be used by startRaftNode or when testing to not run the Go routine.
func (s *Server) initRaftNode(accName string, cfg *RaftConfig, labels pprofLabels) (*raft, error) {
restorePeerState := func(n *raft) error {
ps, err := readPeerState(s.diskIOSemaphore(), cfg.Store)
if err != nil {
return err
}
if ps == nil {
return errNoPeerState
}
n.processPeerState(ps)View on GitHub (pinned to 3a66a489d2)
Solutions
- chown/chmod the storage directory so the server process has write access (e.g. chown -R nats:nats /data/js).
- Remount the volume read-write or move store_dir to a writable path.
- Free disk space / inodes on the volume, then restart the server.
- Check server logs for the underlying os error to identify ENOSPC vs EACCES vs EROFS.
Example fix
// before # ls -ld /data/js dr-xr-xr-x root root /data/js // after chown -R nats:nats /data/js && chmod u+rwx /data/js
Defensive patterns
Strategy: validation
Validate before calling
if err := os.MkdirAll(storeDir, 0750); err != nil { return err }
probe, err := os.CreateTemp(storeDir, "_write_probe_")
if err != nil { return err }
probe.Close(); os.Remove(probe.Name()) Prevention
- Ensure the server's OS user owns or can write the JetStream store directory.
- Do not mount store volumes read-only.
- Monitor disk space/inodes on store volumes.
When it happens
Trigger: os.CreateTemp(cfg.Store, "_test_") fails: directory has no write permission for the server user, filesystem is mounted read-only, or the underlying device has no space/inodes.
Common situations: Running NATS as a non-root user against a root-owned JetStream store dir, Docker/K8s volume mounted readOnly, or full disk after WAL growth.
Understand the failure class
Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.
Related errors
- could not create storage directory - %v
- storage directory is not writable
- could not create message storage directory - %v
- could not create consumer storage directory - %v
- Error creating msg block file: %v
AI-assisted analysis of nats-io/nats-server@3a66a489d2 (2026-09-02).
Data as JSON: /api/errors/fa88b191b134d9bf.
Report an issue: GitHub.