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

  1. chown/chmod the storage directory so the server process has write access (e.g. chown -R nats:nats /data/js).
  2. Remount the volume read-write or move store_dir to a writable path.
  3. Free disk space / inodes on the volume, then restart the server.
  4. 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

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


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