Percona XtraBackup copia los archivos de datos de InnoDB mientras MySQL sigue atendiendo lecturas y escrituras, y registra en paralelo los cambios que se producen durante la copia. El resultado es un backup físico consistente, mucho más rápido de hacer y de restaurar que un volcado con mysqldump en bases de datos grandes. En esta guía instalarás XtraBackup en Ubuntu 24.04 con MySQL 8.0, harás backups completos e incrementales, los enviarás a otro servidor, restaurarás uno y dejarás programada una copia diaria.

Requisitos previos

  • Un servidor con Ubuntu 24.04 LTS y MySQL 8.0 (el paquete mysql-server de Ubuntu) o Percona Server 8.0, por ejemplo un VPS de CubePath.
  • Un usuario no root con privilegios sudo.
  • Espacio libre para los backups de al menos el tamaño de /var/lib/mysql. Compruébalo con sudo du -sh /var/lib/mysql.
  • Tablas en InnoDB. XtraBackup también copia tablas MyISAM, pero las bloquea durante su copia.

Paso 1: Instalar Percona XtraBackup

Instala el paquete percona-release, que configura los repositorios oficiales de Percona:

sudo apt update
sudo apt install -y curl gnupg2 lsb-release
curl -fsSLO https://repo.percona.com/apt/percona-release_latest.generic_all.deb
sudo apt install -y ./percona-release_latest.generic_all.deb

Activa el repositorio de herramientas e instala XtraBackup 8.0 junto con zstd, que se usa para descomprimir los backups:

sudo percona-release enable-only tools release
sudo apt update
sudo apt install -y percona-xtrabackup-80 zstd

Comprueba que la versión coincide con la de MySQL:

xtrabackup --version
mysql --version

La primera línea de xtrabackup --version indica la versión de XtraBackup y la de MySQL en la que se basa. Si al hacer un backup XtraBackup avisa de que no reconoce la versión del servidor, actualiza el paquete antes de confiar en esas copias.

Paso 2: Crear el usuario de backup

XtraBackup se conecta a MySQL para coordinar la copia. Crea un usuario con los permisos mínimos que indica Percona. Entra en la consola de MySQL:

sudo mysql

Ejecuta estas sentencias sustituyendo your_backup_password:

CREATE USER 'bkpuser'@'localhost' IDENTIFIED BY 'your_backup_password';
GRANT BACKUP_ADMIN, PROCESS, RELOAD, LOCK TABLES, REPLICATION CLIENT ON *.* TO 'bkpuser'@'localhost';
GRANT SELECT ON performance_schema.log_status TO 'bkpuser'@'localhost';
GRANT SELECT ON performance_schema.keyring_component_status TO 'bkpuser'@'localhost';
EXIT;

Para no escribir la contraseña en la línea de comandos, guárdala en un archivo de opciones que solo pueda leer root:

sudo nano /etc/mysql/xtrabackup.cnf
[xtrabackup]
user=bkpuser
password=your_backup_password
sudo chmod 600 /etc/mysql/xtrabackup.cnf

Todos los comandos siguientes usan --defaults-extra-file=/etc/mysql/xtrabackup.cnf, que debe ser siempre la primera opción.

Paso 3: Hacer un backup completo

Crea el directorio de backups y lanza una copia completa. Se ejecuta como root porque necesita leer /var/lib/mysql:

sudo mkdir -p /backups/mysql
sudo xtrabackup --defaults-extra-file=/etc/mysql/xtrabackup.cnf \
  --backup \
  --parallel=4 \
  --target-dir=/backups/mysql/full

La copia termina con esta línea si todo ha ido bien:

... [Note] [MY-011825] [Xtrabackup] completed OK!

El directorio contiene, además de los datos, archivos de metadatos. xtrabackup_checkpoints indica el tipo de backup y hasta qué LSN (número de secuencia del registro de InnoDB) llega:

sudo cat /backups/mysql/full/xtrabackup_checkpoints
backup_type = full-backuped
from_lsn = 0
to_lsn = 24681935
last_lsn = 24682011
flushed_lsn = 24681935

Paso 4: Preparar el backup

Los archivos copiados no son consistentes por sí mismos: hay que aplicarles los cambios registrados durante la copia. Este paso se llama preparar y se hace una vez antes de restaurar:

sudo xtrabackup --prepare --target-dir=/backups/mysql/full

Al terminar verás de nuevo completed OK! y xtrabackup_checkpoints mostrará backup_type = full-prepared. Un backup preparado ya no admite incrementales encima, por eso en el siguiente paso se prepara de otra forma.

Paso 5: Hacer backups incrementales

Un backup incremental copia solo las páginas de InnoDB que han cambiado desde el backup anterior, lo que ahorra tiempo y espacio. Parte de un backup completo sin preparar:

sudo xtrabackup --defaults-extra-file=/etc/mysql/xtrabackup.cnf \
  --backup --target-dir=/backups/mysql/base

Horas después, crea el primer incremental tomando base como referencia:

sudo xtrabackup --defaults-extra-file=/etc/mysql/xtrabackup.cnf \
  --backup --target-dir=/backups/mysql/inc1 \
  --incremental-basedir=/backups/mysql/base

El segundo incremental toma como referencia el primero:

sudo xtrabackup --defaults-extra-file=/etc/mysql/xtrabackup.cnf \
  --backup --target-dir=/backups/mysql/inc2 \
  --incremental-basedir=/backups/mysql/inc1

Para restaurar, se prepara la base y se le aplican los incrementales en orden. Todos los pasos salvo el último llevan --apply-log-only, que evita deshacer transacciones que un incremental posterior podría completar:

sudo xtrabackup --prepare --apply-log-only --target-dir=/backups/mysql/base
sudo xtrabackup --prepare --apply-log-only --target-dir=/backups/mysql/base \
  --incremental-dir=/backups/mysql/inc1
sudo xtrabackup --prepare --target-dir=/backups/mysql/base \
  --incremental-dir=/backups/mysql/inc2

Después de esto, /backups/mysql/base contiene el estado de la base de datos en el momento de inc2 y está listo para restaurar.

Paso 6: Comprimir y enviar backups a otro servidor

Un backup en el mismo disco que la base de datos no te protege si pierdes el servidor. XtraBackup puede emitir la copia como un flujo xbstream que se envía por SSH y se desempaqueta en destino sin pasar por el disco local. El servidor remoto necesita tener instalado XtraBackup, que incluye la utilidad xbstream.

Envía un backup comprimido con zstd, sustituyendo your_user y backup_server_ip:

sudo xtrabackup --defaults-extra-file=/etc/mysql/xtrabackup.cnf \
  --backup --compress=zstd --compress-threads=4 --stream=xbstream \
  --target-dir=/tmp | \
  ssh your_user@backup_server_ip "mkdir -p ~/mysql-backup && xbstream -x -C ~/mysql-backup"

En el servidor remoto, los archivos llegan con extensión .zst. Antes de preparar el backup hay que descomprimirlo, para lo que ese servidor también necesita el paquete zstd:

xtrabackup --decompress --remove-original --parallel=4 --target-dir="$HOME/mysql-backup"

Paso 7: Restaurar un backup

La restauración sustituye el directorio de datos completo, así que requiere detener MySQL. Usa un backup ya preparado (paso 4 o paso 5).

Detén MySQL y aparta el directorio de datos actual en lugar de borrarlo:

sudo systemctl stop mysql
sudo mv /var/lib/mysql /var/lib/mysql.old
sudo mkdir /var/lib/mysql

Copia el backup al directorio de datos. --copy-back lee la ruta de datadir de la configuración de MySQL y exige que el directorio esté vacío:

sudo xtrabackup --copy-back --target-dir=/backups/mysql/full

Devuelve la propiedad de los archivos al usuario mysql y arranca el servicio:

sudo chown -R mysql:mysql /var/lib/mysql
sudo chmod 750 /var/lib/mysql
sudo systemctl start mysql

Comprueba que MySQL está activo y que tus datos están ahí:

sudo systemctl status mysql --no-pager
sudo mysql -e "SHOW DATABASES;"

Cuando hayas verificado la restauración, puedes borrar /var/lib/mysql.old. Antes, copia de ahí los binlogs si vas a hacer una recuperación a un punto en el tiempo.

Recuperación a un punto en el tiempo

Si el binlog está activo (lo está por defecto en MySQL 8.0, con archivos binlog.000NNN en el directorio de datos), puedes aplicar los cambios posteriores al backup hasta justo antes de un incidente. El backup guarda la posición exacta en la que termina:

sudo cat /backups/mysql/full/xtrabackup_binlog_info
binlog.000014	1587	

Copia los binlogs del directorio de datos antiguo a un lugar seguro:

sudo mkdir -p /root/binlogs
sudo sh -c 'cp /var/lib/mysql.old/binlog.[0-9]* /root/binlogs/'

Aplica los binlogs desde esa posición hasta la fecha deseada:

sudo mysqlbinlog --start-position=1587 --stop-datetime="2026-09-25 14:29:59" \
  /root/binlogs/binlog.000014 /root/binlogs/binlog.000015 | sudo mysql

Paso 8: Automatizar el backup diario

Un script sencillo hace un backup completo comprimido cada noche en un directorio con fecha y elimina los de más de siete días. Créalo:

sudo nano /usr/local/sbin/mysql-xtrabackup.sh
#!/usr/bin/env bash
set -euo pipefail

BACKUP_ROOT="/backups/mysql/daily"
RETENTION_DAYS=7
TARGET="${BACKUP_ROOT}/$(date +%F_%H%M)"

mkdir -p "$BACKUP_ROOT"

xtrabackup --defaults-extra-file=/etc/mysql/xtrabackup.cnf \
  --backup \
  --compress=zstd \
  --parallel=4 \
  --target-dir="$TARGET"

find "$BACKUP_ROOT" -mindepth 1 -maxdepth 1 -type d -mtime +"$RETENTION_DAYS" -exec rm -rf {} +

Hazlo ejecutable:

sudo chmod 750 /usr/local/sbin/mysql-xtrabackup.sh

Crea un servicio de systemd que lo ejecute:

sudo nano /etc/systemd/system/mysql-xtrabackup.service
[Unit]
Description=Backup de MySQL con Percona XtraBackup
After=mysql.service

[Service]
Type=oneshot
ExecStart=/usr/local/sbin/mysql-xtrabackup.sh

Y un temporizador que lo lance cada día a las 02:30:

sudo nano /etc/systemd/system/mysql-xtrabackup.timer
[Unit]
Description=Backup diario de MySQL

[Timer]
OnCalendar=*-*-* 02:30:00
Persistent=true

[Install]
WantedBy=timers.target

Activa el temporizador y lanza una ejecución manual para probarlo:

sudo systemctl daemon-reload
sudo systemctl enable --now mysql-xtrabackup.timer
sudo systemctl start mysql-xtrabackup.service

Comprueba el resultado y la próxima ejecución programada:

sudo journalctl -u mysql-xtrabackup.service -n 5 --no-pager
systemctl list-timers mysql-xtrabackup.timer

El registro debe terminar con completed OK!. Si el script falla, systemd marca el servicio como failed, lo que puedes vigilar con tu sistema de monitorización.

Solución de problemas

Access denied for user 'bkpuser'@'localhost'. Revisa la contraseña en /etc/mysql/xtrabackup.cnf y que --defaults-extra-file es la primera opción del comando; si va detrás de otra, se ignora.

Unsupported server version o avisos de versión al empezar. El servidor MySQL es más reciente que XtraBackup. Actualiza con sudo apt update && sudo apt install --only-upgrade percona-xtrabackup-80, o usa la serie de XtraBackup que corresponda a tu MySQL.

--copy-back falla con Original data directory /var/lib/mysql is not empty!. El directorio de datos debe estar vacío. Muévelo como en el paso 7 en lugar de copiar encima.

MySQL no arranca tras restaurar. Revisa sudo journalctl -u mysql -n 50 y /var/log/mysql/error.log. La causa más frecuente es no haber cambiado el propietario de los archivos a mysql o haber restaurado un backup sin preparar.

Conclusión

Has instalado Percona XtraBackup, hecho backups completos e incrementales sin detener MySQL, enviado una copia comprimida a otro servidor, restaurado un backup y programado copias diarias con un temporizador de systemd. Como siguientes pasos, envía los backups diarios a almacenamiento externo compatible con S3 con xbcloud, combina un completo semanal con incrementales diarios para ahorrar espacio y activa la retención de binlogs suficiente para cubrir el intervalo entre backups.