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

  1. Remove the file/symlink at cfg.Store and create a directory in its place (mkdir -p <path>).
  2. Point the raft store_dir config at an existing directory or a path that does not exist (it will be created).
  3. 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

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


AI-assisted analysis of nats-io/nats-server@3a66a489d2 (2026-09-02). Data as JSON: /api/errors/5408e551a38cd7ff. Report an issue: GitHub.