Skip to main content
RunBook Academy

Proxmox VEVI · ZFSZFS in Proxmox

volblocksize, recordsize and guest alignment

Advanced⏱ ~26 minzfsqm

What you'll learn

  • Choose a volblocksize against the guest filesystem, the vdev geometry and the workload
  • Explain why a small volblocksize costs capacity on RAIDZ and not on mirrors
  • Set the blocksize on a Proxmox ZFS storage and verify what a zvol actually got
  • Migrate an existing VM disk to a different volblocksize, since it cannot be changed in place

Prerequisites

Verified against Proxmox VE 9.2.4 · Proxmox Backup Server 4.2.5 · Ceph Squid / Tentacle · Debian 13 (Trixie) · Linux kernel 7.0 (PVE 9.2 default) · 2026-08-12

Not yet marked complete on this device.

Nearly every ZFS mistake is recoverable. ARC is too big, so you cap it. The scrub schedule is wrong, so you change it. Compression is off, so you turn it on and new writes get it.

volblocksize is not in that category. The property page states it plainly: “The blocksize cannot be changed once the volume has been written, so it should be set at volume creation time.” Every VM disk Proxmox creates on a zfspool storage is a zvol, and every one of them takes its block size from the storage’s blocksize setting at the moment it is created.

Which means the number in /etc/pve/storage.cfg on the day you built the cluster is baked into every disk created since, and correcting it means rewriting all of them.

The default moved, and old advice did not

OpenZFS changed the default volblocksize from 8 KiB to 16 KiB in version 2.2. PVE 9.2 ships ZFS 2.4, so a zvol created today with no explicit setting gets 16 KiB.

The upstream reasoning is worth reading in full, because it is the whole trade in two sentences: “Current default (16KB since v2.2) balances the metadata overhead, compression opportunities and decent space efficiency on majority of pool configurations due to 4KB disk physical block rounding (especially on RAIDZ and DRAID), while incurring some write amplification on guest FSes that run with smaller block sizes.”

The three numbers that have to agree

volblocksize sits between two other block sizes and should not be chosen without both of them in view.

guest filesystem block size   (ext4/XFS/NTFS: 4 KiB; InnoDB pages: 16 KiB)
        |
        v
volblocksize                  (the zvol's allocation unit)
        |
        v
ashift                        (the pool's sector size: 2^12 = 4 KiB)

The hard floor comes from upstream and it is absolute: “It’s meaningless to set volblocksize less than guest FS’s block size or ashift.”

Below the guest’s block size, you have not made I/O smaller — the guest still writes 4 KiB — you have only made ZFS do more work per guest write. Below ashift, ZFS cannot address the storage that finely anyway.

Above the guest’s block size, every partial write becomes a read-modify-write: ZFS must read the whole block, apply the guest’s 4 KiB, recompute the checksum, and write the whole block back. On a size-3 mirror that is one read and two writes for a 4 KiB guest operation.

Guest workloadGuest blockSensible volblocksizeWhy
General-purpose Linux, ext4/XFS4 KiB16 KiBThe default. Good compression, acceptable amplification
Windows Server, NTFS default4 KiB16 KiBSame reasoning
MySQL / InnoDB16 KiB16 KiBExact match; no read-modify-write on a page write
PostgreSQL8 KiB16 KiB8 KiB is defensible on mirrors, costly on RAIDZ
File server, media, backups4 KiB, sequential64–128 KiBSequential access; compression ratio improves markedly
Small random I/O on a mirror pool4 KiB8 KiBOnly when the pool is mirrors and IOPS matter more than space

The right way to read that table is that 16 KiB is correct until you can name why it is not. The exceptions are real, and there are fewer of them than the tuning discussions suggest.

Setting it in Proxmox

Proxmox exposes this as the blocksize option on a zfspool storage, documented as “Set ZFS blocksize parameter”. No default is documented, which means when it is unset the pool’s own volblocksize default applies — 16 KiB on ZFS 2.2 and later.

Read-only / Safewhat is actually configured right now
grep -A8 '^zfspool:' /etc/pve/storage.cfg
zfs version
Read-only / Safewhat the existing zvols actually got
POOL=tank
zfs list -t volume -o name,volsize,volblocksize,used,logicalused,compressratio -r "$POOL"
Read-only / Safe
$ zfs list -t volume -o name,volsize,volblocksize,used,logicalused,compressratio -r tank
NAME                  VOLSIZE  VOLBLOCK   USED  LUSED  RATIO
tank/vm-101-disk-0        100G       16K  41.2G  38.9G  1.06x
tank/vm-102-disk-0        200G       16K  88.7G  84.1G  1.05x
tank/vm-140-disk-0        500G        8K   412G   287G  1.04x

Illustrative output

That last row is the whole lesson in one line. USED is what the pool gave up; LUSED is what the guest actually stored. A ratio far above compressratio means space is going somewhere other than data, and on RAIDZ with a small volblocksize that somewhere is parity and padding.

To change the setting for future disks:

Configuration changeset the blocksize on a storage
STORAGE=local-zfs
pvesm set "$STORAGE" --blocksize 16k

grep -A8 "^zfspool: $STORAGE" /etc/pve/storage.cfg

Changing it on a disk that already exists

You cannot. What you can do is create a new zvol with the right setting and copy the data, which Proxmox will do for you.

Service impact possiblemove a disk to a storage with the right blocksize
VMID=140
DISK=scsi0
TARGET=local-zfs-16k

qm move-disk "$VMID" "$DISK" "$TARGET" --delete 0

# confirm the new zvol has the block size you wanted
zfs get volblocksize "tank16k/vm-$VMID-disk-0"

The trick that makes this practical: define a second zfspool storage entry pointing at a different dataset of the same pool, with the blocksize you want. Then qm move-disk between them is a local copy at pool speed, and you can work through the estate a VM at a time rather than planning an outage.

Once the VM has booted from the new disk and you are satisfied, remove the old one — qm move-disk with --delete 0 leaves it attached as an unused disk, visible in the GUI, which is the correct place for it until you are sure.

recordsize is the same idea for datasets, with one important difference

Zvols have volblocksize. Datasets — a ZFS filesystem exposed to Proxmox as directory storage, holding qcow2 or raw files, ISOs, templates, backups — have recordsize, default 128 KiB.

The difference that matters operationally: “Changing the recordsize on a dataset will only take effect for new files.” Existing files keep their record size, but new ones get the new value, so a dataset is correctable by rewriting files rather than by moving storage. Upstream names the mechanism: “A cp followed by a mv on each file is sufficient.”

Where it earns attention:

  • A dataset holding PBS chunk data or backup files: leave it at 128 KiB. Large sequential writes, and a smaller record size only adds metadata.
  • A dataset holding qcow2 VM images: the qcow2 cluster size (64 KiB by default) is the alignment that matters, not the guest’s filesystem.
  • A dataset holding a database directly (a bind mount into an LXC container rather than a VM disk): upstream gives concrete numbers — recordsize=16K for InnoDB data files with 128 KiB left on the logs, and recordsize=32K for PostgreSQL data and WAL, noting 64 KiB and the 128 KiB default also work well.

Common mistakes

  • Copying volblocksize=8K from a runbook written before ZFS 2.2. The default is 16 KiB now, and 8 KiB on RAIDZ costs real capacity.
  • Setting it below the guest filesystem block size. Upstream calls this meaningless, and it is: the guest still writes 4 KiB and ZFS just does more work.
  • Tuning it for the pool and ignoring the guest. An InnoDB VM and a file server on the same storage want different numbers; that is what a second storage entry is for.
  • Assuming pvesm set --blocksize fixed the existing disks. It applies to zvols created afterwards. Check with zfs get volblocksize.
  • Diagnosing the used-versus-logicalused gap as a compression problem. On RAIDZ with small blocks it is parity and padding, and no compression setting will change it.
  • Running qm move-disk with --delete 1 on the first pass. Keep the original until the VM has booted from the new disk.
  • Changing recordsize on a dataset and expecting existing files to follow. They do not; only new files get it.

Key takeaways

  • volblocksize is fixed when the zvol is written. It is the one ZFS setting with no in-place correction.
  • The default is 16 KiB since ZFS 2.2, and it is the right answer until you can name why it is not.
  • Never set it below the guest filesystem’s block size or below ashift.
  • On mirrors the choice barely affects capacity. On RAIDZ a small block size loses capacity to per-block parity and to allocation rounding.
  • Proxmox sets it per storage with blocksize; new zvols only.
  • Correct an existing disk with qm move-disk to a second storage entry, or by restoring it from PBS onto the corrected storage.
  • recordsize is the dataset equivalent and is correctable by rewriting files, which volblocksize is not.

Knowledge check

Knowledge check · 4 questions

  1. Q1. A pool of RAIDZ2 vdevs with ashift=12 holds VM disks created with volblocksize=8K. Reported used is roughly 1.4x logicalused, while compressratio reads 1.05x. What accounts for the gap?

  2. Q2. You run pvesm set local-zfs --blocksize 16k on a storage whose existing zvols were created at 8K. What changes?

  3. Q3. Which of these are sound reasons to choose something other than the 16 KiB default for a particular Proxmox ZFS storage? Select all that apply.

  4. Q4. Setting recordsize on a ZFS dataset and setting volblocksize on a zvol have the same permanence: both apply only to data written afterwards, but recordsize can be corrected by rewriting individual files while volblocksize cannot be corrected at all without moving the volume.

Passing score: 75%. Answers are checked in this browser.