Reimpostare la password di root di MySQL e MariaDB
Password di root dimenticata? Spesso non ti serve affatto. E se serve davvero, ecco come aprire il database per un solo minuto senza consegnarlo a mezzo internet.
La password di root del database è una di quelle password che si impostano una volta durante l'installazione e poi non servono mai più, perché ogni applicazione lavora con un account proprio. Fino al giorno in cui invece serve davvero. E allora compare questo:
ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: YES)
La risposta standard che si trova in rete è: fermi il servizio, lo riavvii con --skip-grant-tables, imposti la password e hai finito. È vero, ma è solo metà della storia. In questa guida trovi anche perché il comando copiato ovunque non ha alcun effetto su Ubuntu con MySQL 8, perché in molti casi non ti serve nessuna password e cosa fare se dopo il servizio non parte più.
Prima una verifica: probabilmente non ti serve nessuna password
Su Debian e Ubuntu il root del database non è più protetto da una password ormai da anni, ma dall'identità dell'utente di sistema. MariaDB chiama questo meccanismo unix_socket, MySQL lo chiama auth_socket. Il concetto è lo stesso: chi si collega attraverso il socket locale ed è già root a livello di sistema operativo entra senza password. La logica dietro è semplice: un utente di sistema root arriva comunque a tutti i file di dati e alla RAM del processo, quindi una password aggiuntiva non rappresenta alcun ostacolo.
Il primo tentativo dovrebbe quindi essere sempre questo:
sudo mariadb
sudo mysql
Attenzione: sudo mysql -u root -p non funziona, perché il -p commuta sull'autenticazione tramite password. È proprio qui che si blocca la maggior parte delle persone. Senza -p e con sudo di norma finisci direttamente nel prompt. Da lì reimposti la password in due secondi, senza nemmeno toccare il database.
La seconda via di uscita è l'account di manutenzione della distribuzione. Su Ubuntu con il pacchetto mysql-server esiste ancora il file /etc/mysql/debian.cnf, che contiene le credenziali di un account con tutti i permessi ed è leggibile solo da root:
sudo ls -l /etc/mysql/debian.cnf
sudo mysql --defaults-file=/etc/mysql/debian.cnf -e "SELECT current_user();"
Se torna indietro una riga, sei dentro e puoi proseguire subito. Non aspettarti però root@localhost: la query risponde con debian-sys-maint@localhost. È corretto così, perché questo account di manutenzione ha ALL PRIVILEGES ed è più che sufficiente per il reset, ma con esso tu non sei root. Questa strada è inoltre specifica di Debian e Ubuntu. Su AlmaLinux, Rocky Linux e Oracle Linux non esistono né la directory /etc/mysql/ né l'account, e lì vale invece il login via socket come root descritto nel paragrafo precedente. Da MariaDB 10.4 in poi questo account non viene più creato (il file rimanda semplicemente a root attraverso il socket), ma sulle installazioni esistenti spesso è ancora presente. Dare un'occhiata non costa nulla e, se funziona, ti risparmia tutto il resto di questa guida.
Quale database gira davvero su questo server?
Non è una formalità, perché da questo dipende ogni comando successivo. Su Debian nell'archivio ufficiale non esiste più da anni un pacchetto mysql-server: lì gira praticamente sempre MariaDB, anche quando il comando mysql esiste. Quel comando infatti è solo un collegamento simbolico al client MariaDB. Nelle distribuzioni attuali la situazione è questa:
| Sistema | mysql-server | mariadb-server |
| Debian 13 | non disponibile | 11.8 |
| Debian 12 | non disponibile | 10.11 |
| Ubuntu 24.04 | 8.0 | 10.11 |
| Ubuntu 22.04 | 8.0 | 10.6 |
Chiedilo al server stesso, non al client:
systemctl list-units --type=service --all | grep -Ei 'mysql|mariadb'
mysqladmin --version
mariadbd --version
Il --all serve, perché altrimenti list-units mostra soltanto le unit caricate e attive. Proprio nel caso in cui ti serve questo comando, cioè con un servizio fermo, l'output resterebbe vuoto. mysqladmin --version esiste su entrambi i fronti e nomina il motore in chiaro: i sistemi MariaDB rispondono con ... Distrib 11.8.6-MariaDB .... Con mariadbd --version, su un sistema puramente MySQL, command not found è il risultato atteso e non un errore; specularmente vale lo stesso per i comandi specifici di mysqld su un sistema puramente MariaDB.
Se la unit si chiama mariadb.service, stai lavorando con MariaDB. Se si chiama mysql.service, con MySQL. Sui sistemi con MariaDB esiste in più un alias mysql.service che punta a mariadb.service, quindi l'output di systemctl list-units è più significativo della semplice presenza di un nome.
I nomi delle unit differiscono tra le famiglie di distribuzioni, e tutte le chiamate a systemctl di questa guida sono scritte per Debian e Ubuntu. Nella famiglia Red Hat non esiste affatto una unit chiamata mysql.service: lì systemctl status mysql risponde con Unit mysql.service could not be found. In quel caso usa ovunque i nomi della colonna di destra, anche nel percorso della directory drop-in:
| Che cosa | Debian e Ubuntu | AlmaLinux, Rocky, RHEL |
|---|---|---|
| Unit MariaDB | mariadb.service | mariadb.service |
| Unit MySQL | mysql.service | mysqld.service |
| Directory drop-in MySQL | /etc/systemd/system/mysql.service.d/ | /etc/systemd/system/mysqld.service.d/ |
| Binario del server MySQL | /usr/sbin/mysqld | /usr/libexec/mysqld --basedir=/usr |
| Configurazione | /etc/mysql/ | /etc/my.cnf e /etc/my.cnf.d/ |
Isolare prima il server: perché questo passaggio non è facoltativo
Con --skip-grant-tables il server non carica le tabelle dei permessi. Questo non significa "root entra senza password", ma "chiunque può fare tutto, senza password, con l'identità di un utente qualsiasi". In questo stato non esistono né autenticazione né controllo dei permessi, nemmeno per i database dei tuoi clienti.
In questo caso MySQL 8 attiva automaticamente anche skip_networking e quindi non accetta più connessioni TCP. Non fidarti comunque di questo comportamento e aggiungi sempre l'opzione da te. Con MariaDB è in ogni caso la raccomandazione ufficiale della documentazione, e chi gestisce entrambi i sistemi non ha voglia di ricordarsi quale dei due ci pensa da solo.
--skip-grant-tables --skip-networking
Due aspetti che vengono spesso trascurati. Primo: skip_networking chiude soltanto la porta di rete. Il socket Unix sotto /run/mysqld/mysqld.sock resta aperto e su molti sistemi è raggiungibile da tutti gli utenti locali. Un processo PHP compromesso sotto www-data può leggere in quella finestra qualsiasi database presente sul server. Tieni quindi la finestra il più breve possibile e non eseguire l'operazione mentre gira un webserver con codice sconosciuto. Secondo: ferma prima tutto ciò che si collega in automatico, quindi webserver e servizi applicativi. I loro tentativi di connessione non solo disturbano, ma in questo stato girano con tutti i permessi.
Se il server è raggiungibile dall'esterno, chiudi in più il firewall. Come impostarlo in modo pulito e duraturo è spiegato in Configurare il firewall UFW.
sudo apt-get install -y ufw
sudo ufw deny 3306/tcp
sudo ss -ltnp | grep 3306
La prima riga non è superflua: su un'installazione Debian minimale ufw non è preinstallato e il comando finirebbe altrimenti con sudo: ufw: command not found. Su AlmaLinux, Rocky Linux e RHEL ufw non esiste proprio, lì il filtraggio dei pacchetti passa da firewalld:
sudo firewall-cmd --permanent --remove-service=mysql
sudo firewall-cmd --reload
Su tutte le installazioni Debian e Ubuntu verificate bind-address è comunque impostato su 127.0.0.1, confermato con ss -ltnp. In quel caso il server non accetta connessioni dall'esterno e la regola firewall è la seconda barriera, non la protezione vera e propria.
MariaDB: reimpostare la password
La unit di MariaDB avvia il server con ExecStart=/usr/sbin/mariadbd $MYSQLD_OPTS. Questa variabile è prevista esattamente per casi come questo, quindi non devi modificare alcun file. Fai una breve verifica, così sai che la strada seguente funziona sul tuo sistema:
systemctl cat mariadb | grep ExecStart
Poi, in quest'ordine:
sudo systemctl stop mariadb
sudo systemctl set-environment MYSQLD_OPTS="--skip-grant-tables --skip-networking"
sudo systemctl start mariadb
sudo mariadb -u root
Nel prompt il primo comando è FLUSH PRIVILEGES. Senza questo passaggio il server non ha ancora le tabelle dei permessi in memoria e rifiuta qualsiasi operazione sugli account. Dopo imposti la password:
FLUSH PRIVILEGES;
ALTER USER 'root'@'localhost' IDENTIFIED VIA unix_socket OR mysql_native_password USING PASSWORD('LaTuaNuovaPassword');
Questa scrittura un po' macchinosa è voluta ed è il punto specifico di MariaDB più importante di tutta la guida. Dalla 10.4 MariaDB può gestire più metodi di autenticazione per ogni account, ed è esattamente così che root viene configurato dopo l'installazione del pacchetto: prima il socket, in alternativa la password. Se al suo posto usi il semplice ALTER USER 'root'@'localhost' IDENTIFIED BY '...', sostituisci l'intera catena con la sola autenticazione tramite password. Funziona, ma dopo sudo mariadb senza password non va più, e gli script di manutenzione interni della distribuzione, che contano sul socket, finiscono a vuoto. Avresti soltanto rimandato il problema di un anno.
Alla fine rimuovi di nuovo lo stato eccezionale:
sudo systemctl stop mariadb
sudo systemctl unset-environment MYSQLD_OPTS
sudo systemctl start mariadb
Non dimenticare unset-environment. La variabile è legata al manager systemd, non al servizio, e sopravvive a ogni riavvio del servizio. Se di notte un aggiornamento dei pacchetti riavvia MariaDB, da quel momento il tuo database continua a girare senza alcun controllo dei permessi e nessuno se ne accorge. Solo un reboot del server elimina la variabile da sola.
MySQL 8: qui il comando della maggior parte delle guide non fa nulla
Per MySQL circola la stessa ricetta con systemctl set-environment MYSQLD_OPTS=.... Viene dalla documentazione di Oracle e vale per i pacchetti di Oracle stessa. La unit dell'archivio Ubuntu però è diversa: lì c'è semplicemente ExecStart=/usr/sbin/mysqld, senza variabile. Il comando va a buon fine, non segnala errori, e il server si avvia comunque del tutto normalmente con il controllo dei permessi attivo. Tu resti davanti a un Access denied e non capisci perché. Verificalo tu stesso:
systemctl cat mysql | grep ExecStart
Se lì non compare nessun $MYSQLD_OPTS, ti serve un drop-in. E visto che devi crearne uno, prendi subito la strada migliore: --init-file. Con questa opzione il server si avvia del tutto normalmente con il controllo dei permessi ed esegue all'avvio un file SQL con tutti i privilegi. Non si apre nessuna finestra temporale in cui qualcuno possa entrare senza password. Oracle raccomanda esplicitamente questa variante rispetto a --skip-grant-tables.
sudo systemctl stop mysql
printf "ALTER USER 'root'@'localhost' IDENTIFIED WITH caching_sha2_password BY 'LaTuaNuovaPassword';\n" | sudo tee /var/lib/mysql-files/kh-reset.sql
sudo chown mysql:mysql /var/lib/mysql-files/kh-reset.sql
sudo chmod 600 /var/lib/mysql-files/kh-reset.sql
sudo mkdir -p /etc/systemd/system/mysql.service.d
Il pezzo decisivo è IDENTIFIED WITH caching_sha2_password, ed è il motivo per cui innumerevoli guide falliscono proprio qui senza mostrare alcun errore. Su Debian e Ubuntu root@localhost usa il plugin auth_socket anche sotto MySQL. Un semplice IDENTIFIED BY 'password' imposta sì l'hash della password, ma non cambia il plugin. In mysql.user resta scritto auth_socket, il servizio si avvia senza problemi, non compare un solo messaggio di errore e ciononostante ogni login con password termina con ERROR 1698 (28000): Access denied for user 'root'@'localhost'. Particolarmente insidioso: chi fa la controprova come utente di sistema root viene fatto passare da auth_socket senza password e crede che il reset sia riuscito. Prova quindi da un altro account oppure via TCP. Solo con client molto vecchi, che non supportano caching_sha2_password, usa al suo posto mysql_native_password, con i limiti descritti nel paragrafo più avanti.
La directory /var/lib/mysql-files è scelta di proposito: appartiene all'utente del database ed è consentita nel profilo AppArmor di mysqld. Se metti il file in /root o in /tmp, l'avvio può fallire per colpa di AppArmor, e il messaggio nel log aiuta poco. Ora il drop-in:
[Service]
ExecStart=
ExecStart=/usr/sbin/mysqld --init-file=/var/lib/mysql-files/kh-reset.sql
La prima riga ExecStart vuota è obbligatoria, altrimenti systemd aggiunge il tuo comando a quello esistente e rifiuta il servizio con un errore di configurazione. Salva il tutto in /etc/systemd/system/mysql.service.d/override.conf, poi:
sudo systemctl daemon-reload
sudo systemctl start mysql
sudo mysql -u root -p
Se il login funziona, rimuovi entrambe le cose, il file SQL e il drop-in:
sudo rm -f /var/lib/mysql-files/kh-reset.sql
sudo rm -f /etc/systemd/system/mysql.service.d/override.conf
sudo systemctl daemon-reload
sudo systemctl restart mysql
Se vuoi comunque la strada classica, nel drop-in sostituisci la riga con ExecStart=/usr/sbin/mysqld --skip-grant-tables --skip-networking, ti colleghi con sudo mysql e lì esegui prima FLUSH PRIVILEGES;, poi ALTER USER. Una nota sulla cifratura: MySQL 8 usa di default caching_sha2_password. Se dopo un'applicazione molto vecchia segnala "The server requested authentication method unknown to the client", aiuta IDENTIFIED WITH mysql_native_password BY '...'. È però un vicolo cieco, perché questo metodo è considerato deprecato dalla 8.0.34 e in MySQL 8.4 non è più incluso. Meglio aggiornare il client.
I messaggi di errore alla lettera
ERROR 1290 (HY000): The MySQL server is running with the --skip-grant-tables option so it cannot execute this statement. Hai dimenticato FLUSH PRIVILEGES;. Eseguilo e subito dopo ALTER USER funziona.
ERROR 1288 (HY000): The target table user of the UPDATE is not updatable. Stai seguendo una guida vecchia che propone UPDATE mysql.user SET password=.... Da MariaDB 10.4 i permessi risiedono in mysql.global_priv e mysql.user è ormai soltanto una vista su quella tabella. Usa ALTER USER oppure SET PASSWORD. Per inciso: scrivere direttamente nelle tabelle dei permessi era già in passato un ottimo metodo per distruggere l'account in modo definitivo.
ERROR 1698 (28000): Access denied for user 'root'@'localhost'. Non è una password sbagliata, è il contrario: l'account si aspetta l'autenticazione via socket e tu non stai lavorando come utente di sistema root oppure hai passato -p. Riprova con sudo e senza -p.
ERROR 1524 (HY000): Plugin 'unix_socket' is not loaded. L'account rimanda a un plugin che il server in esecuzione non conosce, tipico dopo un passaggio da MariaDB a MySQL oppure dopo la copia di una directory dati. Riporta l'account su un metodo adatto seguendo la procedura descritta sopra.
ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/run/mysqld/mysqld.sock' (2). Il server non è in esecuzione. Guarda prima systemctl status e il log, invece di continuare a provare dal client.
mysqld: Can't create directory '/run/mysqld/' (Errcode: 13 - Permission denied) oppure un avvio che si interrompe subito. Succede quando il server è stato avviato a mano invece che tramite systemd, perché in quel caso manca la directory di runtime. Riparazione:
sudo mkdir -p /run/mysqld
sudo chown mysql:mysql /run/mysqld
Job for mysql.service failed because the control process exited with error code. Questo messaggio di per sé non dice nulla. La causa vera sta nel journal e nel log degli errori:
sudo journalctl -u 'mysql*' -u 'mariadb*' -n 60 --no-pager
Il pattern con entrambi i nomi di unit è scelto di proposito, perché l'interrogazione singola più ovvia porta dritti in una trappola silenziosa. Su Debian con MariaDB mysql.service è solo un alias di mariadb.service. systemctl risolve l'alias, ma il journal indicizza le voci sotto il nome reale della unit, e per questo journalctl -u mysql risponde con -- No entries -- ed exit code 0 anche se il journal è pieno. Al contrario, su un sistema Ubuntu con MySQL anche journalctl -u mariadb restituisce -- No entries --.
Con il file di errore vale un discorso simile. Il /var/log/mysql/error.log citato ovunque esiste soltanto su Ubuntu con MySQL. Su Debian con MariaDB la directory /var/log/mysql/ non esiste affatto, perché log_error è commentato in 50-server.cnf e MariaDB scrive nel journal. La riga giusta a seconda del sistema:
| Sistema e server | Comando |
|---|---|
| Debian o Ubuntu, MariaDB | sudo journalctl -u mariadb -n 60 --no-pager |
| Ubuntu, MySQL | sudo tail -n 60 /var/log/mysql/error.log |
| AlmaLinux, Rocky, RHEL, MySQL | sudo tail -n 60 /var/log/mysql/mysqld.log |
| AlmaLinux, Rocky, RHEL, MariaDB | sudo tail -n 60 /var/log/mariadb/mariadb.log |
Se non vuoi tirare a indovinare il percorso, chiedilo al server stesso: sudo mariadb -e "SHOW VARIABLES LIKE 'log_error';" oppure lo stesso comando con mysql.
Le tre cause più frequenti a questo punto: un errore di battitura nel drop-in (in quel caso systemd segnala la riga), un disco pieno (a riguardo Disco pieno su Linux) oppure un secondo processo del server ancora in esecuzione che tiene bloccati i file di dati. Quest'ultimo caso lo verifichi con pgrep -a mariadbd oppure pgrep -a mysqld prima di riavviare.
Se dopo il reset entri, ma le tue applicazioni continuano a essere respinte, il problema è altrove: applicazioni come WordPress o Nextcloud usano utenti di database propri, non root. I dettagli in Risolvere MySQL Access denied for user.
Come capire che ha funzionato davvero
Un login riuscito da solo non basta come prova, perché nello stato di emergenza funziona anche senza alcuna password. Verifica quindi quattro cose dopo che il servizio è tornato a girare normalmente.
Primo: non è più attiva nessuna configurazione speciale. Il primo output deve essere vuoto, il secondo non deve più mostrare opzioni aggiuntive.
systemctl show-environment | grep MYSQLD_OPTS
systemctl cat mariadb | grep ExecStart
Secondo: il controllo dei permessi è di nuovo attivo. Un login con una password volutamente sbagliata deve fallire. Se passa, il server sta ancora girando aperto.
Terzo: la nuova password e il metodo previsto sono registrati nell'account. Su MariaDB lo interroghi così, su MySQL senza la colonna JSON:
sudo mariadb -e "SELECT user, host, plugin FROM mysql.user WHERE user='root';"
sudo mysql -e "SELECT user, host, plugin FROM mysql.user WHERE user='root';"
Quarto: la porta di rete si comporta di nuovo come prima. Un server di database usato solo in locale, dopo la pulizia, dovrebbe restare in ascolto esclusivamente su 127.0.0.1:
sudo ss -ltnp | grep 3306
grep -rs bind-address /etc/mysql/ /etc/my.cnf /etc/my.cnf.d/
Il -s e i tre percorsi sono voluti. Il solo grep -r bind-address /etc/mysql/ sulla famiglia Red Hat si interrompe con No such file or directory, perché lì /etc/mysql/ non esiste. Con -s grep ignora in silenzio i percorsi mancanti e la riga va bene per entrambi i mondi.
Fare pulizia, perché non succeda una seconda volta
La password ora si trova probabilmente in punti a cui non pensi. Un comando con -e "ALTER USER ... IDENTIFIED BY '...'" finisce in ~/.bash_history, un comando digitato nel prompt finisce nel file di history del client del database. Vanno ripuliti entrambi:
history -c
rm -f ~/.mysql_history ~/.mariadb_history
Servono entrambi i nomi di file, perché il client ha cambiato nome. Fino a MariaDB 10.11, quindi fino a Debian 12 incluso, scrive in ~/.mysql_history. Da MariaDB 11, quindi da Debian 13, scrive in ~/.mariadb_history. Chi lì cancella solo il file vecchio lascia sul disco, senza accorgersene, la password digitata in chiaro. Meglio ancora è non far nascere affatto la history: export MYSQL_HISTFILE=/dev/null oppure export MARIADB_HISTFILE=/dev/null prima della sessione, o direttamente la strada con --init-file del paragrafo su MySQL, in cui la password non passa mai da una sessione interattiva.
Più sensato di una password che tra un anno dimenticherai di nuovo è un setup che nel lavoro quotidiano fa a meno della password. Su Debian e Ubuntu significa: root resta su unix_socket oppure auth_socket, e per tutto il resto crei utenti normali con esattamente i permessi di cui la singola applicazione ha bisogno. Chi ha bisogno dell'accesso da un altro computer lo fa passare in un tunnel SSH invece di aprire la porta 3306, vedi Collegarsi al server via SSH.
Se comunque stai già lavorando sul database, porta a casa subito anche il resto: rimuovere gli utenti anonimi, cancellare il database di test, disattivare l'accesso remoto per root. Se ne occupano mariadb-secure-installation e mysql_secure_installation in pochi minuti, descritti nel dettaglio in Proteggere MariaDB e MySQL. E dato che la password è raramente l'unica cosa che non va su un server appena preso in gestione, vale la pena fare un giro completo della checklist per i nuovi server root.
Un ultimo pensiero sull'ordine delle operazioni: prima di mettere un server di produzione attivo nello stato privo di permessi, fai un backup della directory dati oppure uno snapshot. Il reset in sé è innocuo, ma un servizio che dopo un errore di battitura nella unit non parte più non lo è affatto alle tre del mattino.
Domande frequenti
Ho dimenticato la password di root. Devo davvero fermare MySQL?
Perché ricevo ERROR 1290 anche se ho avviato con skip-grant-tables?
Il mio server è attaccabile durante il reset?
Qual è la differenza tra MySQL 8 e MariaDB nel reset?
Dopo il reset entro come root, ma il mio sito continua a segnalare Access denied. Perché?
Che cosa succede se dimentico systemctl unset-environment?
2026 KernelHost GmbH. Tutti i diritti riservati. Questa guida è protetta dal diritto d'autore. La ripubblicazione su altri siti web, anche parziale o in forma modificata, non è consentita senza il nostro consenso scritto. Le citazioni con indicazione della fonte e un link sono le benvenute.

