Corriger l'erreur MySQL « Access denied for user »
Pourquoi « Access denied for user » n'a le plus souvent rien à voir avec un mot de passe erroné : unix_socket, localhost contre 127.0.0.1, les droits et la réinitialisation sûre du mot de passe root.
« Access denied for user » est le message d'erreur le plus recherché dans l'univers MySQL, et dans la plupart des cas le mot de passe n'y est pour rien. Sur Debian et Ubuntu, la connexion échoue avant tout à cause de l'authentification par socket, de la confusion entre localhost et 127.0.0.1, ou d'une ligne de compte créée pour le mauvais hôte. Cet article parcourt les causes dans l'ordre où elles se présentent réellement, montre la réinitialisation du mot de passe via skip-grant-tables ainsi que le chemin de retour, et décrit à quoi vous reconnaissez que la connexion est vraiment réparée.
Toutes les indications se rapportent à Debian 13 (MariaDB 11.8), Debian 12 (MariaDB 10.11), Ubuntu 24.04 (MariaDB 10.11 ou MySQL 8.0) et Ubuntu 22.04 (MariaDB 10.6 ou MySQL 8.0). Un point important d'emblée : Debian ne fournit aucun paquet mysql-server. Si vous avez installé « MySQL » sur un système Debian, c'est MariaDB qui tourne, et cela explique déjà une partie de la confusion.
Lire le message d'erreur avec précision
Le libellé exact détermine quelle cause entre en ligne de compte. Ces cinq variantes se rencontrent en pratique :
| Message | Signification |
|---|---|
ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: YES) | Un mot de passe a été envoyé et ne correspondait pas, ou il n'existe aucune ligne de compte adaptée. |
ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: NO) | Aucun mot de passe n'a été envoyé. Le plus souvent, -p manque ou l'application ne lit pas sa configuration. |
ERROR 1698 (28000): Access denied for user 'root'@'localhost' | Le grand classique : le compte utilise unix_socket ou auth_socket. Un mot de passe n'a ici aucune importance. |
ERROR 1044 (42000): Access denied for user 'app'@'localhost' to database 'shop' | La connexion a réussi. Il manque seulement des droits sur cette base de données. |
ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/run/mysqld/mysqld.sock' | Aucun problème d'authentification. Le service ne tourne pas ou le chemin du socket est incorrect. |
Deux détails passent régulièrement inaperçus. Premièrement, le nom d'hôte affiché dans le message est l'hôte tel que le serveur vous a vu, pas celui que vous avez saisi. Si vous y lisez 'app'@'localhost' alors que vous vous êtes connecté avec -h 127.0.0.1, c'est que le serveur a effectué une résolution inverse. Deuxièmement, 1045 ne fait pas la différence entre « mot de passe incorrect » et « compte inexistant ». Les deux produisent la même sortie, volontairement, pour qu'un attaquant ne puisse pas deviner les noms d'utilisateur valides.
Le cas le plus fréquent : unix_socket avec MariaDB
Depuis MariaDB 10.4, les paquets Debian et Ubuntu créent le compte root@localhost de telle sorte qu'il s'authentifie via le socket Unix. La ligne de compte ressemble en substance à ceci :
CREATE USER 'root'@'localhost' IDENTIFIED VIA mysql_native_password USING 'invalid'
OR unix_socket;
Autrement dit : celui qui est connecté au système en tant qu'utilisateur root entre sans mot de passe. Celui qui envoie un mot de passe est vérifié contre le hachage de la chaîne « invalid », que personne ne peut reproduire. La partie mysql_native_password ne figure là que parce que SET PASSWORD échouerait sinon avec une erreur. Conséquence pratique : mysql -u root -p lancé en tant qu'utilisateur normal échoue quel que soit le mot de passe saisi. La bonne commande est :
sudo mariadb
Sous MySQL 8.0 sur Ubuntu, le plugin s'appelle auth_socket au lieu de unix_socket, le comportement est identique. L'appel y est sudo mysql.
Vérifiez quel plugin un compte utilise réellement, et ce avec SHOW CREATE USER :
mariadb -e "SHOW CREATE USER 'root'@'localhost';"
La sortie montre la chaîne complète des deux méthodes :
CREATE USER `root`@`localhost` IDENTIFIED VIA mysql_native_password USING 'invalid' OR unix_socket
Un piège se cache ici, que presque aucun tutoriel ne mentionne : depuis MariaDB 10.4, mysql.user n'est plus qu'une vue (view), les données réelles se trouvent au format JSON dans mysql.global_priv. Cette vue ne connaît qu'une seule méthode d'authentification par compte et signale donc mysql_native_password pour root@localhost, alors que c'est en réalité unix_socket qui s'applique. Qui prend la requête très répandue SELECT User, Host, plugin FROM mysql.user pour argent comptant croit le serveur authentifié par mot de passe et cherche l'erreur au mauvais endroit. JSON_VALUE(priv,"$.plugin") souffre exactement du même angle mort, car il ne lit lui aussi que le premier élément. La seconde méthode se trouve dans le champ auth_or et doit être lue séparément :
mariadb -e 'SELECT CONCAT(user,"@",host) AS konto, JSON_VALUE(priv,"$.plugin") AS plugin, JSON_QUERY(priv,"$.auth_or") AS auth_or FROM mysql.global_priv;'
Pour root@localhost, la colonne plugin affiche alors toujours mysql_native_password, tandis que auth_or contient [{},{"plugin":"unix_socket"}]. Seule cette seconde colonne montre que la connexion par socket est active.
Le plugin est-il chargé ? Lors d'une mise à niveau défectueuse, il peut manquer, et le serveur signale alors 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%';"
Si vous voulez sciemment faire passer root à un mot de passe, par exemple parce qu'un script de sauvegarde tourne sous un autre utilisateur système, conservez en plus la variante par socket. Sinon, les scripts de maintenance de la distribution et sudo mariadb ne fonctionnent plus :
ALTER USER 'root'@'localhost' IDENTIFIED VIA unix_socket
OR mysql_native_password USING PASSWORD('VotreNouveauMotDePasse');
De toute façon, la meilleure approche reste un compte d'administration dédié plutôt qu'un mot de passe pour root. Pour les applications, c'est encore plus vrai : une base de données, un utilisateur, uniquement les droits nécessaires.
localhost n'est pas 127.0.0.1
Cette distinction provoque plus de messages « Access denied » que n'importe quel mot de passe erroné. Les clients de MySQL et de MariaDB traitent le nom d'hôte localhost comme un cas particulier et se connectent via le socket Unix. Seul 127.0.0.1 force une connexion TCP. Du point de vue de la gestion des droits, les deux ne désignent pas le même nom de machine, car le serveur inscrit l'hôte localhost pour une connexion par socket, et pour une connexion TCP sur l'adresse de bouclage soit localhost (après résolution inverse), soit 127.0.0.1, selon le réglage de skip_name_resolve.
Un compte qui n'existe que sous la forme 'app'@'127.0.0.1' n'est pas joignable via le socket, et inversement. C'est exactement ce qui se produit lorsqu'une application PHP a host=localhost dans sa configuration : PHP se connecte via le socket, mais les droits ont été attribués pour l'adresse IP. Deux contre-tests :
mariadb -u app -p'MotDePasse' -e "SELECT USER(), CURRENT_USER();"
mariadb -h 127.0.0.1 -u app -p'MotDePasse' -e "SELECT USER(), CURRENT_USER();"
Si une seule des deux échoue, la cause est trouvée. Faites attention à la résolution de noms : tant que skip_name_resolve est désactivé, et c'est le cas dans les installations par paquets de Debian et d'Ubuntu, le serveur résout l'adresse de bouclage en sens inverse vers localhost. Si un compte 'app'@'localhost' existe déjà, la connexion avec -h 127.0.0.1 réussit donc elle aussi, et CURRENT_USER() signale alors app@localhost au lieu de app@127.0.0.1. Un compte 'app'@'127.0.0.1' créé en complément reste sans effet dans ce cas, il ne s'applique que si le compte localhost manque. La solution propre consiste à créer le compte pour le chemin que l'application emprunte réellement, et non à configurer les deux variantes « au cas où ».
Vérifiez en plus si la résolution de noms est désactivée. Si skip_name_resolve est actif, les lignes de compte avec des noms d'hôte comme 'app'@'web01.intern' ne fonctionnent plus du tout, seules les adresses IP comptent alors :
mariadb -e "SHOW VARIABLES LIKE 'skip_name_resolve';"
Un autre écueil est l'ordre dans lequel le serveur sélectionne les lignes correspondantes. Il trie du plus spécifique au plus général et retient la première correspondance, pas la meilleure. Si, à côté de 'app'@'%', il existe encore un compte anonyme ''@'localhost', c'est le compte anonyme qui l'emporte pour une connexion locale, et votre connexion échoue avec un message qui mentionne malgré tout votre nom d'utilisateur. Les paquets actuels ne créent plus de comptes anonymes, mais on en trouve encore sur des systèmes migrés pendant des années :
mariadb -e "SELECT user, host FROM mysql.global_priv WHERE user = '';"
Mot de passe, droits et casse
Restent les causes qui ont réellement à voir avec les identifiants.
Le shell avale les caractères spéciaux
Aucun espace ne doit se glisser entre -p et le mot de passe, sinon le client interprète le mot de passe comme un nom de base de données. Les mots de passe contenant $, !, & ou des espaces se placent entre apostrophes simples, faute de quoi le shell les remplace ou les tronque. Le plus propre reste de ne pas passer du tout le mot de passe sur la ligne de commande, car il atterrit alors dans la liste des processus et dans le fichier d'historique. Créez plutôt un fichier ~/.my.cnf avec les droits 0600 :
[client]
user=app
password=VotreMotDePasse
Inversement, un ~/.my.cnf oublié constitue aussi une cause d'erreur possible. Il écrase silencieusement ce que vous indiquez sur la ligne de commande, et vous obtenez alors « Access denied » pour un utilisateur que vous n'avez jamais saisi.
Casse : oui pour le nom d'utilisateur, non pour l'hôte
Dans MySQL et MariaDB, les noms d'utilisateur doivent être reproduits caractère par caractère, App et app sont deux comptes différents. Les noms d'hôte sont comparés sans tenir compte de la casse, mais stockés exactement tels qu'ils figuraient dans le CREATE USER. Qui a créé par inadvertance 'app'@'LOCALHOST' voit dans les sorties de SHOW GRANTS et dans les scripts deux comptes apparemment différents, qui correspondent pourtant à la même connexion. De tels doublons rendent le diagnostic inutilement pénible, car vous attribuez des droits sur une ligne et le serveur en choisit une autre. Voici comment les repérer :
mariadb -e "SELECT user, host FROM mysql.global_priv WHERE host <> LOWER(host);"
Ce sont les droits qui manquent, pas la connexion
Si ERROR 1044 apparaît au lieu de 1045, la connexion a réussi. Ce sont alors des droits sur une base de données précise qui manquent. Regardez ce que le compte a réellement le droit de faire :
mariadb -e "SHOW GRANTS FOR 'app'@'localhost';"
FLUSH PRIVILEGES ne vous sert que si vous avez modifié les tables de droits directement par INSERT ou UPDATE. Après GRANT, CREATE USER ou ALTER USER, c'est superflu, et cela masque parfois le fait que la commande initiale n'a pas pris effet du tout.
Le client ne comprend pas le plugin
MySQL 8.0 utilise caching_sha2_password par défaut. Les clients et bibliothèques plus anciens répondent par ERROR 2059 (HY000): Authentication plugin 'caching_sha2_password' cannot be loaded. Ce n'est pas un problème de droits, mais une incompatibilité. Dans MySQL 8.0, mysql_native_password reste disponible comme solution de repli, dans MySQL 8.4 il a été supprimé. Dans le doute, mettez plutôt le client à jour au lieu de revenir en arrière sur le chiffrement.
Réinitialiser le mot de passe root
Quand plus rien n'aide, le serveur doit démarrer une fois sans vérification des droits. Deux chemins mènent au but. Celui qui passe par --init-file est le plus sûr, car le serveur y fonctionne en permanence avec la vérification des droits active.
Variante 1 : init-file (recommandée)
Le fichier SQL doit se trouver à un endroit que le service a le droit de lire. Sous /tmp ou /root, cela échoue régulièrement sur Ubuntu à cause d'AppArmor et de PrivateTmp dans le service systemd. Placez-le donc dans le répertoire de données :
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('NouveauMotDePasseRoot');
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
Faites ensuite impérativement le ménage, sinon le serveur repart avec ce fichier à chaque démarrage :
sudo systemctl unset-environment MYSQLD_OPTS
sudo rm /var/lib/mysql/kh-reset.sql
sudo systemctl restart mariadb
Pour MySQL 8.0 sur Ubuntu, le service s'appelle mysql et la ligne SQL devient :
ALTER USER 'root'@'localhost' IDENTIFIED WITH caching_sha2_password BY 'NouveauMotDePasseRoot';
Variante 2 : skip-grant-tables
Cette variante désactive complètement la vérification des droits. Sans --skip-networking, quiconque atteint le port disposerait pendant ce temps d'un accès complet à toutes les données. L'option n'est donc pas facultative, elle est obligatoire.
sudo systemctl stop mariadb
sudo systemctl set-environment MYSQLD_OPTS="--skip-grant-tables --skip-networking"
sudo systemctl start mariadb
sudo mariadb
Dans la session, chargez d'abord les tables de droits, sinon le serveur refuse ALTER USER avec un message d'erreur :
FLUSH PRIVILEGES;
ALTER USER 'root'@'localhost' IDENTIFIED VIA unix_socket
OR mysql_native_password USING PASSWORD('NouveauMotDePasseRoot');
EXIT;
Puis retour au fonctionnement normal :
sudo systemctl unset-environment MYSQLD_OPTS
sudo systemctl restart mariadb
Si votre distribution n'évalue pas la variable MYSQLD_OPTS, le fichier drop-in sous /etc/systemd/system/mariadb.service.d/reset.conf fonctionne toujours, avec le contenu [Service] et Environment="MYSQLD_OPTS=--skip-grant-tables --skip-networking", suivi de sudo systemctl daemon-reload. Supprimez ensuite le fichier et rechargez systemd.
Quand la réinitialisation tourne mal
C'est précisément ici que la plupart des tutoriels s'arrêtent. Les quatre problèmes consécutifs les plus fréquents :
Le service ne démarre plus. Le plus souvent une faute de frappe dans le fichier SQL. Le serveur s'interrompt alors au démarrage. Debian journalise MariaDB par défaut dans le journal, Ubuntu avec MySQL en plus dans un fichier :
sudo journalctl -u mariadb -n 50 --no-pager
sudo tail -n 50 /var/log/mysql/error.log
L'endroit où le serveur écrit se vérifie avec SHOW VARIABLES LIKE 'log_error';. N'y attendez toutefois aucun chemin de fichier : sur les installations par paquets de Debian et d'Ubuntu, la valeur est vide, parce que le service tourne avec --skip-log-error. Tout part alors vers la sortie d'erreur standard et atterrit dans le journal ou dans /var/log/syslog, consultable avec journalctl -u mariadb. Pour corriger, retirez la variable d'environnement --init-file et redémarrez.
Le serveur tourne durablement sans vérification des droits. Cela arrive quand le unset-environment a été oublié, et c'est la variante la plus dangereuse, car vu de l'extérieur tout semble normal. Deux contrôles :
systemctl show-environment
ps -o args= -C mariadbd
Si skip-grant-tables apparaît dans l'une des deux sorties, la vérification des droits est encore désactivée. systemctl show-environment suppose par ailleurs que systemd tourne en tant que PID 1. Sur un serveur ordinaire, c'est acquis, mais dans un conteneur (container) ou sur un système avec SysV-Init, la commande n'existe pas. Vous y lisez alors l'environnement directement depuis le processus en cours :
cat /proc/$(pgrep -n mariadbd)/environ | tr '\0' '\n'
Ne vous fiez pas non plus à la seule liste d'arguments : lancé via systemd, ps n'affiche souvent que /usr/sbin/mariadbd sans la moindre option, alors qu'un démarrage par un script SysV-Init fait apparaître la liste complète. Autre indice : sous skip-grant-tables, SHOW GRANTS répond par un message d'erreur au lieu de lister des droits.
AppArmor bloque le fichier init. Le symptôme est un serveur qui ne démarre pas sans raison apparente. Un coup d'œil dans le journal du noyau permet de trancher :
sudo dmesg | grep -i denied
Vous vous verrouillez complètement dehors. Cela se produit quand on fait passer root à un simple mot de passe via ALTER USER ... IDENTIFIED BY, qu'on perd au passage l'authentification par socket et qu'on ne se souvient plus ensuite du nouveau mot de passe. La sortie de secours consiste à refaire la même réinitialisation, cette fois avec la double variante unix_socket OR mysql_native_password montrée plus haut. Faites-vous une copie des tables de droits avant chaque intervention, cela prend quelques secondes et épargne des heures en cas de coup dur :
sudo mariadb-dump mysql > /root/mysql-grants.sql
L'option --single-transaction, habituelle par ailleurs, est ici inutile. Elle s'exécute certes sans erreur, mais reste sans effet, car les tables de la base de données mysql reposent sur Aria ou MyISAM et ne sont donc pas transactionnelles.
Sur un hébergement web mutualisé et géré, vous n'avez pas d'accès système et donc aucune de ces options. Les utilisateurs et mots de passe de base de données y sont réinitialisés via l'interface d'administration. Sur un serveur root KVM ou un serveur dédié chez KernelHost, vous disposez d'un accès root complet, et si le serveur n'est plus joignable par la connexion réseau, vous atteignez le système via la console dans l'espace client.
Vérifier que le problème est vraiment résolu
Qu'une commande s'exécute sans erreur ne signifie pas encore que la connexion fonctionne durablement. Ces quatre contrôles révèlent les erreurs résiduelles typiques.
Premièrement, la différence entre USER() et CURRENT_USER(). La première fonction montre ce que vous avez déclaré être, la seconde quelle ligne de compte le serveur utilise réellement :
mariadb -u app -p'MotDePasse' -e "SELECT USER(), CURRENT_USER();"
Si deux valeurs différentes y figurent, par exemple app@localhost et app@%, alors vos droits passent par une autre ligne que celle imaginée. Cela explique les erreurs 1044 qui surviennent plus tard, avant qu'elles ne se manifestent en production.
Deuxièmement, un accès réel à la base de données cible et non une simple connexion, ainsi que l'évaluation du code de retour :
mariadb -u app -p'MotDePasse' ma_base -e "SELECT 1;" ; echo "Code de retour : $?"
Troisièmement, un redémarrage du service, puis de nouveau la même connexion. Vous vous assurez ainsi que la modification figure vraiment dans les tables et pas seulement dans la mémoire du processus en cours, et qu'aucune variable d'environnement de la réinitialisation n'est restée :
sudo systemctl restart mariadb
sudo systemctl is-active mariadb
Quatrièmement, l'application elle-même. Un test réussi sur la ligne de commande dit peu de chose d'une application PHP qui se connecte sous l'utilisateur du serveur web et via le socket. Testez donc sous le même utilisateur système :
sudo -u www-data mariadb -u app -p'MotDePasse' ma_base -e "SELECT CURRENT_USER();"
Différences entre les distributions
Un tutoriel qui affirme la même chose pour les quatre systèmes se trompe sur au moins un point. Ce tableau résume les écarts pertinents :
| Système | Base de données par défaut | Plugin de socket | Remarque |
|---|---|---|---|
| Debian 13 | MariaDB 11.8 | unix_socket | aucun paquet mysql-server disponible ; mysql n'est plus qu'un lien symbolique vers mariadb et affiche un avertissement à l'appel |
| Debian 12 | MariaDB 10.11 | unix_socket | aucun paquet mysql-server disponible |
| Ubuntu 24.04 | MariaDB 10.11 ou MySQL 8.0 | unix_socket ou auth_socket | les deux paquets côte à côte dans les sources, risque de confusion avec les tutoriels |
| Ubuntu 22.04 | MariaDB 10.6 ou MySQL 8.0 | unix_socket ou auth_socket | JSON_VALUE et JSON_QUERY sur mysql.global_priv fonctionnent ici exactement comme sous 10.11 et 11.8 ; en 10.6 aussi, mysql.user n'est qu'une vue, et pour la chaîne d'authentification complète SHOW CREATE USER reste le chemin le plus court |
D'autres différences coûtent du temps en pratique. Les fichiers de configuration se trouvent chez MariaDB sous /etc/mysql/mariadb.conf.d/50-server.cnf, chez MySQL sous /etc/mysql/mysql.conf.d/mysqld.cnf. Les noms de service sont respectivement mariadb et mysql, MariaDB fournissant en plus un alias mysql. Chez MariaDB, l'utilisateur debian-sys-maint n'existe plus depuis la version 10.4, le fichier /etc/mysql/debian.cnf y renvoie vers root via le socket. Chez MySQL sur Ubuntu, l'utilisateur de maintenance existe toujours, et c'est le meilleur accès de secours quand le mot de passe root est perdu et que vous ne voulez pas redémarrer le serveur :
sudo mysql --defaults-file=/etc/mysql/debian.cnf
Si vous respectez cet ordre, à savoir d'abord lire le message avec précision, puis vérifier le plugin et la ligne d'hôte, ensuite le chemin de connexion, et seulement tout à la fin réinitialiser le mot de passe, la grande majorité des cas se résout en quelques minutes et sans interruption de service. La réinitialisation via skip-grant-tables est le dernier recours, pas la première étape.
Questions fréquentes
Pourquoi mysql -u root -p ne fonctionne-t-il pas alors que le mot de passe est correct ?
Quelle est la différence entre ERROR 1045 et ERROR 1698 ?
localhost est-il la même chose que 127.0.0.1 ?
La casse compte-t-elle pour le nom d'utilisateur et pour l'hôte ?
Comment réinitialiser le mot de passe root sans skip-grant-tables ?
À quoi reconnaît-on que le serveur tourne encore sans vérification des droits ?
Existe-t-il un paquet mysql-server sous Debian ?
2026 KernelHost GmbH. Tous droits réservés. Ce guide est protégé par le droit d'auteur. Sa republication sur d'autres sites web, même partielle ou sous une forme modifiée, n'est pas autorisée sans notre accord écrit. Les citations accompagnées de la source et d'un lien sont expressément les bienvenues.

