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.
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 workload
Guest block
Sensible volblocksize
Why
General-purpose Linux, ext4/XFS
4 KiB
16 KiB
The default. Good compression, acceptable amplification
Windows Server, NTFS default
4 KiB
16 KiB
Same reasoning
MySQL / InnoDB
16 KiB
16 KiB
Exact match; no read-modify-write on a page write
PostgreSQL
8 KiB
16 KiB
8 KiB is defensible on mirrors, costly on RAIDZ
File server, media, backups
4 KiB, sequential
64–128 KiB
Sequential access; compression ratio improves markedly
Small random I/O on a mirror pool
4 KiB
8 KiB
Only 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— Read-only. The absence of a blocksize line means the ZFS default applies, which is 16K on ZFS 2.2+ and 8K on anything older - so an upgraded host can have two eras of zvol on one pool.
grep -A8 '^zfspool:' /etc/pve/storage.cfg
zfs version
Read-only / Safewhat the existing zvols actually got— Read-only. This is the authoritative answer; storage.cfg tells you what new disks will get, this tells you what the current ones have. A mixture is normal on a pool that predates a settings change.
POOL=tank
zfs list -t volume -o name,volsize,volblocksize,used,logicalused,compressratio -r "$POOL"
Read-only / Safe— Illustrative. vm-140 predates the storage change and still has 8K. Note its USED against LUSED: 412G on disk for 287G of logical data is the RAIDZ rounding cost, not compression failing.
$ 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— Affects only zvols created after this change. Existing disks keep the block size they were created with; nothing about them changes and nothing is at risk.
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— Creates a new zvol on the target storage, copies every block, then switches the VM to it. The VM may run during the move on most storage combinations, but the copy competes for pool I/O. Keep --delete 0 on the first pass so the original disk survives until you have confirmed the new one boots.
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
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?
Q2. You run pvesm set local-zfs --blocksize 16k on a storage whose existing zvols were created at 8K. What changes?
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.
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.