nats-io/nats-server · critical
storage directory is not a directory
Error message
storage directory is not a directory
What it means
JetStream enablement: cfg.StoreDir exists but its stat mode is not a directory (a file occupies the path), failing the post-stat check that verifies the storage location is usable as a directory.
Source
Thrown at server/jetstream.go:455
atomic.StoreInt64(&js.memMax, cfg.MaxMemory)
atomic.StoreInt64(&js.storeMax, cfg.MaxStore)
// TODO: Not currently reloadable.
atomic.StoreInt64(&js.queueLimit, s.getOpts().JetStreamRequestQueueLimit)
atomic.StoreInt64(&js.infoQueueLimit, s.getOpts().JetStreamInfoQueueLimit)
s.js.Store(js)
// FIXME(dlc) - Allow memory only operation?
if stat, err := os.Stat(cfg.StoreDir); os.IsNotExist(err) {
if err := os.MkdirAll(cfg.StoreDir, defaultDirPerms); err != nil {
return fmt.Errorf("could not create storage directory - %v", err)
}
} else {
// Make sure its a directory and that we can write to it.
if stat == nil || !stat.IsDir() {
return fmt.Errorf("storage directory is not a directory")
}
tmpfile, err := os.CreateTemp(cfg.StoreDir, "_test_")
if err != nil {
return fmt.Errorf("storage directory is not writable")
}
tmpfile.Close()
os.Remove(tmpfile.Name())
}
if err := s.initJetStreamEncryption(); err != nil {
return err
}
// JetStream is an internal service so we need to make sure we have a system account.
// This system account will export the JetStream service endpoints.
if s.SystemAccount() == nil {
s.SetDefaultSystemAccount()
}View on GitHub (pinned to 3a66a489d2)
Solutions
- Remove or rename the file occupying store_dir, then create a real directory there
- Point store_dir at the intended directory in config
- Verify with ls -la <store_dir> that the path is a directory before startup
Example fix
// before
jetstream { store_dir: "/var/lib/nats/js" } // /var/lib/nats/js is a regular file
// after
// mv /var/lib/nats/js /var/lib/nats/js.bak && mkdir /var/lib/nats/js && chown nats:nats /var/lib/nats/js Defensive patterns
Strategy: validation
Validate before calling
if fi, err := os.Stat(storeDir); err == nil && !fi.IsDir() {
return fmt.Errorf("%s exists but is not a directory; remove or move it", storeDir)
} Prevention
- Never point store_dir at a file path; validate config paths at deploy time
- Check that log rotation/backup jobs don't drop files into the store dir
- When mounting volumes, ensure the mount source is a directory
- Add a pre-start check script: [ -d "$STORE_DIR" ] || exit 1
When it happens
Trigger: cfg.StoreDir points to an existing regular file, symlink to a file, or other non-directory — os.Stat succeeds but stat.IsDir() is false (or stat is nil).
Common situations: A file was accidentally created at the store_dir path (e.g. logs or a tarball written there); config pointing at a file like /etc/nats/js.conf instead of a directory; docker volume mounting a file over the dir path.
Related errors
- could not create storage directory - %v
- storage directory is not writable
- raft: could not create storage directory - %v
- storage type can not be updated
- system account not setup
AI-assisted analysis of nats-io/nats-server@3a66a489d2 (2026-09-02).
Data as JSON: /api/errors/bd9ca71ed0c76197.
Report an issue: GitHub.