Backup diario de bases de datos MySQL en Debian, Ubuntu y Linux

Publicado el Actualizado el 14 min de lectura

Un script pequeño y un cronjob bastan para guardar cada noche todas las bases de datos MySQL y MariaDB comprimidas. Incluye los puntos en los que Debian y Ubuntu se diferencian y un backup falla en silencio.

¿Usas Debian, Ubuntu u otra distribución de Linux en tu VPS, servidor root o servidor dedicado y quieres hacer una copia de seguridad automática y diaria de todas tus bases de datos MySQL y MariaDB? Entonces estás en el sitio correcto. En esta guía vas a montar un script de backup que cada noche exporta todas las bases de datos a un archivo SQL comprimido y elimina automáticamente las copias antiguas.

El procedimiento es el mismo en todas las distribuciones y funciona igual en Debian y Ubuntu que en AlmaLinux, Rocky Linux y RHEL. Las diferencias no están en el script, sino en qué servidor de bases de datos tienes en marcha y en cómo te deja iniciar sesión. Más abajo hay una sección propia sobre Debian frente a Ubuntu.

Requisitos

Necesitas un acceso SSH con permisos de root y un servidor MySQL o MariaDB ya instalado. Recomendamos las versiones actuales de cada distribución: Debian 13 "Trixie" y Debian 12 "Bookworm", Ubuntu 24.04 LTS "Noble Numbat" y Ubuntu 22.04 LTS "Jammy Jellyfish", además de AlmaLinux y Rocky Linux en sus versiones 9 y 10. Los sistemas más antiguos ya no deberías usarlos en producción: Debian 10 llegó al fin de soporte en junio de 2024, Ubuntu 20.04 LTS en mayo de 2025, y CentOS 7 tampoco recibe ya actualizaciones de seguridad regulares.

Actualiza primero el sistema e instala el editor de texto Nano si todavía no está.

Para Debian y Ubuntu:

apt update && apt upgrade -y

apt install nano -y

Para AlmaLinux, Rocky Linux y RHEL:

dnf update -y

dnf install nano -y

En los sistemas actuales de la familia RHEL, dnf es el sucesor de yum. El comando antiguo suele seguir funcionando allí como enlace, pero deberías usar dnf.

Prevé además suficiente espacio de almacenamiento. Según el tamaño de tus bases de datos, una semana de copias comprimidas ocupa enseguida varios gigabytes. El espacio libre lo compruebas con df -h.

Guardar las credenciales de forma segura

La contraseña no debe ir directamente en la llamada a mysqldump, porque las líneas de comandos son visibles para todos los usuarios del sistema a través de ps. Crea en su lugar un archivo de credenciales que solo root pueda leer:

nano /root/.my.cnf

Contenido del archivo:

[client]
user=root
password=TU_CONTRASEÑA_DE_BASE_DE_DATOS

Ajusta después los permisos para que únicamente root tenga acceso:

chmod 600 /root/.my.cnf

Cuando no existe ninguna contraseña de root

En Debian y Ubuntu, la cuenta de base de datos root no suele estar protegida por una contraseña, sino por la identidad del usuario del sistema. MariaDB llama a ese método unix_socket y MySQL lo llama auth_socket. En ese caso sencillamente no existe ninguna contraseña, y una entrada en el archivo de credenciales no sirve de nada. Esta consulta te muestra si es tu caso:

mysql -e "SELECT user, host, plugin FROM mysql.user WHERE user='root';"

Si ahí aparece unix_socket o auth_socket, tienes dos caminos: ejecutar el script como el usuario del sistema root y prescindir por completo del archivo de credenciales, o crear un usuario de backup propio. La segunda variante es la mejor, porque funciona sin los permisos completos de la cuenta root:

CREATE USER 'kh_backup'@'localhost' IDENTIFIED BY 'TU_CONTRASEÑA_DE_BACKUP';
GRANT SELECT, SHOW VIEW, EVENT, TRIGGER, LOCK TABLES, RELOAD, PROCESS ON *.* TO 'kh_backup'@'localhost';
FLUSH PRIVILEGES;

En el archivo de credenciales indicas entonces user=kh_backup. Los permisos están deliberadamente ajustados al mínimo: leer, consultar vistas, eventos, triggers y, como último recurso, bloquear las tablas que no usan InnoDB. RELOAD y PROCESS solo se pueden conceder de forma global, de ahí el ON *.*. MySQL 8 necesita PROCESS para leer de paso la información de los tablespaces. Si prefieres no concederlo, añade en el script --no-tablespaces. Y si ya no consigues entrar de ninguna manera, te ayuda el artículo Restablecer la contraseña de root de MySQL y MariaDB.

Crear el script de backup

Ahora creamos un script de Bash que se encarga de la exportación y borra las copias antiguas. En nuestro ejemplo se llama mysql_export_all.sh y está en el directorio /opt/mysqlbackups:

mkdir -p /opt/mysqlbackups

nano /opt/mysqlbackups/mysql_export_all.sh

En ese script escribes el siguiente contenido:

#!/bin/bash
set -euo pipefail

BACKUP_DIR="/opt/mysqlbackups"
KEEP_DAYS=7
DATE=$(date +%Y-%m-%d-%H-%M)

mkdir -p "$BACKUP_DIR"

mysqldump --defaults-extra-file=/root/.my.cnf --all-databases --single-transaction --routines --events | gzip > "$BACKUP_DIR/alldbs_$DATE.sql.gz"

find "$BACKUP_DIR" -type f -name "alldbs_*.sql.gz" -mtime +$KEEP_DAYS -delete

Las opciones más importantes de un vistazo:

  • --defaults-extra-file lee el usuario y la contraseña del archivo que acabas de crear. Esta opción tiene que ir en primer lugar.
  • --single-transaction genera un estado coherente en las tablas InnoDB sin bloquear la base de datos.
  • --routines y --events incluyen en la copia los procedimientos almacenados y los eventos programados. Los triggers los guarda mysqldump por su cuenta de todos modos.
  • gzip comprime la exportación y ahorra así bastante espacio de almacenamiento.
  • El comando find borra únicamente los archivos de backup con más de siete días de antigüedad. Con KEEP_DAYS ajustas el tiempo de retención.

La línea set -euo pipefail es más importante de lo que parece. Sin pipefail, la shell solo evalúa el valor de retorno de gzip, y ese es cero incluso cuando mysqldump se ha interrumpido antes. Te quedaría un archivo comprimido técnicamente impecable con un contenido incompleto, y nadie se daría cuenta.

El servidor de bases de datos que se ejecuta en tu sistema depende de la distribución. Debian no incluye ningún paquete mysql-server y apuesta íntegramente por MariaDB, mientras que Ubuntu ofrece los dos. El script funciona sin cambios en todos los casos:

Sistemamysql-servermariadb-server
Debian 13no disponible11.8
Debian 12no disponible10.11
Ubuntu 24.04 LTS8.010.11
Ubuntu 22.04 LTS8.010.6

Hacer ejecutable el script y probarlo

Da permisos de ejecución al script:

chmod +x /opt/mysqlbackups/mysql_export_all.sh

Ejecútalo una vez a mano y comprueba el resultado antes de automatizarlo:

/opt/mysqlbackups/mysql_export_all.sh

ls -lh /opt/mysqlbackups/

El archivo generado debería ocupar bastante más de cero bytes. Aun así, el final dice más que el principio. Comprueba por eso si el archivo está intacto y si la exportación llegó realmente al final:

gzip -t /opt/mysqlbackups/alldbs_*.sql.gz

zcat /opt/mysqlbackups/alldbs_*.sql.gz | tail -n 1

La última línea de una exportación completa empieza por -- Dump completed on. Si falta, la exportación se interrumpió y el archivo no vale nada como copia de seguridad, aunque ocupe varios cientos de megabytes. Esa única línea es la prueba fiable más rápida que tienes.

Configurar un cronjob para el backup diario

Abre el editor de cronjobs:

export VISUAL=nano; crontab -e

Para un backup diario a las 5 de la mañana añade la siguiente línea:

0 5 * * * /opt/mysqlbackups/mysql_export_all.sh >> /var/log/mysql-backup.log 2>&1

La salida acaba así en un archivo de log, de modo que después puedas seguir el rastro de los errores. Si el cronjob se ha guardado correctamente lo compruebas con crontab -l. El trabajo tiene que estar en la crontab de root. En muchas imágenes de Ubuntu el inicio de sesión como root está desactivado, y allí la llamada es sudo crontab -e. Si no, como usuario normal crearías tu propia crontab y el script fallaría más tarde por el archivo de credenciales de /root.

En las instalaciones mínimas de la familia RHEL falta a veces el servicio cron. Así lo instalas y lo activas:

dnf install cronie -y

systemctl enable --now crond

A partir de ahora, todas las bases de datos se exportan cada noche a las 5, y las copias con más de siete días desaparecen automáticamente. Encontrarás más sobre horarios y tropiezos habituales en Configurar un cronjob en Linux.

Restaurar un backup

Un backup solo vale algo cuando además puedes restaurarlo. Prueba por eso la restauración una vez de forma consciente, preferiblemente en un sistema de pruebas:

zcat /opt/mysqlbackups/alldbs_2026-07-26-05-00.sql.gz | mysql --defaults-extra-file=/root/.my.cnf

Si solo quieres recuperar una base de datos concreta, descomprime primero la copia y extrae el bloque que corresponda, o guarda además cada base de datos por separado con mysqldump --databases midb. Resulta más cómodo escribir directamente un archivo por base de datos. Para eso, sustituye en el script la línea de mysqldump por este bucle:

for DB in $(mysql --defaults-extra-file=/root/.my.cnf -N -B -e "SHOW DATABASES;" | grep -Ev '^(information_schema|performance_schema|sys)$'); do
  mysqldump --defaults-extra-file=/root/.my.cnf --single-transaction --routines --events "$DB" | gzip > "$BACKUP_DIR/${DB}_$DATE.sql.gz"
done

Las tres bases de datos excluidas son vistas sobre estados internos del servidor y no se pueden restaurar de forma útil. Ajusta también el patrón de búsqueda del comando find, porque si no dejará de limpiar los nuevos nombres de archivo. Para una mudanza completa a otro servidor, Migrar WordPress a un servidor nuevo muestra el procedimiento con un ejemplo práctico.

Guardar los backups fuera del servidor

Las copias que están únicamente en el mismo servidor no sirven de nada cuando el sistema entero pierde los datos. Transfiere por eso los archivos también a un segundo destino, por ejemplo con rsync o scp a otro servidor o a un almacenamiento de backup:

rsync -avz /opt/mysqlbackups/ usuario@servidor-backup:/ruta/al/backup/

Añade simplemente ese comando al final de tu script y la transferencia se ejecuta automáticamente con él. Para que eso funcione en el cronjob sin preguntas, root necesita una clave SSH sin passphrase cuya parte pública esté depositada en el sistema de destino. Cómo crearla lo explica Conectarse al servidor por SSH.

Diferencias entre Debian y Ubuntu

Ambos sistemas usan apt, los dos guardan la configuración en /etc/mysql/ y el script de arriba funciona sin cambios en cualquiera de ellos. Aun así, hay seis puntos en los que se diferencian, y cada uno de ellos puede hacer que una copia de seguridad falle en silencio:

TemaDebianUbuntu
Servidor de bases de datosúnicamente MariaDBMySQL 8.0 o MariaDB
Paquete de clientemariadb-clientmysql-client-8.0 o mariadb-client
Herramienta de copiamariadb-dump, mysqldump como enlacecon MySQL solo mysqldump
Inicio de sesión de rootunix_socketauth_socket con el MySQL del paquete
Cuenta de mantenimientodesde MariaDB 10.4 ya no se creadebian-sys-maint en /etc/mysql/debian.cnf
Log de erroresjournal, journalctl -u mariadbcon MySQL /var/log/mysql/error.log

Nombres de paquetes y herramientas

Si respaldas la base de datos desde otro equipo, allí solo necesitas el paquete de cliente. En Debian se llama mariadb-client y en Ubuntu con MySQL 8 se llama mysql-client-8.0. Para un script que deba funcionar en ambos sistemas existe el metapaquete default-mysql-client: en Debian apunta al cliente de MariaDB y en Ubuntu al cliente de MySQL.

Desde MariaDB 10.5, las herramientas llevan además un nombre con el prefijo mariadb-, y desde MariaDB 11 ese es el nombre real, mientras que mysqldump ya solo es un enlace hacia él. Allí funcionan las dos llamadas. En un sistema Ubuntu con MySQL 8, en cambio, existe únicamente mysqldump, y un script con mariadb-dump termina allí con command not found. Por eso, para los scripts que deban funcionar en los dos mundos, mysqldump es la llamada correcta.

Cuenta de mantenimiento y autenticación

En Ubuntu, el paquete mysql-server sigue creando la cuenta de mantenimiento debian-sys-maint y escribe sus credenciales en /etc/mysql/debian.cnf. Ese archivo solo lo puede leer root y se deja usar directamente para una copia de seguridad, sin ninguna preparación adicional:

mysqldump --defaults-file=/etc/mysql/debian.cnf --all-databases --single-transaction --routines --events | gzip > /opt/mysqlbackups/alldbs.sql.gz

En Debian con MariaDB a partir de la versión 10.4 esa cuenta ya no se crea de nuevo. El archivo allí suele seguir existiendo, pero por regla general solo apunta a root a través del socket. Así que no des por hecho que un script que en Ubuntu funciona con ese archivo haga lo mismo en Debian. Comprobarlo te lleva un solo paso:

mysql --defaults-file=/etc/mysql/debian.cnf -e "SELECT current_user();"

Las cuentas nuevas las crea MySQL 8 con el método caching_sha2_password y MariaDB con mysql_native_password. Para la exportación a través del socket local eso da igual, pero sí importa cuando respaldas por red desde un cliente antiguo. Si este avisa con "The server requested authentication method unknown to the client", el cliente es demasiado antiguo para MySQL 8 y toca actualizarlo.

AppArmor en Ubuntu

En Ubuntu, AppArmor está activo de fábrica y limita el servidor de bases de datos, es decir, el proceso mysqld o mariadbd. No limita la herramienta mysqldump, y esa diferencia decide si te topas con la trampa o no.

La exportación de esta guía escribe el archivo a través de la shell (| gzip > ...), o sea, con el usuario que ejecuta el script. Esa vía no se ve afectada por AppArmor y funciona en cualquier directorio en el que el usuario tenga permiso de escritura. En cuanto escribe el propio servidor, sin embargo, rigen otras reglas. Es lo que pasa con mysqldump --tab=/ruta y con cualquier SELECT ... INTO OUTFILE, porque ahí el archivo lo crea el proceso del servidor, no tu shell. Entonces actúan dos bloqueos independientes entre sí: la variable del servidor secure_file_priv y el perfil de AppArmor del servidor. En Ubuntu, esa variable apunta de fábrica a /var/lib/mysql-files/, y justo ese directorio es el que también está permitido en el perfil de AppArmor. Por eso una ruta de destino como /opt/mysqlbackups falla por partida doble.

Lo traicionero aquí es el diagnóstico. El primer bloqueo se anuncia con claridad: ERROR 1290 (HY000): The MySQL server is running with the --secure-file-priv option so it cannot execute this statement. El segundo, en cambio, aparece como un simple Errcode: 13 "Permission denied", aunque el propietario y los permisos del directorio de destino parezcan a primera vista completamente correctos. Quien entonces se pone a repartir permisos de archivo está buscando en el sitio equivocado. Comprueba en ese caso las dos cosas:

mysql -e "SHOW VARIABLES LIKE 'secure_file_priv';"

aa-status | grep -Ei 'mysqld|mariadbd'

journalctl -k | grep -i 'apparmor.*DENIED' | tail -n 20

Si en el último comando aparece una línea con apparmor="DENIED" y tu ruta de destino, ya tienes la causa. Lo más sencillo es no llegar a ese punto y quedarte con la exportación a través de la tubería (pipe). Si de verdad necesitas escrituras del lado del servidor, escribe en /var/lib/mysql-files/ y mueve el archivo después. Solo cuando ninguna de las dos opciones sirva, amplía el perfil con la ruta adicional. Para eso está previsto el archivo /etc/apparmor.d/local/usr.sbin.mysqld, en MariaDB el correspondiente usr.sbin.mariadbd, seguido de systemctl reload apparmor. Desactivar AppArmor no es una solución, sino quitar una capa de protección que asegura precisamente ese servidor.

Errores frecuentes y soluciones

La copia ocupa 0 bytes: normalmente las credenciales de /root/.my.cnf no son correctas. Prueba el inicio de sesión con mysql --defaults-extra-file=/root/.my.cnf -e "SHOW DATABASES;". Muchas veces detrás está el caso descrito arriba: la cuenta trabaja a través del socket y por eso una contraseña guardada no encaja en absoluto.

"Access denied" en el cronjob, pero no en la consola: el trabajo se ejecuta con un usuario distinto del esperado. Añade el cronjob a la crontab de root y usa en el script únicamente rutas absolutas. Más causas las recoge Solucionar el error MySQL Access denied for user.

Unknown table 'COLUMN_STATISTICS' in information_schema: estás respaldando una base de datos MariaDB con el mysqldump de MySQL 8, por ejemplo desde un equipo Ubuntu. Durante la exportación, MySQL 8 consulta una tabla que en MariaDB no existe. Añade --column-statistics=0 a la llamada. Esa opción solo la conoce el cliente de MySQL, así que en un sistema puramente MariaDB no debes ponerla.

Access denied; you need (at least one of) the PROCESS privilege(s) for this operation: en MySQL 8, el usuario de backup no puede leer la información de los tablespaces. Concede PROCESS como se describe arriba o añade --no-tablespaces.

mariadb-dump: command not found: el script viene de un sistema Debian con MariaDB 11 y ahora se ejecuta en Ubuntu con MySQL 8. Allí ese nombre no existe. Escribe mysqldump, que funciona en los dos sistemas.

Can't connect to local MySQL server through socket: el servidor no está en marcha, o el socket está en una ruta distinta de la esperada. Las posibles causas las describe Solucionar los errores de socket de MySQL.

SELinux bloquea el script (AlmaLinux, Rocky Linux, RHEL): comprueba con ausearch -m avc -ts recent si se ha registrado alguna denegación de acceso y ajusta, si hace falta, el contexto del directorio de backups.

El disco se llena: reduce KEEP_DAYS o mueve las copias antiguas a un almacenamiento externo. Qué más devora espacio lo muestra Liberar espacio en un disco lleno en Linux.

Mensaje de error sobre --single-transaction con tablas MyISAM: esta opción solo surte efecto con InnoDB. Con tablas MyISAM puedes usar en su lugar --lock-tables, que bloquea las tablas mientras dura la exportación. Ten en cuenta además que en MariaDB las tablas del sistema están en el motor Aria y --single-transaction no las cubre.

Si ya estás trabajando en la base de datos, aprovecha para lo demás: eliminar los usuarios anónimos, borrar la base de datos de prueba y desactivar el acceso remoto de root. Cómo se hace lo explica Proteger MariaDB y MySQL.

Preguntas frecuentes

¿Por qué no debe ir la contraseña de la base de datos en el comando mysqldump?
Las líneas de comandos son visibles para todos los usuarios del sistema a través del comando ps. Guarda por eso el usuario y la contraseña en el archivo /root/.my.cnf y ajusta los permisos con chmod 600, de modo que únicamente root pueda leerlo. En el script, el archivo se carga con --defaults-extra-file, y esa opción tiene que ir en primer lugar.
¿Qué diferencia a Debian y Ubuntu a la hora de respaldar bases de datos MySQL?
Sobre todo el servidor de bases de datos. Debian no incluye ningún paquete mysql-server y apuesta íntegramente por MariaDB (Debian 13 en la versión 11.8, Debian 12 en la 10.11), mientras que Ubuntu trae MySQL 8.0 y MariaDB. De ahí salen cuatro diferencias prácticas: el paquete de cliente se llama mariadb-client en vez de mysql-client-8.0; desde MariaDB 11 la herramienta se llama mariadb-dump (mysqldump se mantiene como enlace, y en MySQL 8 solo existe ese nombre); la cuenta de mantenimiento debian-sys-maint en /etc/mysql/debian.cnf ya solo la crea Ubuntu; y con MariaDB el log de errores está en el journal en lugar de en /var/log/mysql/error.log. El script de backup en sí funciona sin cambios en los dos sistemas.
AppArmor impide en Ubuntu escribir el archivo de backup. ¿Qué hago?
Comprueba primero quién escribe realmente. AppArmor limita el servidor de bases de datos, no la herramienta mysqldump. Una exportación por tubería hacia gzip la escribe la shell, así que no se ve afectada. Si en cambio el archivo lo crea el servidor, por ejemplo con mysqldump --tab o con SELECT ... INTO OUTFILE, actúan dos bloqueos: la variable secure_file_priv (en Ubuntu, de fábrica /var/lib/mysql-files/) y el perfil de AppArmor del servidor. El segundo se anuncia solo como Errcode 13 Permission denied, aunque los permisos del archivo sean correctos. Se hace visible con journalctl -k y la búsqueda de apparmor=DENIED. La solución: quedarte con la exportación por tubería, o escribir en /var/lib/mysql-files/ y mover el archivo después. Desactivar AppArmor no es una solución.
¿Funciona la guía en cualquier distribución de Linux?
Sí. El script de backup en sí es independiente de la distribución. Lo único que cambia es la gestión de paquetes: Debian y Ubuntu usan apt, mientras que AlmaLinux, Rocky Linux y RHEL usan dnf. En los sistemas actuales de la familia RHEL, dnf es el sucesor de yum. El comando antiguo suele seguir funcionando allí como enlace, pero deberías usar dnf.
¿Qué hago si la cuenta de base de datos root no tiene ninguna contraseña?
En Debian y Ubuntu, root está protegido de serie por la identidad del usuario del sistema, en MariaDB mediante unix_socket y en MySQL mediante auth_socket. En ese caso no existe ninguna contraseña, y una entrada en el archivo de credenciales no sirve de nada. Ejecuta entonces el script como el usuario del sistema root y prescinde del archivo de credenciales, o crea un usuario de backup propio con los permisos SELECT, SHOW VIEW, EVENT, TRIGGER, LOCK TABLES, RELOAD y PROCESS. Esta segunda vía es la más limpia, porque funciona sin los permisos completos de la cuenta root.
¿Cuánto tiempo se conservan los backups?
En el script de ejemplo, siete días. Los archivos más antiguos los borra automáticamente el comando find. Con la variable KEEP_DAYS del script ajustas el tiempo de retención al espacio de almacenamiento del que dispongas.
¿Basta con guardar los backups en el mismo servidor?
No. Ante un fallo del disco, un ransomware o un directorio borrado por descuido, las copias se verían afectadas igual que la propia base de datos. Una copia que corre la misma suerte que el original no es una copia. Transfiere por eso los archivos también con rsync o scp a un segundo destino, por ejemplo una Storage Box o un segundo servidor en otra ubicación.
¿Cómo restauro un backup?
Con zcat y una redirección hacia el cliente de base de datos, por ejemplo zcat /opt/mysqlbackups/alldbs_2026-07-26-05-00.sql.gz seguido de una tubería hacia mysql. Prueba la restauración una vez de forma consciente en un sistema de pruebas. Si quieres poder restaurar bases de datos concretas por separado, escribe en el script un archivo propio por cada base de datos.
El cronjob no se ejecuta o avisa de Access denied. ¿Qué puedo comprobar?
Comprueba primero con crontab -l si la entrada se guardó realmente y si está en la crontab de root. En muchas imágenes de Ubuntu el inicio de sesión como root está desactivado, y allí la llamada es sudo crontab -e. Usa en el script únicamente rutas absolutas. En las instalaciones mínimas de la familia RHEL falta a menudo el servicio cron: instala el paquete cronie y activa el servicio crond.
El backup ocupa 0 bytes. ¿A qué se debe?
Normalmente, las credenciales de /root/.my.cnf no son correctas. Prueba el inicio de sesión con mysql --defaults-extra-file=/root/.my.cnf y una consulta sencilla como SHOW DATABASES. Comprueba además si un backup aparentemente completo llegó realmente al final: la última línea de una exportación terminada empieza por -- Dump completed on. Si falta, la exportación se interrumpió y el archivo no vale nada como copia de seguridad.

Backup MySQL Backup MariaDB mysqldump Backup Cronjob MySQL Linux Debian Ubuntu