diff --git a/changelog/unreleased/issue-4239 b/changelog/unreleased/issue-4239
new file mode 100644
index 000000000..247f3d9ed
--- /dev/null
+++ b/changelog/unreleased/issue-4239
@@ -0,0 +1,11 @@
+Bugfix: Correct number of blocks reported in mount point
+
+Restic mount points incorrectly reported the number of 512-byte (POSIX
+standard) blocks for files and links, due to a rounding bug. In particular,
+empty files were reported as taking one block instead of zero.
+
+The rounding is now fixed: the number of blocks reported is the file size
+(or link target size), divided by 512 and rounded up to a whole number.
+
+https://github.com/restic/restic/issues/4239
+https://github.com/restic/restic/pull/4240
diff --git a/internal/fuse/file.go b/internal/fuse/file.go
index 28ff5d450..35bc2a73e 100644
--- a/internal/fuse/file.go
+++ b/internal/fuse/file.go
@@ -50,7 +50,7 @@ func (f *file) Attr(ctx context.Context, a *fuse.Attr) error {
 	a.Inode = f.inode
 	a.Mode = f.node.Mode
 	a.Size = f.node.Size
-	a.Blocks = (f.node.Size / blockSize) + 1
+	a.Blocks = (f.node.Size + blockSize - 1) / blockSize
 	a.BlockSize = blockSize
 	a.Nlink = uint32(f.node.Links)
 
diff --git a/internal/fuse/fuse_test.go b/internal/fuse/fuse_test.go
index e71bf6fee..863c7672d 100644
--- a/internal/fuse/fuse_test.go
+++ b/internal/fuse/fuse_test.go
@@ -8,6 +8,7 @@ import (
 	"context"
 	"math/rand"
 	"os"
+	"strings"
 	"testing"
 	"time"
 
@@ -216,6 +217,37 @@ func testTopUIDGID(t *testing.T, cfg Config, repo restic.Repository, uid, gid ui
 	rtest.Equals(t, uint32(0), attr.Gid)
 }
 
+// Test reporting of fuse.Attr.Blocks in multiples of 512.
+func TestBlocks(t *testing.T) {
+	root := &Root{}
+
+	for _, c := range []struct {
+		size, blocks uint64
+	}{
+		{0, 0},
+		{1, 1},
+		{511, 1},
+		{512, 1},
+		{513, 2},
+		{1024, 2},
+		{1025, 3},
+		{41253, 81},
+	} {
+		target := strings.Repeat("x", int(c.size))
+
+		for _, n := range []fs.Node{
+			&file{root: root, node: &restic.Node{Size: uint64(c.size)}},
+			&link{root: root, node: &restic.Node{LinkTarget: target}},
+			&snapshotLink{root: root, snapshot: &restic.Snapshot{}, target: target},
+		} {
+			var a fuse.Attr
+			err := n.Attr(context.TODO(), &a)
+			rtest.OK(t, err)
+			rtest.Equals(t, c.blocks, a.Blocks)
+		}
+	}
+}
+
 func TestInodeFromNode(t *testing.T) {
 	node := &restic.Node{Name: "foo.txt", Type: "chardev", Links: 2}
 	ino1 := inodeFromNode(1, node)
diff --git a/internal/fuse/link.go b/internal/fuse/link.go
index f910aadc4..47ee666a3 100644
--- a/internal/fuse/link.go
+++ b/internal/fuse/link.go
@@ -42,7 +42,7 @@ func (l *link) Attr(ctx context.Context, a *fuse.Attr) error {
 
 	a.Nlink = uint32(l.node.Links)
 	a.Size = uint64(len(l.node.LinkTarget))
-	a.Blocks = 1 + a.Size/blockSize
+	a.Blocks = (a.Size + blockSize - 1) / blockSize
 
 	return nil
 }
diff --git a/internal/fuse/snapshots_dir.go b/internal/fuse/snapshots_dir.go
index 977d0ab17..c19155741 100644
--- a/internal/fuse/snapshots_dir.go
+++ b/internal/fuse/snapshots_dir.go
@@ -142,7 +142,7 @@ func (l *snapshotLink) Attr(ctx context.Context, a *fuse.Attr) error {
 	a.Inode = l.inode
 	a.Mode = os.ModeSymlink | 0777
 	a.Size = uint64(len(l.target))
-	a.Blocks = 1 + a.Size/blockSize
+	a.Blocks = (a.Size + blockSize - 1) / blockSize
 	a.Uid = l.root.uid
 	a.Gid = l.root.gid
 	a.Atime = l.snapshot.Time