Risolvere l'errore MySQL "Access denied for user"
Perché "Access denied for user" quasi mai dipende da una password sbagliata: unix_socket, localhost contro 127.0.0.1, i permessi e il reset sicuro della password di root.
"Access denied for user" è il messaggio di errore più cercato in ambito MySQL, e nella maggior parte dei casi la colpa non è affatto della password. Su Debian e Ubuntu l'accesso fallisce soprattutto per l'autenticazione tramite socket, per la confusione tra localhost e 127.0.0.1 oppure per una riga utente creata per l'host sbagliato. Questo articolo affronta le cause nell'ordine in cui si presentano davvero, mostra il reset della password tramite skip-grant-tables compreso il percorso di ritorno, e descrive da che cosa capisci che l'accesso è davvero riparato.
Tutte le indicazioni si riferiscono a Debian 13 (MariaDB 11.8), Debian 12 (MariaDB 10.11), Ubuntu 24.04 (MariaDB 10.11 o MySQL 8.0) e Ubuntu 22.04 (MariaDB 10.6 o MySQL 8.0). Un punto importante da chiarire subito: Debian non fornisce alcun pacchetto mysql-server. Se su un sistema Debian hai installato "MySQL", quello che gira lì è MariaDB, e questo spiega già una parte della confusione.
Leggere con attenzione il messaggio di errore
La formulazione esatta decide quale causa entri in gioco. Nella pratica ti capitano queste cinque varianti:
| Messaggio | Significato |
|---|---|
ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: YES) | Una password è stata inviata e non corrispondeva, oppure non esiste una riga account adatta. |
ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: NO) | Non è stata inviata alcuna password. Di solito manca -p oppure l'applicazione non legge la configurazione. |
ERROR 1698 (28000): Access denied for user 'root'@'localhost' | Il classico: l'account usa unix_socket oppure auth_socket. Qui una password non ha alcun significato. |
ERROR 1044 (42000): Access denied for user 'app'@'localhost' to database 'shop' | L'accesso è riuscito. Mancano soltanto i permessi su questo database. |
ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/run/mysqld/mysqld.sock' | Nessun problema di autenticazione. Il servizio non è in esecuzione oppure il percorso del socket non è corretto. |
Due dettagli vengono regolarmente ignorati. Primo: il nome host nel messaggio è l'host con cui il server ti ha visto, non quello che hai digitato. Se lì compare 'app'@'localhost' anche se ti sei collegato con -h 127.0.0.1, allora il server ha eseguito una risoluzione inversa. Secondo: 1045 non distingue tra "password sbagliata" e "account inesistente". Entrambi i casi producono lo stesso output, ed è voluto, così un attaccante non può individuare a tentativi i nomi utente validi.
Il caso più frequente: unix_socket su MariaDB
Dalla versione MariaDB 10.4 i pacchetti di Debian e Ubuntu creano l'account root@localhost in modo che venga autenticato tramite il socket Unix. La riga dell'account somiglia a questa:
CREATE USER 'root'@'localhost' IDENTIFIED VIA mysql_native_password USING 'invalid'
OR unix_socket;
Significa che chi è collegato al sistema come utente root entra senza password. Chi invia una password viene invece verificato contro l'hash della stringa "invalid", e quella non la indovina nessuno. La parte con mysql_native_password compare soltanto perché altrimenti SET PASSWORD si interromperebbe con un errore. Conseguenza pratica: mysql -u root -p eseguito come utente normale fallisce indipendentemente dalla password che digiti. La forma corretta è:
sudo mariadb
Su MySQL 8.0 in Ubuntu il plugin si chiama auth_socket invece di unix_socket, il comportamento è identico. Lì il comando è sudo mysql.
Verifica quale plugin usa realmente un account, e fallo con SHOW CREATE USER:
mariadb -e "SHOW CREATE USER 'root'@'localhost';"
L'output mostra la catena completa di entrambi i metodi:
CREATE USER `root`@`localhost` IDENTIFIED VIA mysql_native_password USING 'invalid' OR unix_socket
Qui si nasconde una trappola che quasi nessuna guida menziona: dalla versione MariaDB 10.4 mysql.user è soltanto una vista (view), i dati veri stanno come JSON in mysql.global_priv. Quella vista conosce un solo metodo di autenticazione per account e per root@localhost riporta quindi mysql_native_password, anche se in realtà è unix_socket a fare il lavoro. Chi prende come prova la diffusissima query SELECT User, Host, plugin FROM mysql.user crede che il server sia autenticato tramite password e cerca l'errore nel posto sbagliato. Lo stesso punto cieco ce l'ha JSON_VALUE(priv,"$.plugin"), perché anche quello legge solo il primo elemento. Il secondo metodo sta nel campo auth_or e va letto a parte:
mariadb -e 'SELECT CONCAT(user,"@",host) AS account, JSON_VALUE(priv,"$.plugin") AS plugin, JSON_QUERY(priv,"$.auth_or") AS auth_or FROM mysql.global_priv;'
Per root@localhost nella colonna plugin continua allora a comparire mysql_native_password, in auth_or invece [{},{"plugin":"unix_socket"}]. Solo questa seconda colonna mostra che l'accesso tramite socket è attivo.
Il plugin è caricato? Dopo un aggiornamento andato male può mancare, e allora il server segnala ERROR 1524 (HY000): Plugin 'unix_socket' is not loaded:
mariadb -e "SELECT plugin_name, plugin_status FROM information_schema.plugins WHERE plugin_name LIKE '%socket%';"
Se vuoi passare consapevolmente root a una password, per esempio perché uno script di backup gira sotto un altro utente di sistema, mantieni in aggiunta anche la variante socket. Altrimenti gli script di manutenzione della distribuzione e sudo mariadb smettono di funzionare:
ALTER USER 'root'@'localhost' IDENTIFIED VIA unix_socket
OR mysql_native_password USING PASSWORD('TuaNuovaPassword');
La strada migliore resta comunque un account di amministrazione dedicato, invece di una password per root. Per le applicazioni vale a maggior ragione: un database, un utente, solo i permessi necessari.
localhost non è 127.0.0.1
Questa distinzione provoca più messaggi "Access denied" di qualsiasi password sbagliata. I client di MySQL e MariaDB trattano il nome host localhost come caso speciale e si collegano tramite il socket Unix. Solo 127.0.0.1 impone una connessione TCP. Dal punto di vista della gestione dei permessi i due non sono affatto lo stesso nome di macchina, perché con una connessione tramite socket il server registra l'host localhost, mentre con TCP sull'indirizzo di loopback registra o localhost (dopo la risoluzione inversa) oppure 127.0.0.1, a seconda dell'impostazione di skip_name_resolve.
Un account che esiste soltanto come 'app'@'127.0.0.1' non è raggiungibile tramite il socket, e viceversa. È esattamente ciò che succede quando un'applicazione PHP ha host=localhost nella configurazione: PHP si collega tramite il socket, ma i permessi sono stati assegnati per l'indirizzo IP. Due controprove:
mariadb -u app -p'password' -e "SELECT USER(), CURRENT_USER();"
mariadb -h 127.0.0.1 -u app -p'password' -e "SELECT USER(), CURRENT_USER();"
Se fallisce solo una delle due, la causa è trovata. Fai attenzione alla risoluzione dei nomi: finché skip_name_resolve è disattivato, e nelle installazioni da pacchetto di Debian e Ubuntu lo è, il server risolve all'indietro l'indirizzo di loopback in localhost. Se esiste già un account 'app'@'localhost', riesce quindi anche l'accesso con -h 127.0.0.1, e CURRENT_USER() riporta allora app@localhost invece di app@127.0.0.1. Un account 'app'@'127.0.0.1' creato in aggiunta resta in questo caso senza effetto, entra in gioco solo quando l'account localhost manca. La soluzione pulita è creare l'account per la via che l'applicazione percorre davvero, e non predisporre entrambe le varianti "per ogni evenienza".
Verifica inoltre se la risoluzione dei nomi è disattivata. Se skip_name_resolve è attivo, le righe account con nomi host come 'app'@'web01.intern' smettono del tutto di funzionare, da quel momento contano solo gli indirizzi IP:
mariadb -e "SHOW VARIABLES LIKE 'skip_name_resolve';"
Un altro ostacolo è l'ordine con cui il server sceglie le righe corrispondenti. Ordina dal più specifico al più generico e prende il primo risultato, non il migliore. Se accanto a 'app'@'%' esiste anche un account anonimo ''@'localhost', su una connessione locale vince l'account anonimo, e il tuo accesso fallisce con un messaggio che nomina comunque il tuo nome utente. I pacchetti attuali non creano più account anonimi, ma sui sistemi migrati per anni li si trova ancora:
mariadb -e "SELECT user, host FROM mysql.global_priv WHERE user = '';"
Password, permessi e maiuscole
Restano le cause che hanno davvero a che fare con le credenziali di accesso.
La shell si mangia i caratteri speciali
Tra -p e la password non ci deve essere alcuno spazio, altrimenti il client interpreta la password come nome di database. Le password con $, !, & o spazi vanno racchiuse tra apici singoli, perché altrimenti la shell le sostituisce o le tronca. La cosa davvero pulita è non passare affatto la password sulla riga di comando, perché lì finisce nell'elenco dei processi e nel file della cronologia. Crea invece un file ~/.my.cnf con permessi 0600:
[client]
user=app
password=TuaPassword
Al contrario, un file ~/.my.cnf dimenticato è a sua volta una possibile causa di errore. Sovrascrive in silenzio quello che indichi sulla riga di comando, e a quel punto ricevi "Access denied" per un utente che non hai mai digitato.
Maiuscole e minuscole: sì per il nome utente, no per l'host
In MySQL e MariaDB i nomi utente vanno indovinati carattere per carattere, App e app sono due account diversi. I nomi host vengono confrontati senza distinzione tra maiuscole e minuscole, ma memorizzati esattamente come stavano nel CREATE USER. Chi ha creato per sbaglio 'app'@'LOCALHOST' vede negli output di SHOW GRANTS e negli script due account apparentemente diversi, che però corrispondono alla stessa connessione. Duplicati del genere rendono la ricerca dell'errore inutilmente faticosa, perché tu assegni i permessi su una riga e il server sceglie l'altra. Si possono scovare così:
mariadb -e "SELECT user, host FROM mysql.global_priv WHERE host <> LOWER(host);"
Mancano i permessi, non l'accesso
Se arriva ERROR 1044 invece di 1045, l'accesso è riuscito. Allora mancano i permessi su un determinato database. Controlla che cosa può fare davvero l'account:
mariadb -e "SHOW GRANTS FOR 'app'@'localhost';"
FLUSH PRIVILEGES ti serve solo se hai modificato le tabelle dei permessi direttamente con INSERT o UPDATE. Dopo GRANT, CREATE USER o ALTER USER è superfluo, e ogni tanto nasconde il fatto che il comando vero e proprio non ha avuto alcun effetto.
Il client non capisce il plugin
MySQL 8.0 usa per impostazione predefinita caching_sha2_password. I client e le librerie più vecchie rispondono con ERROR 2059 (HY000): Authentication plugin 'caching_sha2_password' cannot be loaded. Non è un problema di permessi, ma un'incompatibilità. In MySQL 8.0 mysql_native_password è ancora disponibile come ripiego, in MySQL 8.4 è stato rimosso. Nel dubbio aggiorna piuttosto il client, invece di tornare indietro sulla cifratura.
Reimpostare la password di root
Quando non serve più nulla, il server deve avviarsi una volta senza controllo dei permessi. Due strade portano allo scopo. La via tramite --init-file è la più sicura, perché così il server gira per tutto il tempo con il controllo dei permessi attivo.
Variante 1: init-file (consigliata)
Il file SQL deve trovarsi in un punto che il servizio ha il diritto di leggere. Sotto /tmp o /root su Ubuntu la cosa fallisce regolarmente per colpa di AppArmor e di PrivateTmp nel servizio systemd. Mettilo perciò nella directory dei dati:
sudo systemctl stop mariadb
sudo tee /var/lib/mysql/kh-reset.sql >/dev/null <<'SQL'
ALTER USER 'root'@'localhost' IDENTIFIED VIA unix_socket
OR mysql_native_password USING PASSWORD('NuovaPasswordRoot');
SQL
sudo chown mysql:mysql /var/lib/mysql/kh-reset.sql
sudo systemctl set-environment MYSQLD_OPTS="--init-file=/var/lib/mysql/kh-reset.sql"
sudo systemctl start mariadb
Dopo, fai assolutamente pulizia, altrimenti a ogni avvio il server rigira con quel file:
sudo systemctl unset-environment MYSQLD_OPTS
sudo rm /var/lib/mysql/kh-reset.sql
sudo systemctl restart mariadb
Per MySQL 8.0 su Ubuntu il servizio si chiama mysql e la riga SQL è questa:
ALTER USER 'root'@'localhost' IDENTIFIED WITH caching_sha2_password BY 'NuovaPasswordRoot';
Variante 2: skip-grant-tables
Questa variante disattiva completamente il controllo dei permessi. Senza --skip-networking in quel lasso di tempo chiunque raggiunga la porta avrebbe accesso completo a tutti i dati. L'opzione quindi non è facoltativa, è obbligatoria.
sudo systemctl stop mariadb
sudo systemctl set-environment MYSQLD_OPTS="--skip-grant-tables --skip-networking"
sudo systemctl start mariadb
sudo mariadb
Nella sessione carica prima le tabelle dei permessi, altrimenti il server rifiuta ALTER USER con un messaggio di errore:
FLUSH PRIVILEGES;
ALTER USER 'root'@'localhost' IDENTIFIED VIA unix_socket
OR mysql_native_password USING PASSWORD('NuovaPasswordRoot');
EXIT;
E poi di nuovo verso il funzionamento normale:
sudo systemctl unset-environment MYSQLD_OPTS
sudo systemctl restart mariadb
Se la tua distribuzione non valuta la variabile MYSQLD_OPTS, funziona sempre il file drop-in sotto /etc/systemd/system/mariadb.service.d/reset.conf con il contenuto [Service] ed Environment="MYSQLD_OPTS=--skip-grant-tables --skip-networking", seguito da sudo systemctl daemon-reload. Cancella poi di nuovo il file e ricarica systemd.
Quando il reset va storto
È esattamente qui che la maggior parte delle guide si ferma. I quattro problemi successivi più frequenti:
Il servizio non parte più. Di solito è un errore di battitura nel file SQL. Il server si interrompe allora all'avvio. Debian registra MariaDB per impostazione predefinita nel journal, Ubuntu con MySQL anche in un file:
sudo journalctl -u mariadb -n 50 --no-pager
sudo tail -n 50 /var/log/mysql/error.log
Dove scrive il server si può controllare con SHOW VARIABLES LIKE 'log_error';. Non aspettarti però lì un percorso di file: nelle installazioni da pacchetto di Debian e Ubuntu il valore è vuoto, perché il servizio gira con --skip-log-error. Tutto finisce allora sullo standard error e da lì nel journal oppure in /var/log/syslog, consultabile con journalctl -u mariadb. Per risolvere, rimuovi la variabile d'ambiente --init-file e riavvia.
Il server gira stabilmente senza controllo dei permessi. Succede quando ci si dimentica l'unset-environment, ed è la variante più pericolosa, perché dall'esterno sembra tutto normale. Due controlli:
systemctl show-environment
ps -o args= -C mariadbd
Se in uno dei due output compare skip-grant-tables, il controllo dei permessi è ancora disattivato. systemctl show-environment presuppone però che systemd giri come PID 1. Su un server normale è così, in un container o su un sistema con SysV-Init il comando non esiste. Lì leggi l'ambiente direttamente dal processo in esecuzione:
cat /proc/$(pgrep -n mariadbd)/environ | tr '\0' '\n'
Nemmeno sulla sola lista degli argomenti dovresti fare affidamento: avviato tramite systemd, ps mostra spesso solo /usr/sbin/mariadbd senza alcuna opzione, mentre tramite uno script SysV-Init compare l'elenco completo. Un ulteriore indizio: sotto skip-grant-tables il comando SHOW GRANTS risponde con un messaggio di errore invece che con i permessi.
AppArmor blocca il file init. Il sintomo è un server che non si avvia senza un motivo riconoscibile. Uno sguardo al log del kernel chiarisce la cosa:
sudo dmesg | grep -i denied
Ti chiudi fuori completamente. Succede quando si passa root con ALTER USER ... IDENTIFIED BY alla sola password, perdendo così l'autenticazione tramite socket, e poi non ci si ricorda più la nuova password. La via d'uscita è di nuovo lo stesso reset, questa volta con la doppia variante unix_socket OR mysql_native_password mostrata sopra. Prima di ogni intervento fatti una copia delle tabelle dei permessi, ci vogliono pochi secondi e nei casi seri ti risparmia ore:
sudo mariadb-dump mysql > /root/mysql-grants.sql
L'opzione --single-transaction, per il resto abituale, qui puoi risparmiartela. Gira sì senza errori, ma non ha effetto, perché le tabelle del database mysql stanno su Aria oppure MyISAM e quindi non sono transazionali.
Su un hosting web gestito non hai accesso al sistema e quindi nessuna di queste opzioni. Lì utenti e password del database si reimpostano dall'interfaccia di amministrazione. Su un server root KVM o su un server dedicato di KernelHost hai accesso root completo e, se il server non è più raggiungibile tramite la connessione di rete, arrivi al sistema attraverso la console nell'area clienti.
Verificare che sia davvero risolto
Che un comando termini senza errori non significa ancora che l'accesso funzioni stabilmente. Questi quattro controlli scoprono i tipici errori residui.
Primo, la differenza tra USER() e CURRENT_USER(). La prima funzione mostra per chi ti sei spacciato, la seconda quale riga account il server usa davvero:
mariadb -u app -p'password' -e "SELECT USER(), CURRENT_USER();"
Se lì compaiono due valori diversi, per esempio app@localhost e app@%, allora i tuoi permessi passano da una riga diversa da quella che immaginavi. Questo spiega gli errori 1044 che compaiono più tardi, prima che si facciano notare in produzione.
Secondo, un accesso reale al database di destinazione e non solo un login, con la valutazione del valore restituito:
mariadb -u app -p'password' mio_db -e "SELECT 1;" ; echo "Codice di uscita: $?"
Terzo, un riavvio del servizio e subito dopo di nuovo lo stesso accesso. Così ti assicuri che la modifica sia davvero nelle tabelle e non solo nella memoria del processo in esecuzione, e che non sia rimasta nessuna variabile d'ambiente del reset:
sudo systemctl restart mariadb
sudo systemctl is-active mariadb
Quarto, l'applicazione stessa. Un test riuscito sulla riga di comando dice poco su un'applicazione PHP che si collega con l'utente del server web e tramite il socket. Prova perciò con lo stesso utente di sistema:
sudo -u www-data mariadb -u app -p'password' mio_db -e "SELECT CURRENT_USER();"
Differenze tra le distribuzioni
Una guida che sostiene la stessa cosa per tutti e quattro i sistemi è sbagliata almeno in un punto. Questa tabella riassume gli scostamenti rilevanti:
| Sistema | Database predefinito | Plugin socket | Nota |
|---|---|---|---|
| Debian 13 | MariaDB 11.8 | unix_socket | nessun pacchetto mysql-server disponibile; mysql è ormai soltanto un collegamento simbolico a mariadb e avvisa quando lo richiami |
| Debian 12 | MariaDB 10.11 | unix_socket | nessun pacchetto mysql-server disponibile |
| Ubuntu 24.04 | MariaDB 10.11 o MySQL 8.0 | unix_socket o auth_socket | entrambi i pacchetti presenti in parallelo nelle fonti, con le guide il rischio di confusione è alto |
| Ubuntu 22.04 | MariaDB 10.6 o MySQL 8.0 | unix_socket o auth_socket | JSON_VALUE e JSON_QUERY su mysql.global_priv qui funzionano esattamente come sotto 10.11 e 11.8; anche in 10.6 mysql.user è solo una vista, e per la catena di autenticazione completa SHOW CREATE USER resta la via più breve |
Altre differenze che nella pratica costano tempo: i file di configurazione con MariaDB stanno sotto /etc/mysql/mariadb.conf.d/50-server.cnf, con MySQL sotto /etc/mysql/mysql.conf.d/mysqld.cnf. I nomi dei servizi sono rispettivamente mariadb e mysql, dove MariaDB porta con sé in aggiunta un alias mysql. In MariaDB dalla versione 10.4 non esiste più un utente debian-sys-maint, e il file /etc/mysql/debian.cnf rimanda lì a root tramite il socket. Con MySQL su Ubuntu l'utente di manutenzione esiste ancora, ed è il miglior accesso di emergenza quando la password di root è persa e non vuoi riavviare il server:
sudo mysql --defaults-file=/etc/mysql/debian.cnf
Se rispetti questo ordine, quindi prima leggere con attenzione il messaggio, poi verificare plugin e riga host, poi la via di connessione, e solo alla fine reimpostare la password, la stragrande maggioranza dei casi si risolve in pochi minuti e senza downtime. Il reset tramite skip-grant-tables è l'ultima risorsa, non il primo passo.
Domande frequenti
Perché mysql -u root -p non funziona anche se la password è giusta?
Qual è la differenza tra ERROR 1045 e ERROR 1698?
localhost è la stessa cosa di 127.0.0.1?
Le maiuscole contano per nome utente e host?
Come reimposto la password di root senza skip-grant-tables?
Da che cosa capisco che il server gira ancora senza controllo dei permessi?
Su Debian esiste un pacchetto mysql-server?
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.

