Backup quotidiano dei database MySQL su Debian, Ubuntu e Linux

Pubblicato il Aggiornato il 13 min di lettura

Bastano un piccolo script e un cron job per salvare ogni notte tutti i database MySQL e MariaDB in forma compressa. Compresi i punti in cui Debian e Ubuntu divergono e un backup fallisce in silenzio.

Fai girare Debian, Ubuntu o un'altra distribuzione Linux sul tuo VPS, server root o server dedicato e vuoi eseguire ogni giorno il backup automatico di tutti i database MySQL e MariaDB? Sei nel posto giusto. In questa guida configuri uno script di backup che ogni notte esporta tutti i database in un file SQL compresso e ripulisce da solo i backup più vecchi.

La procedura è identica su tutte le distribuzioni e funziona su Debian e Ubuntu esattamente come su AlmaLinux, Rocky Linux e RHEL. Le differenze non stanno nello script, ma nel server di database che gira sulla tua macchina e nel modo in cui gestisce l'accesso. Più avanti trovi una sezione dedicata: Debian a confronto con Ubuntu.

Requisiti

Ti servono un accesso SSH con permessi di root e un server MySQL o MariaDB già installato. Sono consigliate le rispettive versioni attuali delle distribuzioni: Debian 13 "Trixie" e Debian 12 "Bookworm", Ubuntu 24.04 LTS "Noble Numbat" e Ubuntu 22.04 LTS "Jammy Jellyfish", oltre ad AlmaLinux e Rocky Linux nelle versioni 9 e 10. I sistemi più vecchi non andrebbero più usati in produzione: Debian 10 ha raggiunto la fine del supporto a giugno 2024, Ubuntu 20.04 LTS a maggio 2025, e anche CentOS 7 non riceve più aggiornamenti di sicurezza regolari.

Per prima cosa porta il sistema all'ultimo stato e installa l'editor di testo Nano, se ancora manca.

Su Debian e Ubuntu:

apt update && apt upgrade -y

apt install nano -y

Su AlmaLinux, Rocky Linux e RHEL:

dnf update -y

dnf install nano -y

Sui sistemi recenti della famiglia RHEL dnf è il successore di yum. Il vecchio comando di solito funziona ancora come collegamento, ma quello da usare è dnf.

Prevedi inoltre spazio di storage sufficiente. Una settimana di backup compressi occupa in fretta diversi gigabyte, a seconda della dimensione dei database. Lo spazio libero lo controlli con df -h.

Salvare le credenziali in modo sicuro

La password non va scritta direttamente nella chiamata a mysqldump, perché le righe di comando sono visibili a tutti gli utenti del sistema tramite ps. Crea invece un file di credenziali leggibile soltanto da root:

nano /root/.my.cnf

Contenuto del file:

[client]
user=root
password=LA_TUA_PASSWORD_DATABASE

Imposta poi i permessi in modo che solo root abbia accesso:

chmod 600 /root/.my.cnf

Quando non esiste nessuna password di root

Su Debian e Ubuntu l'account di database root di norma non è protetto da una password, ma dall'identità dell'utente di sistema. MariaDB chiama questo metodo unix_socket, MySQL lo chiama auth_socket. In quel caso una password semplicemente non esiste, e una voce nel file di credenziali resta senza effetto. Questa query ti dice se è la tua situazione:

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

Se compare unix_socket oppure auth_socket, puoi eseguire lo script come utente di sistema root e rinunciare del tutto al file di credenziali, oppure creare un utente dedicato al backup. La seconda strada è la migliore, perché fa a meno dei permessi completi dell'account root:

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

Nel file di credenziali indichi poi user=kh_backup. I permessi sono volutamente ridotti: lettura, viste, eventi, trigger e, come rete di sicurezza, il blocco delle tabelle che non usano InnoDB. RELOAD e PROCESS si possono assegnare solo a livello globale, da qui ON *.*. PROCESS serve a MySQL 8 per leggere anche le informazioni sui tablespace. Se preferisci non concederlo, aggiungi invece --no-tablespaces nello script. Se non riesci più ad accedere in nessun modo, ti aiuta l'articolo Reimpostare la password di root di MySQL e MariaDB.

Creare lo script di backup

Ora creiamo uno script Bash che si occupa dell'esportazione e cancella i backup vecchi. Nel nostro esempio si chiama mysql_export_all.sh e si trova nella directory /opt/mysqlbackups:

mkdir -p /opt/mysqlbackups

nano /opt/mysqlbackups/mysql_export_all.sh

In questo script scrivi il contenuto seguente:

#!/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

Le opzioni più importanti in sintesi:

  • --defaults-extra-file legge utente e password dal file appena creato. Questa opzione deve stare per prima.
  • --single-transaction produce uno stato coerente delle tabelle InnoDB senza bloccare il database.
  • --routines e --events includono nel backup le stored procedure e gli eventi pianificati. I trigger mysqldump li salva comunque da sé.
  • gzip comprime l'esportazione e fa risparmiare parecchio storage.
  • Il comando find cancella soltanto i file di backup più vecchi di sette giorni. Con KEEP_DAYS adatti la durata di conservazione.

La riga set -euo pipefail conta più di quanto sembri. Senza pipefail la shell valuta solo il valore di ritorno di gzip, che resta zero anche quando mysqldump si è interrotto prima. Ti ritroveresti con un archivio tecnicamente integro ma dal contenuto incompleto, e nessuno se ne accorgerebbe.

Quale server di database gira sulla tua macchina dipende dalla distribuzione. Debian non contiene alcun pacchetto mysql-server e punta interamente su MariaDB, mentre Ubuntu li offre entrambi. Lo script funziona in ogni caso senza modifiche:

Sistemamysql-servermariadb-server
Debian 13non disponibile11.8
Debian 12non disponibile10.11
Ubuntu 24.04 LTS8.010.11
Ubuntu 22.04 LTS8.010.6

Rendere lo script eseguibile e provarlo

Rendi lo script eseguibile:

chmod +x /opt/mysqlbackups/mysql_export_all.sh

Eseguilo una volta a mano e controlla il risultato prima di automatizzarlo:

/opt/mysqlbackups/mysql_export_all.sh

ls -lh /opt/mysqlbackups/

Il file creato deve essere nettamente più grande di zero byte. Più dell'inizio, però, conta la fine. Verifica quindi che l'archivio sia intatto e che l'esportazione sia arrivata davvero in fondo:

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

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

L'ultima riga di un'esportazione completa inizia con -- Dump completed on. Se manca, l'esportazione si è interrotta e il file non vale nulla come backup, anche se pesa diverse centinaia di megabyte. Quella singola riga è il test affidabile più rapido che hai a disposizione.

Configurare il cron job per il backup quotidiano

Apri l'editor dei cron job:

export VISUAL=nano; crontab -e

Per un backup quotidiano alle 5 del mattino aggiungi questa riga:

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

Così l'output finisce in un file di log e puoi ricostruire gli errori in un secondo momento. Se il cron job sia stato salvato correttamente lo verifichi con crontab -l. La voce deve trovarsi nella crontab di root. Su molte immagini Ubuntu l'accesso come root è disattivato, lì il comando è sudo crontab -e. Da utente normale creeresti altrimenti una crontab tua, e lo script si bloccherebbe poi sul file di credenziali sotto /root.

Sulle installazioni minimali della famiglia RHEL a volte manca il servizio cron. Lo installi e lo attivi così:

dnf install cronie -y

systemctl enable --now crond

Da adesso tutti i database vengono esportati ogni notte alle 5, e i backup più vecchi di sette giorni spariscono automaticamente. Trovi altro su pianificazione e trappole tipiche in Configurare un cron job su Linux.

Ripristinare un backup

Un backup vale qualcosa solo quando riesci anche a rimetterlo in produzione. Prova quindi il ripristino una volta di proposito, meglio se su un sistema di test:

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

Se vuoi recuperare un solo database, decomprimi prima il backup ed estrai la sezione che ti serve, oppure salva in più i singoli database separatamente con mysqldump --databases miodb. È più comodo scrivere subito un file per ogni database. Per farlo, sostituisci nello script la riga mysqldump con questo ciclo:

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

I tre database esclusi sono viste sullo stato interno del server e non si possono ripristinare in modo sensato. Adatta anche il pattern di ricerca nel comando find, altrimenti non ripulisce più i nuovi nomi di file. Per il trasferimento completo su un altro server, Trasferire WordPress su un nuovo server mostra la procedura con un esempio pratico.

Conservare i backup fuori dal server

I backup che restano soltanto sullo stesso server non aiutano quando perdi l'intero sistema. Trasferisci quindi i file anche verso una seconda destinazione, per esempio con rsync o scp su un altro server o su uno storage di backup:

rsync -avz /opt/mysqlbackups/ utente@destinazione-backup:/percorso/del/backup/

Aggiungi semplicemente questo comando in fondo al tuo script, così il trasferimento parte in automatico. Perché funzioni nel cron job senza richieste interattive, a root serve una chiave SSH senza passphrase, la cui parte pubblica sia depositata sul sistema di destinazione. Come crearla lo trovi in Collegarsi al server via SSH.

Differenze tra Debian e Ubuntu

Entrambi i sistemi usano apt, entrambi salvano la configurazione sotto /etc/mysql/, e lo script qui sopra gira su tutti e due senza modifiche. Ci sono comunque sei punti in cui divergono, e ognuno di essi può far fallire un backup in silenzio:

ArgomentoDebianUbuntu
Server di databasesolo MariaDBMySQL 8.0 o MariaDB
Pacchetto clientmariadb-clientmysql-client-8.0 o mariadb-client
Strumento di backupmariadb-dump, mysqldump come collegamentocon MySQL solo mysqldump
Accesso di rootunix_socketauth_socket con MySQL dal pacchetto
Account di manutenzioneda MariaDB 10.4 non viene più creatodebian-sys-maint in /etc/mysql/debian.cnf
Log degli errorijournal, journalctl -u mariadbcon MySQL /var/log/mysql/error.log

Nomi dei pacchetti e strumenti

Se esegui il backup del database da un'altra macchina, lì ti serve solo il pacchetto client. Su Debian si chiama mariadb-client, su Ubuntu con MySQL 8 si chiama mysql-client-8.0. Per uno script che deve girare su entrambi i sistemi esiste il metapacchetto default-mysql-client: su Debian punta al client MariaDB, su Ubuntu al client MySQL.

Da MariaDB 10.5 gli strumenti portano in più un nome con il prefisso mariadb-, da MariaDB 11 quello è il nome vero e proprio e mysqldump è soltanto un collegamento. Lì funzionano entrambe le chiamate. Su un sistema Ubuntu con MySQL 8 esiste invece unicamente mysqldump, e uno script che usa mariadb-dump finisce con command not found. Per gli script che devono girare in entrambi i mondi la chiamata giusta è quindi mysqldump.

Account di manutenzione e autenticazione

Su Ubuntu il pacchetto mysql-server continua a creare l'account di manutenzione debian-sys-maint e ne scrive le credenziali in /etc/mysql/debian.cnf. Questo file è leggibile solo da root e si può usare direttamente per un backup, senza altri preparativi:

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

Su Debian con MariaDB dalla versione 10.4 questo account non viene più creato. Il file di solito esiste ancora, ma in genere rimanda semplicemente a root tramite il socket. Non dare quindi per scontato che uno script che su Ubuntu funziona con questo file faccia lo stesso su Debian. Puoi verificarlo in un passaggio:

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

MySQL 8 crea i nuovi account con il metodo caching_sha2_password, MariaDB con mysql_native_password. Per l'esportazione tramite il socket locale non fa differenza, ma la fa se esegui il backup dalla rete con un client più vecchio. Se questo segnala "The server requested authentication method unknown to the client", il client è troppo vecchio per MySQL 8 e va aggiornato.

AppArmor su Ubuntu

Su Ubuntu AppArmor è attivo di serie e limita il server di database, cioè il processo mysqld oppure mariadbd. Non limita lo strumento mysqldump, e questa differenza decide se finirai nella trappola oppure no.

L'esportazione descritta in questa guida scrive il file tramite la shell (| gzip > ...), quindi con l'utente che esegue lo script. Questa strada non è toccata da AppArmor e funziona in qualunque directory su cui l'utente possa scrivere. Non appena però è il server stesso a scrivere, valgono altre regole. È il caso di mysqldump --tab=/percorso e di ogni SELECT ... INTO OUTFILE, perché lì il file lo crea il processo del server, non la tua shell. Entrano allora in gioco due blocchi indipendenti tra loro: la variabile di server secure_file_priv e il profilo AppArmor del server. Su Ubuntu la variabile è impostata di serie su /var/lib/mysql-files/, ed è esattamente questa la directory autorizzata anche nel profilo AppArmor. Un percorso di destinazione come /opt/mysqlbackups fallisce quindi due volte.

La parte insidiosa è la diagnosi. Il primo blocco si annuncia chiaramente con ERROR 1290 (HY000): The MySQL server is running with the --secure-file-priv option so it cannot execute this statement. Il secondo compare invece come un semplice Errcode: 13 "Permission denied", anche se proprietario e permessi della directory di destinazione a prima vista sono perfettamente a posto. Chi a quel punto si mette a cambiare i permessi dei file sta cercando nel posto sbagliato. In questo caso controlla entrambe le cose:

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

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

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

Se nell'ultimo comando compare una riga con apparmor="DENIED" e il tuo percorso di destinazione, hai trovato la causa. La cosa più semplice è non arrivarci proprio e restare sull'esportazione tramite pipe. Se ti servono davvero scritture lato server, scrivi in /var/lib/mysql-files/ e sposta il file subito dopo. Solo se nessuna delle due strade è praticabile, estendi il profilo con il percorso aggiuntivo. A questo serve il file /etc/apparmor.d/local/usr.sbin.mysqld, con MariaDB corrispondentemente usr.sbin.mariadbd, seguito da systemctl reload apparmor. Disattivare AppArmor non è una soluzione, ma la rimozione di uno strato di protezione che tutela proprio questo server.

Errori frequenti e soluzioni

Il backup è di 0 byte: quasi sempre le credenziali in /root/.my.cnf non sono corrette. Prova l'accesso con mysql --defaults-extra-file=/root/.my.cnf -e "SHOW DATABASES;". Spesso dietro c'è il caso descritto sopra: l'account lavora tramite socket, quindi una password memorizzata non combacia affatto.

"Access denied" nel cron ma non da console: il cron job gira con un utente diverso da quello previsto. Inserisci il job nella crontab di root e usa nello script solo percorsi assoluti. Altre cause le raccoglie Risolvere MySQL Access denied for user.

Unknown table 'COLUMN_STATISTICS' in information_schema: stai salvando un database MariaDB con il mysqldump di MySQL 8, per esempio da una macchina Ubuntu. Durante l'esportazione MySQL 8 interroga una tabella che in MariaDB non esiste. Aggiungi --column-statistics=0 alla chiamata. L'opzione la conosce solo il client MySQL, su un sistema puramente MariaDB non devi impostarla.

Access denied; you need (at least one of) the PROCESS privilege(s) for this operation: sotto MySQL 8 l'utente di backup non può leggere le informazioni sui tablespace. Assegna PROCESS come descritto sopra oppure aggiungi --no-tablespaces.

mariadb-dump: command not found: lo script viene da un sistema Debian con MariaDB 11 e ora gira su Ubuntu con MySQL 8, dove quel nome non esiste. Scrivi mysqldump, che funziona su entrambi i sistemi.

Can't connect to local MySQL server through socket: il server non è in esecuzione, oppure il socket si trova in un percorso diverso da quello previsto. Le possibili cause le descrive Risolvere gli errori di socket MySQL.

SELinux blocca lo script (AlmaLinux, Rocky Linux, RHEL): controlla con ausearch -m avc -ts recent se è stato registrato un rifiuto di accesso e adatta, se necessario, il contesto della directory di backup.

Il disco si riempie: riduci KEEP_DAYS oppure sposta i backup più vecchi su uno storage esterno. Che cos'altro divora spazio lo mostra Liberare spazio su Linux quando il disco è pieno.

Messaggio di errore su --single-transaction con tabelle MyISAM: questa opzione ha effetto solo su InnoDB. Con le tabelle MyISAM puoi usare invece --lock-tables, così le tabelle restano bloccate per tutta la durata dell'esportazione. Tieni presente inoltre che in MariaDB le tabelle di sistema si trovano nel motore Aria e non vengono coperte da --single-transaction.

Se comunque stai già mettendo mano al database, porta a casa anche il resto: rimuovi gli utenti anonimi, cancella il database di test e disattiva l'accesso remoto per root. Come si fa lo trovi in Mettere in sicurezza MariaDB e MySQL.

Domande frequenti

Perché la password del database non deve stare nel comando mysqldump?
Le righe di comando sono visibili a tutti gli utenti del sistema tramite il comando ps. Salva quindi utente e password nel file /root/.my.cnf e imposta i permessi con chmod 600, così soltanto root può leggerlo. Nello script il file viene caricato con --defaults-extra-file, e questa opzione deve stare per prima.
Che cosa cambia tra Debian e Ubuntu nel backup dei database MySQL?
Soprattutto il server di database. Debian non contiene alcun pacchetto mysql-server e punta interamente su MariaDB (Debian 13 nella versione 11.8, Debian 12 nella 10.11), mentre Ubuntu fornisce MySQL 8.0 e MariaDB. Ne derivano quattro differenze pratiche: il pacchetto client si chiama mariadb-client e non mysql-client-8.0, da MariaDB 11 lo strumento si chiama mariadb-dump (mysqldump resta come collegamento, mentre sotto MySQL 8 esiste solo quel nome), l'account di manutenzione debian-sys-maint in /etc/mysql/debian.cnf ormai lo crea soltanto Ubuntu, e il log degli errori di MariaDB finisce nel journal invece che in /var/log/mysql/error.log. Lo script di backup vero e proprio gira senza modifiche su entrambi i sistemi.
Su Ubuntu AppArmor impedisce la scrittura del file di backup. Che cosa faccio?
Controlla prima di tutto chi scrive davvero. AppArmor limita il server di database, non lo strumento mysqldump. Un'esportazione inviata in pipe a gzip viene scritta dalla shell e quindi non è interessata. Se invece è il server a creare il file, per esempio con mysqldump --tab oppure SELECT ... INTO OUTFILE, entrano in gioco due blocchi: la variabile secure_file_priv (su Ubuntu di serie /var/lib/mysql-files/) e il profilo AppArmor del server. Il secondo si manifesta solo come Errcode 13 Permission denied, anche se i permessi dei file sono corretti. Lo rendi visibile con journalctl -k cercando apparmor=DENIED. Soluzione: restare sull'esportazione in pipe oppure scrivere in /var/lib/mysql-files/ e spostare il file subito dopo. Disattivare AppArmor non è una soluzione.
La guida funziona su qualsiasi distribuzione Linux?
Sì. Lo script di backup in sé è indipendente dalla distribuzione. Cambia solo la gestione dei pacchetti: Debian e Ubuntu usano apt, mentre AlmaLinux, Rocky Linux e RHEL usano dnf. Sui sistemi recenti della famiglia RHEL dnf è il successore di yum. Il vecchio comando di solito funziona ancora come collegamento, ma quello da usare è dnf.
Che cosa faccio se l'account di database root non ha nessuna password?
Su Debian e Ubuntu root è protetto per impostazione predefinita dall'identità dell'utente di sistema, con unix_socket in MariaDB e con auth_socket in MySQL. In quel caso una password non esiste, e una voce nel file di credenziali resta senza effetto. Puoi eseguire lo script come utente di sistema root e rinunciare al file di credenziali, oppure creare un utente di backup dedicato con i permessi SELECT, SHOW VIEW, EVENT, TRIGGER, LOCK TABLES, RELOAD e PROCESS. Questa è la strada più pulita, perché fa a meno dei permessi completi dell'account root.
Per quanto tempo vengono conservati i backup?
Nello script di esempio sette giorni. I file più vecchi li cancella automaticamente il comando find. Con la variabile KEEP_DAYS nello script adatti la durata di conservazione allo spazio di storage che hai a disposizione.
Basta conservare i backup sullo stesso server?
No. In caso di guasto del disco, di un ransomware o di una directory cancellata per sbaglio, i backup sarebbero colpiti esattamente come il database stesso. Un backup che condivide la sorte dell'originale non è un backup. Trasferisci quindi i file anche verso una seconda destinazione con rsync o scp, per esempio una Storage Box oppure un secondo server in un'altra sede.
Come ripristino un backup?
Con zcat e una redirezione verso il client di database, per esempio zcat /opt/mysqlbackups/alldbs_2026-07-26-05-00.sql.gz seguito da una pipe verso mysql. Meglio provare il ripristino una volta di proposito su un sistema di test. Se vuoi poter ripristinare i singoli database separatamente, nello script conviene scrivere un file per ogni database.
Il cron job non parte oppure segnala Access denied. Che cosa posso controllare?
Verifica prima con crontab -l che la voce sia stata davvero salvata e che si trovi nella crontab di root. Su molte immagini Ubuntu l'accesso come root è disattivato, lì il comando è sudo crontab -e. Usa nello script soltanto percorsi assoluti. Sulle installazioni minimali della famiglia RHEL manca spesso il servizio cron: installa il pacchetto cronie e attiva il servizio crond.
Il backup è di 0 byte. Da che cosa dipende?
Quasi sempre le credenziali in /root/.my.cnf non sono corrette. Prova l'accesso con mysql --defaults-extra-file=/root/.my.cnf e una query semplice come SHOW DATABASES. Controlla inoltre se un backup apparentemente completo sia arrivato davvero in fondo: l'ultima riga di un'esportazione conclusa inizia con -- Dump completed on. Se manca, l'esportazione si è interrotta e il file non vale nulla come backup.

Backup MySQL Backup MariaDB mysqldump Backup Cron job MySQL Linux Debian Ubuntu