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

  1. Remove or rename the file occupying store_dir, then create a real directory there
  2. Point store_dir at the intended directory in config
  3. 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

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


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