restic/restic · error

sftp: no space left on device

Error message

sftp: no space left on device

What it means

isReachable-style handling in the SFTP backend: when a write fails with an SSH_FX_FAILURE status and the server supports statvfs@openssh.com, restic stats the target directory. If free file slots are zero (Favail == 0) or free bytes (Frsize*Bavail) are below the requested size, it converts the failure into a permanent 'no space left on device' error so retry loops stop immediately instead of hammering a full disk.

Source

Thrown at internal/backend/sftp/sftp.go:436

// on the remote, and if so, makes it permanent.
func (r *SFTP) checkNoSpace(dir string, size int64, origErr error) error {
	// The SFTP protocol has a message for ENOSPC,
	// but pkg/sftp doesn't export it and OpenSSH's sftp-server
	// sends FX_FAILURE instead.

	e, ok := origErr.(*sftp.StatusError)
	_, hasExt := r.c.HasExtension("statvfs@openssh.com")
	if !ok || e.FxCode() != sftp.ErrSSHFxFailure || !hasExt {
		return origErr
	}

	fsinfo, err := r.c.StatVFS(dir)
	if err != nil {
		debug.Log("sftp: StatVFS returned %v", err)
		return origErr
	}
	if fsinfo.Favail == 0 || fsinfo.Frsize*fsinfo.Bavail < uint64(size) {
		err := errors.New("sftp: no space left on device")
		return backoff.Permanent(err)
	}
	return origErr
}

// Load runs fn with a reader that yields the contents of the file at h at the
// given offset.
func (r *SFTP) Load(ctx context.Context, h backend.Handle, length int, offset int64, fn func(rd io.Reader) error) error {
	if err := r.clientError(); err != nil {
		return err
	}

	return util.DefaultLoad(ctx, h, length, offset, r.openReader, func(rd io.Reader) error {
		if length == 0 || !feature.Flag.Enabled(feature.BackendErrorRedesign) {
			return fn(rd)
		}

		// there is no direct way to efficiently check whether the file is too short

View on GitHub (pinned to a80be1478a)

Solutions

  1. Free space on the remote filesystem (delete/prune data, raise the quota) and retry.
  2. If inodes are exhausted (Favail == 0), remove many small files or reformat with more inodes.
  3. Run 'restic prune' against the repo to shrink it once any space is recoverable.
  4. Point the repository at a larger volume if growth is expected.

Example fix

# before
$ restic -r sftp:admin@nas:/srv/restic backup ./bigdir
# sftp: no space left on device

# after (on the server)
$ df -h /srv/restic && df -i /srv/restic
$ rm -rf /srv/old-exports   # or grow the volume
$ restic -r sftp:admin@nas:/srv/restic backup ./bigdir
Defensive patterns

Strategy: validation

Validate before calling

// estimate need before large writes: statvfs the repo dir via ssh
out, err := exec.Command("ssh", host, "df -B1 --output=avail "+repoDir).Output()
if err == nil {
	avail, _ := strconv.ParseInt(strings.TrimSpace(string(out)), 10, 64)
	if avail < expectedPackBytes {
		return errors.New("insufficient space on remote repo volume")
	}
}

Try / catch

err := be.Save(ctx, h, rd)
if err != nil {
	if strings.Contains(err.Error(), "no space left on device") {
		// permanent: stop the run, alert, do not retry
		return fmt.Errorf("remote volume full; free space or prune before retrying: %w", err)
	}
	return err
}

Prevention

When it happens

Trigger: Saving a pack file whose size exceeds free bytes on the remote filesystem; inode exhaustion on the server (Favail == 0) even with bytes free; requires the server to support statvfs@openssh.com or the original error passes through unchanged.

Common situations: Full or quota-capped remote volumes; small VPS/home-NAS partitions hosting the repo; inode exhaustion from millions of small files; size mispredictions during large backup uploads.

Related errors


AI-assisted analysis of restic/restic@a80be1478a (2026-08-15). Data as JSON: /api/errors/3d72a05e49369c5c. Report an issue: GitHub.