Backup diario de bases de datos MySQL en Debian, Ubuntu y Linux
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-filelee el usuario y la contraseña del archivo que acabas de crear. Esta opción tiene que ir en primer lugar.--single-transactiongenera un estado coherente en las tablas InnoDB sin bloquear la base de datos.--routinesy--eventsincluyen en la copia los procedimientos almacenados y los eventos programados. Los triggers los guardamysqldumppor su cuenta de todos modos.gzipcomprime la exportación y ahorra así bastante espacio de almacenamiento.- El comando
findborra únicamente los archivos de backup con más de siete días de antigüedad. ConKEEP_DAYSajustas 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:
| Sistema | mysql-server | mariadb-server |
|---|---|---|
| Debian 13 | no disponible | 11.8 |
| Debian 12 | no disponible | 10.11 |
| Ubuntu 24.04 LTS | 8.0 | 10.11 |
| Ubuntu 22.04 LTS | 8.0 | 10.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:
| Tema | Debian | Ubuntu |
|---|---|---|
| Servidor de bases de datos | únicamente MariaDB | MySQL 8.0 o MariaDB |
| Paquete de cliente | mariadb-client | mysql-client-8.0 o mariadb-client |
| Herramienta de copia | mariadb-dump, mysqldump como enlace | con MySQL solo mysqldump |
| Inicio de sesión de root | unix_socket | auth_socket con el MySQL del paquete |
| Cuenta de mantenimiento | desde MariaDB 10.4 ya no se crea | debian-sys-maint en /etc/mysql/debian.cnf |
| Log de errores | journal, journalctl -u mariadb | con 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?
¿Qué diferencia a Debian y Ubuntu a la hora de respaldar bases de datos MySQL?
AppArmor impide en Ubuntu escribir el archivo de backup. ¿Qué hago?
¿Funciona la guía en cualquier distribución de Linux?
¿Qué hago si la cuenta de base de datos root no tiene ninguna contraseña?
¿Cuánto tiempo se conservan los backups?
¿Basta con guardar los backups en el mismo servidor?
¿Cómo restauro un backup?
El cronjob no se ejecuta o avisa de Access denied. ¿Qué puedo comprobar?
El backup ocupa 0 bytes. ¿A qué se debe?
2024-2026 KernelHost GmbH. Todos los derechos reservados. Esta guía está protegida por derechos de autor. Su publicación en otros sitios web, aunque sea de forma parcial o modificada, no está permitida sin nuestro consentimiento por escrito. Las citas con indicación de la fuente y un enlace son muy bienvenidas.

