nats-io/nats-server · error
raft: storage directory is not a directory
Error message
raft: storage directory is not a directory
What it means
NATS Server's embedded Raft layer (used for JetStream meta and CTX groups) validates cfg.Store during setupNewRaftNode. If the path exists but is not a directory (stat exists and stat.IsDir() is false, or stat is nil), initialization aborts with this error.
Source
Thrown at server/raft.go:437
// "localhost" -> 127.0.0.1 + ::1) would then count the same server
// multiple times, inflating the expected meta-group size above the
// real node count and preventing meta leader election.
ngwps += len(gw.URLs)
}
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
}View on GitHub (pinned to 3a66a489d2)
Solutions
- Remove the file/symlink at cfg.Store and create a directory in its place (mkdir -p <path>).
- Point the raft store_dir config at an existing directory or a path that does not exist (it will be created).
- Check permissions and ownership so the server process can stat and use the directory.
Example fix
// before (path is a file) store_dir: /data/js/raft // -rw-r--r-- 1 user user /data/js/raft // after rm /data/js/raft mkdir -p /data/js/raft
Defensive patterns
Strategy: validation
Validate before calling
stat, err := os.Stat(storeDir)
if err == nil && !stat.IsDir() {
return fmt.Errorf("raft store path %q exists and is not a directory", storeDir)
}
if os.IsNotExist(err) {
if err := os.MkdirAll(storeDir, 0750); err != nil {
return err
}
} Prevention
- Verify store_dir points at a directory in deployment scripts before starting the server.
- Avoid placing files at the same path as the configured store_dir.
- Check symlink targets after volume migrations.
When it happens
Trigger: Calling server SetupLeafNodeChain / JetStream raft bootstrap with Store set to a path that exists as a regular file, symlink to a file, socket, or device node instead of a directory.
Common situations: A config value like store_dir accidentally pointing at a file (e.g. a stale WAL file left at the path), a bind-mounted file instead of a directory in containers, or a symlink now resolving to a file after migration.
Related errors
- storage directory is not a directory
- raft: could not create storage directory - %v
- raft: storage directory is not writable
- could not create snapshots directory - %v
- account jwt not found
AI-assisted analysis of nats-io/nats-server@3a66a489d2 (2026-09-02).
Data as JSON: /api/errors/5408e551a38cd7ff.
Report an issue: GitHub.