Infrastructure

Converting an OVH Proxmox template from LVM to ZFS

Moving an OVH Proxmox template from LVM to ZFS to enable replication between nodes. Critical pitfalls to avoid (data destruction, ARC memory, zstd compression), pool monitoring, automatic monthly scrubs and an asynchronous replication strategy.

Converting an OVH Proxmox template from LVM to ZFS
Contents (16 sections)
  1. Why ZFS for high availability
  2. Prerequisites
  3. Step 1: install ZFS if needed
  4. Step 2: create the ZFS pool
  5. Why ashift=12
  6. Compression: zstd rather than lz4
  7. Step 3: tune the ZFS ARC (RAM)
  8. Step 4: add the storage in Proxmox
  9. Step 5: migrate existing VMs
  10. Step 6: enable replication between nodes
  11. Monitoring and maintaining the pool
  12. Daily health check
  13. Automatic monthly scrub
  14. E-mail alerts on degradation
  15. Classic pitfalls to avoid
  16. Conclusion

The Proxmox templates supplied by OVH are installed on LVM by default. That makes them quick to provision, but it rules out replication between nodes, atomic snapshots and native data integrity. To unlock real high availability in Proxmox, you need to switch to ZFS: here is the full procedure, tested in production on a three-node OVH cluster.

Why ZFS for high availability #

ZFS supports native replication between nodes through zfs send/receive, the mechanism used by the Proxmox HA module. LVM does not. Other benefits: near-instant atomic snapshots, native compression (practically free in CPU terms in 2026 with zstd), and periodic integrity checks (scrubs) that catch silent corruption before it spreads into your backups.

The drawbacks you need to know about: ZFS needs RAM (allow at least 1 GB of ARC per TB of usable data), it cannot grow a pool as flexibly as LVM (adding a disk means a new vdev or a full replacement), and some unusual Linux operations (POSIX ACLs with chmod, NFS root) need specific settings.

Prerequisites #

  • A Proxmox 8.x cluster with at least 2 nodes (on a single node, ZFS is still useful for snapshots and integrity).
  • A disk dedicated to ZFS: a second physical disk, ideally NVMe on the OVH High Grade / Scale / Advance ranges. ZFS cannot share the same disk with LVM.
  • Root SSH access on every node in the cluster.
  • A full, tested backup BEFORE you start (PBS, LVM snapshot or vzdump export depending on the context).
  • RAM: allow at least 8 GB on the host, of which about 4 GB is reserved for the ZFS ARC (cache). See step 3 for precise tuning.

Step 1: install ZFS if needed #

On recent OVH Proxmox templates, zfsutils-linux is already installed. On older images or on a manual Debian + Proxmox installation, you have to add it explicitly.

Bash
apt update && apt install -y zfsutils-linux
modprobe zfs
zfs version

The zfs version command confirms that the kernel module is loaded. If it fails, it usually means the running kernel lacks the ZFS headers: reinstall pve-headers-$(uname -r) and reload the module.

Step 2: create the ZFS pool #

We create a ZFS storage pool named rpool (the Proxmox convention) on the dedicated disk, with two structural settings applied at creation time.

Bash
# First check you have the right target device
lsblk
parted -l

# Create the pool on the second disk
zpool create -o ashift=12 rpool /dev/sdb

# Enable compression and disable atime (performance)
zfs set compression=zstd rpool
zfs set atime=off rpool

# Create the dataset dedicated to VMs and CTs
zfs create rpool/data

Why ashift=12 #

ashift=12 aligns ZFS blocks on 4 KB (2^12 bytes), the native size of modern SSDs and NVMe drives. Without this parameter, ZFS might align on 512 bytes, cutting write performance on flash disks by a factor of 4 to 8. The parameter is fixed when the pool is created and cannot be changed afterwards, which is why it matters so much to set it from the start.

Compression: zstd rather than lz4 #

The older Proxmox documentation recommended lz4. On 6.5+ kernels with ZFS 2.2+ (standard since Proxmox 8.1), zstd at its default level 3 gives a better compression ratio for barely more CPU, and remains faster than lz4 on reads thanks to multi-threading. Unless you have a specific reason (a very weak CPU, an extreme synchronous workload), go straight to zstd.

Diagram of a ZFS pool on Proxmox with one physical NVMe disk, one vdev, the rpool/data dataset and the properties zstd compression, ashift 12, atime off
Anatomy of a ZFS pool ready for Proxmox: an rpool/data dataset on a single vdev, with zstd compression and 4K alignment locked in at creation.

Step 3: tune the ZFS ARC (RAM) #

By default, ZFS claims up to 50% of the host's total RAM for its ARC (Adaptive Replacement Cache). On an OVH server with 32 GB of RAM meant for 8 VMs, ZFS would take 16 GB at boot, leaving 16 GB for the VMs and the hypervisor itself. That is almost always too much, especially when the VMs have their own filesystem cache on the guest side.

A practical rule: set the ARC to 1 GB per TB of ZFS data actually in use, capped at 25% of the host's total RAM. With 32 GB of RAM, that gives 4 to 8 GB of ARC depending on the pool size.

Bash
# Cap the ARC at 4 GB (value in bytes: 4 * 1024^3)
echo 'options zfs zfs_arc_max=4294967296' > /etc/modprobe.d/zfs.conf
update-initramfs -u
systemctl reboot

# After the reboot, check the effective values
awk '/^c_max|^size/ {print $1, $3}' /proc/spl/kstat/zfs/arcstats

Step 4: add the storage in Proxmox #

In the web interface: Datacenter → Storage → Add → ZFS. Select the rpool/data pool, give it a readable ID (e.g. local-zfs), and tick Thin provision to save space. Do NOT tick Shared unless you have a pool that is synchronous across nodes (a rare case, such as Ceph on top of ZFS). For standard asynchronous Proxmox replication, leave this box unticked.

Step 5: migrate existing VMs #

Assumption: the OVH template created a local-lvm storage on LVM that currently hosts your VMs. The commands below move a VM (VMID 100) from LVM to the new ZFS pool, live or offline depending on the situation.

Bash
# Identify the current disks of VM 100
qm config 100 | grep -E '^(scsi|virtio|sata|ide)[0-9]'

# Online migration (VM running, copy without downtime ~ 1 to 3 GB/min)
qm move-disk 100 scsi0 local-zfs --delete

# Offline migration (VM shut down, much faster)
qm shutdown 100 && qm move-disk 100 scsi0 local-zfs --delete && qm start 100

Step 6: enable replication between nodes #

Once the VM is on ZFS, replication becomes trivial. In the web interface, open the VM → Replication → Add → choose the target node and the frequency (cron in Proxmox format: */15 = every 15 minutes, 0 */6 = every 6 hours on the hour).

Check in the logs (/var/log/pve/replicate-*) that the first replication succeeds. The first synchronisation transfers the whole dataset; later ones only push the changed blocks (a zfs send -i delta), typically 50 to 500 MB per cycle on a moderately active VM.

Diagram of asynchronous ZFS replication between two Proxmox nodes using incremental zfs send/receive, with arrows showing a delta transfer every 15 minutes
Asynchronous replication copies ZFS deltas between nodes every 15 minutes. On failover, the VM restarts on the secondary node with at most 15 minutes of data lost.

Monitoring and maintaining the pool #

A ZFS pool without monitoring is a silent trap: a corrupted disk can stay invisible for weeks before showing up in a replication or a backup, by which point it is too late to recover because the backups already contain the corrupted blocks.

Daily health check #

Bash
# Pool and disk status, and corruption detection
zpool status -v rpool

# All is well if the output shows:
#   state: ONLINE
#   errors: No known data errors

Automatic monthly scrub #

A scrub reads the entire pool and compares checksums: it is the only way to detect silent corruption (bit rot) before it spreads. Proxmox 8 ships a systemd timer, zfs-scrub-monthly@rpool.timer: check that it is enabled.

Bash
# Enable the automatic monthly scrub on the rpool pool
systemctl enable --now zfs-scrub-monthly@rpool.timer

# Run a manual scrub on demand (takes several hours on a large pool)
zpool scrub rpool

# Follow the progress
zpool status rpool

E-mail alerts on degradation #

The pool can go into a DEGRADED state (one failed disk in a mirror) or FAULTED (redundancy lost, data potentially inaccessible) without anyone noticing. Configure the zed daemon (ZFS Event Daemon) to send an e-mail on every critical state change.

Bash
# Configure zed with the e-mail address that receives the alerts
sed -i 's|^#ZED_EMAIL_ADDR=.*|ZED_EMAIL_ADDR="admin@exemple.fr"|' /etc/zfs/zed.d/zed.rc
sed -i 's|^#ZED_NOTIFY_INTERVAL_SECS=.*|ZED_NOTIFY_INTERVAL_SECS=3600|' /etc/zfs/zed.d/zed.rc
systemctl restart zfs-zed

# Test by listing events (without breaking the pool)
zpool events rpool

Classic pitfalls to avoid #

  • ashift too low: pool created without ashift=12 on SSD/NVMe. Write performance divided by 4 to 8, impossible to fix without recreating the pool.
  • ARC eating the RAM: without zfs_arc_max, the ARC can stop VMs from starting for lack of available RAM. Symptom: VMs OOM-killed with no application cause.
  • Disk added without planning: zpool add adds a new vdev (it widens the pool). If the added disk is not mirrored, the failure of that single disk destroys the entire pool. Always replicate the topology of the existing vdev.
  • No scrubs: without scrubs, silent corruption is only detected when you restore a backup… which by then also contains the corrupted blocks.
  • Replication without monitoring: replication can fail silently (network, space on the target disk, diverging datasets). Watch /var/log/pve/replicate-*.log and alert on errors.

Conclusion #

Converting an OVH template from LVM to ZFS unlocks three features that make Proxmox's high-availability promise genuinely usable: replication between nodes, atomic snapshots and periodic integrity checks. The initial effort (1 to 2 hours per node) pays for itself the first time a disk failure is avoided, or the first time a scrub catches silent corruption before it contaminates the backups.

To go further with the backup strategy that complements ZFS replication, the PBS guide covers source-side deduplication, multi-level retention and ransomware protection. And to reproduce this setup on a Hetzner server rather than OVH, the guide to installing Proxmox 8 on Hetzner leads naturally into this one.

About
01/Read nextThree articles

Related articles

Infrastructure

Exploit foundries: your firewall has become the front line

Two undergraduates in Changsha ran an automated pipeline that decrypts firmware, decompiles it through thousands of calls, forms hypotheses about vulnerabilities, writes the exploit and tests it in a lab, in a loop, day and night. More than a dozen unknown vulnerabilities in a month on network devices, some fifty organisations targeted, thirteen collection agents on a scheduled task. What this means for an SME's inventory.

cybersecurityvulnerability
Infrastructure

Intermittent SERVFAIL: the orphaned DS record that broke 15% of lookups

A CI pipeline failing intermittently, two different error messages, an obvious suspect that had nothing to do with it, and an invisible cause: a DNSSEC record still published at the parent while the zone was no longer signed. Post-mortem of a nasty DNS failure, with the method that finally settled it.

dnssecdns
Infrastructure

Patching 36 servers without unattended-upgrades

There are three doctrines: do nothing, automate everything blindly, or look first and then decide. We removed unattended-upgrades from the whole fleet and dismantled our own automatic patching with Ansible, in favour of a “visibility first” doctrine backed by PatchMon. Why, how, and the routine that goes with it.

patch managementpatchmon

What next

Infrastructure that is managed and monitored day and night?

Bastion, SIEM, patches applied on purpose, tested backups: what we apply for our clients. First conversation free.

Discuss your project

Reply within 24 working hours, from one of the two founders. Or by phone: +33 7 59 96 14 99.