ZFS es a la vez un sistema de archivos y un gestor de volúmenes: agrupa discos en un pool, verifica cada bloque con sumas de comprobación, repara datos corruptos a partir de la redundancia y crea snapshots instantáneos. En Linux se usa a través de OpenZFS. En este tutorial instalarás ZFS en Ubuntu 24.04, crearás un pool en espejo con dos discos, configurarás datasets con compresión y cuotas, y automatizarás snapshots, réplicas y la verificación periódica de los datos.

Requisitos previos

  • Un servidor con Ubuntu 24.04 LTS, por ejemplo un servidor dedicado o un VPS de CubePath con volúmenes adicionales.
  • Un usuario no root con privilegios sudo.
  • Dos discos vacíos del mismo tamaño para el pool, distintos del disco del sistema. En los ejemplos aparecen como /dev/vdb y /dev/vdc.
  • Al menos 2 GB de RAM. ZFS usa la memoria libre como caché (ARC) y la libera cuando otros procesos la necesitan.
  • Para el paso 7, un segundo servidor con ZFS al que tengas acceso por SSH con clave.

Paso 1: Instalar ZFS

Ubuntu incluye el módulo del kernel de ZFS en sus kernels oficiales, así que solo necesitas las herramientas de usuario:

sudo apt update
sudo apt install zfsutils-linux

Comprueba que el módulo está cargado y la versión instalada:

zfs version
zfs-2.2.2-0ubuntu9.1
zfs-kmod-2.2.2-0ubuntu9

En Debian 12 el módulo no viene incluido: instala zfs-dkms y zfsutils-linux desde el repositorio contrib, que compila el módulo para tu kernel. En Rocky Linux 9 se usa el repositorio de OpenZFS (zfs-release).

Paso 2: Identificar los discos

Localiza los discos vacíos:

lsblk -o NAME,SIZE,TYPE,MOUNTPOINTS
NAME    SIZE TYPE MOUNTPOINTS
vda      40G disk
├─vda1   39G part /
...
vdb     100G disk
vdc     100G disk

Los nombres /dev/sdX o /dev/vdX pueden cambiar entre arranques. Para crear el pool es preferible usar los nombres estables de /dev/disk/by-id/:

ls -l /dev/disk/by-id/ | grep -E 'vdb|vdc'
lrwxrwxrwx 1 root root  9 Sep 25 10:00 virtio-disk-data1 -> ../../vdb
lrwxrwxrwx 1 root root  9 Sep 25 10:00 virtio-disk-data2 -> ../../vdc

En discos físicos verás nombres como ata-WDC_WD40EFRX-..._WD-XXXX o nvme-Samsung_SSD_.... Si tu VPS no muestra entradas en by-id, usa /dev/vdb y /dev/vdc; ZFS identifica los discos por sus etiquetas internas al importar el pool.

Paso 3: Crear un pool en espejo

Un espejo (mirror) guarda una copia completa de los datos en cada disco y tolera la pérdida de uno de ellos. Crea el pool tank:

sudo zpool create -o ashift=12 -O compression=lz4 -O atime=off -O xattr=sa \
  -O mountpoint=/tank tank mirror \
  /dev/disk/by-id/virtio-disk-data1 /dev/disk/by-id/virtio-disk-data2
  • -o ashift=12 alinea las escrituras a sectores de 4 KiB, correcto para casi cualquier disco moderno. No se puede cambiar después.
  • -O compression=lz4 activa una compresión muy rápida que suele ahorrar espacio y E/S.
  • -O atime=off evita escribir la hora de acceso en cada lectura.
  • -O xattr=sa guarda los atributos extendidos de forma más eficiente.

Las propiedades con -O se heredan en todos los datasets que crees dentro del pool. Comprueba el estado:

zpool status tank
  pool: tank
 state: ONLINE
config:

	NAME                   STATE     READ WRITE CKSUM
	tank                   ONLINE       0     0     0
	  mirror-0             ONLINE       0     0     0
	    virtio-disk-data1  ONLINE       0     0     0
	    virtio-disk-data2  ONLINE       0     0     0

errors: No known data errors

Con cuatro o más discos puedes usar raidz1 (tolera un fallo), raidz2 (dos fallos) o varios espejos (mirror a b mirror c d). Para bases de datos y máquinas virtuales, los espejos ofrecen mejor rendimiento aleatorio.

Paso 4: Crear datasets

Los datasets son sistemas de archivos independientes dentro del pool, cada uno con sus propias propiedades, cuotas y snapshots. Crea uno para las copias de seguridad y otro para una base de datos:

sudo zfs create tank/backups
sudo zfs create -o recordsize=16K tank/mysql

recordsize=16K coincide con el tamaño de página de InnoDB y mejora el rendimiento de MySQL. El valor por defecto, 128K, es adecuado para archivos grandes.

Limita el espacio que puede ocupar tank/backups y cambia su compresión a zstd, más lenta pero con mayor ratio:

sudo zfs set quota=50G tank/backups
sudo zfs set compression=zstd tank/backups

Comprueba los datasets y sus propiedades:

zfs list -o name,used,avail,quota,compression,mountpoint
NAME           USED  AVAIL  QUOTA  COMPRESS  MOUNTPOINT
tank           756K  96.4G   none  lz4       /tank
tank/backups    96K  50.0G    50G  zstd      /tank/backups
tank/mysql      96K  96.4G   none  lz4       /tank/mysql

Los datasets se montan automáticamente en el arranque. Copia algunos datos para comprobar la compresión:

sudo cp -r /usr/share/doc /tank/backups/
zfs get compressratio tank/backups
NAME          PROPERTY       VALUE  SOURCE
tank/backups  compressratio  3.12x  -

Paso 5: Trabajar con snapshots

Un snapshot es una copia de solo lectura de un dataset en un momento dado. Se crea al instante y solo ocupa espacio a medida que los datos cambian. Crea uno:

sudo zfs snapshot tank/backups@antes-de-limpiar

Simula un error borrando datos:

sudo rm -rf /tank/backups/doc

Los snapshots son accesibles en el directorio oculto .zfs/snapshot, desde donde puedes recuperar archivos sueltos:

ls /tank/backups/.zfs/snapshot/antes-de-limpiar/
doc

Para devolver el dataset completo al estado del snapshot, usa rollback. Se pierden todos los cambios posteriores al snapshot:

sudo zfs rollback tank/backups@antes-de-limpiar
ls /tank/backups
doc

Lista los snapshots existentes:

zfs list -t snapshot
NAME                            USED  AVAIL  REFER  MOUNTPOINT
tank/backups@antes-de-limpiar     0B      -  45.2M  -

Paso 6: Automatizar snapshots con Sanoid

Sanoid crea y elimina snapshots según una política de retención. Instálalo desde los repositorios de Ubuntu:

sudo apt install sanoid

Crea su archivo de configuración:

sudo mkdir -p /etc/sanoid
sudo nano /etc/sanoid/sanoid.conf

Esta configuración mantiene 36 snapshots horarios, 30 diarios y 3 mensuales de tank y de todos sus datasets hijos:

[tank]
        use_template = production
        recursive = yes

[template_production]
        frequently = 0
        hourly = 36
        daily = 30
        monthly = 3
        yearly = 0
        autosnap = yes
        autoprune = yes

El paquete incluye un temporizador de systemd que ejecuta Sanoid cada 15 minutos. Asegúrate de que está activo:

sudo systemctl enable --now sanoid.timer
systemctl list-timers sanoid.timer

Fuerza una ejecución y comprueba que aparecen los snapshots:

sudo sanoid --cron --verbose
zfs list -t snapshot -o name | grep autosnap | head
tank@autosnap_2026-09-25_10:15:02_monthly
tank@autosnap_2026-09-25_10:15:02_daily
tank@autosnap_2026-09-25_10:15:02_hourly
tank/backups@autosnap_2026-09-25_10:15:02_monthly
...

Paso 7: Replicar un dataset a otro servidor

zfs send convierte un snapshot en un flujo de datos y zfs receive lo reconstruye en otro pool, conservando propiedades y snapshots. Es la forma nativa de hacer copias de seguridad fuera del servidor.

En el servidor de destino (backup.your_domain), que debe tener un pool llamado backup, crea el dataset contenedor:

sudo zfs create backup/replicas

En el servidor de origen, crea un snapshot y envíalo completo. La opción -u evita que el dataset recibido se monte en el destino:

sudo zfs snapshot tank/mysql@repl-1
sudo zfs send tank/mysql@repl-1 | ssh [email protected]_domain zfs receive -u backup/replicas/mysql

Los envíos siguientes solo transfieren los bloques que han cambiado entre dos snapshots (-i):

sudo zfs snapshot tank/mysql@repl-2
sudo zfs send -i tank/mysql@repl-1 tank/mysql@repl-2 | ssh [email protected]_domain zfs receive -u backup/replicas/mysql

Comprueba en el destino que los dos snapshots han llegado:

ssh [email protected]_domain zfs list -t snapshot -r backup/replicas/mysql
NAME                              USED  AVAIL  REFER  MOUNTPOINT
backup/replicas/mysql@repl-1       56K      -   104K  -
backup/replicas/mysql@repl-2        0B      -   104K  -

Para automatizarlo, el paquete sanoid incluye syncoid, que calcula los snapshots incrementales por ti. Esta orden hace la misma réplica y se puede programar con cron o un temporizador de systemd:

sudo syncoid tank/mysql [email protected]_domain:backup/replicas/mysql

Paso 8: Verificar los discos con scrub

Un scrub lee todos los bloques del pool, verifica sus sumas de comprobación y repara los datos dañados usando la otra copia del espejo. Lánzalo manualmente:

sudo zpool scrub tank
zpool status tank | grep scan
  scan: scrub repaired 0B in 00:00:12 with 0 errors on Fri Sep 25 10:30:14 2026

Ubuntu ya programa un scrub mensual de todos los pools. Comprueba la tarea que instala el paquete:

cat /etc/cron.d/zfsutils-linux

Si un disco falla, zpool status mostrará el pool como DEGRADED. Tras instalar el disco nuevo, sustitúyelo en el pool y ZFS reconstruirá la copia (resilver):

sudo zpool replace tank /dev/disk/by-id/virtio-disk-data2 /dev/disk/by-id/virtio-disk-data3

Paso 9: Limitar la memoria de la caché ARC (opcional)

Por defecto, ARC puede usar hasta la mitad de la RAM en Ubuntu 24.04. En un servidor que también ejecuta aplicaciones con mucha memoria, como una base de datos, conviene limitarla. Para fijar un máximo de 4 GiB (4 × 1024³ = 4294967296 bytes), aplícalo en caliente:

echo 4294967296 | sudo tee /sys/module/zfs/parameters/zfs_arc_max

Hazlo persistente creando /etc/modprobe.d/zfs.conf:

sudo nano /etc/modprobe.d/zfs.conf
options zfs zfs_arc_max=4294967296

Comprueba el tamaño máximo actual de ARC:

arc_summary -s arc | grep -i "max size"
        Max size (high water):                         16:1    4.0 GiB

Solución de problemas

  • zpool create falla con /dev/vdb is in use o contains a filesystem: el disco tiene una tabla de particiones o datos anteriores. Si estás seguro de que puede borrarse, ejecuta sudo wipefs -a /dev/vdb y repite.
  • El pool no aparece tras reiniciar: impórtalo con sudo zpool import tank. Si el problema se repite, comprueba que zfs-import-cache.service y zfs-mount.service están habilitados.
  • zfs receive falla con destination has been modified: el dataset de destino ha cambiado desde el último snapshot recibido, normalmente porque estaba montado. Recibe siempre con -u o añade -F al receive para descartar esos cambios en el destino.

Conclusión

Tienes un pool ZFS en espejo con datasets comprimidos, snapshots automáticos con Sanoid, réplicas incrementales a otro servidor y scrubs periódicos. Como siguientes pasos puedes programar syncoid con un temporizador de systemd para tener copias externas diarias, crear volúmenes de bloques (zfs create -V) para máquinas virtuales o activar el cifrado nativo en datasets nuevos con -o encryption=on.