Aller au contenu
Sylvain Joffraud l4w
Retour

`Access denied @localhost` : grant @'%' vs socket Unix

Mise en service d’un backend Symfony sur un serveur de recette fourni par le client. La connexion à MariaDB refuse en boucle :

ERROR 1045 (28000): Access denied for user 'app-user'@'localhost' (using password: YES)

On a passé deux jours sur ce 28000. Demande d’un nouveau mot de passe à l’infra, mot de passe régénéré, re-test… toujours KO. On commençait sérieusement à douter du compte applicatif lui-même.

Symptôme

Le mot de passe semblait correct (copié-collé, pas d’espace parasite), le serveur MariaDB tournait, le port 3306 répondait. Et pourtant, depuis le serveur, en testant à la main :

mariadb -u app-user -p'<password>' app_db -e "SELECT 1"
# → ERROR 1045 (28000): Access denied for user 'app-user'@'localhost'

L’infra renvoyait la balle (« le compte est bon de notre côté »), nous renvoyions la nôtre (« il refuse le mot de passe »). Blocage.

Cause

La solution était dans le message d’erreur depuis le début : 'app-user'@'localhost'.

Côté client MySQL/MariaDB, localhost n’est pas un nom d’hôte réseau. C’est un cas spécial qui force la connexion par socket Unix (/run/mysqld/mysqld.sock), pas par TCP. 127.0.0.1, lui, passe par TCP. Ce sont deux chemins de connexion distincts — et côté serveur, MariaDB distingue les grants par host :

Le compte avait bien été créé… mais uniquement avec un grant sur @'%'. Aucun grant sur @'localhost'. Or mariadb -u app-user -p… sans -h tombe par défaut sur le socket → host effectif localhost → aucun grant → Access denied.

Le mot de passe était bon depuis le premier jour. Ce n’était pas un problème d’authentification, c’était un problème de host de connexion. Un SHOW GRANTS l’aurait montré en dix secondes :

SHOW GRANTS FOR 'app-user'@'%';
-- GRANT ALL PRIVILEGES ON `app_db`.* TO `app-user`@`%`
-- (et RIEN pour 'app-user'@'localhost')

Solution

Forcer le TCP, et c’est passé du premier coup :

mariadb -u app-user -p'<password>' -h 127.0.0.1 --protocol=TCP app_db -e "SELECT 1"
# → 1  ✓

Côté application, la même classe de bug se neutralise dans la DATABASE_URL Symfony en utilisant 127.0.0.1 et jamais localhost :

# ❌ force le socket Unix → tombe sur un host sans grant
DATABASE_URL="mysql://app-user:<password>@localhost:3306/app_db?serverVersion=mariadb-10.6"

# ✅ TCP explicite → host '%', grant OK
DATABASE_URL="mysql://app-user:<password>@127.0.0.1:3306/app_db?serverVersion=mariadb-10.6"

Subtilité piégeuse : même avec un port :3306 dans la chaîne, certains clients/connecteurs continuent de privilégier le socket si le host littéral est localhost. Le port n’est pas suffisant pour forcer le TCP — c’est le nom d’hôte qui décide du transport. D’où 127.0.0.1.

Lessons learned

#mariadb #mysql #symfony #devops #debugging

Questions fréquentes

Pourquoi MariaDB me refuse en Access denied for user @localhost alors que le mot de passe est correct ?
Souvent ce n’est pas le mot de passe : le message 'user'@'localhost' indique que vous êtes connecté par socket Unix, pas en TCP. Côté serveur, MariaDB distingue les grants par host, et si le compte n’a un grant que sur @'%' (TCP), la connexion socket @'localhost' échoue avec une erreur qui ressemble à un mauvais mot de passe.
Quelle est la différence entre localhost et 127.0.0.1 pour se connecter à MySQL ou MariaDB ?
Côté client MySQL/MariaDB, localhost est un cas spécial historique qui force la connexion par socket Unix (/run/mysqld/mysqld.sock), tandis que 127.0.0.1 passe par TCP. Ce sont deux transports distincts, pas une simple résolution DNS, et le serveur peut avoir des grants différents pour chacun.
Un grant sur @'%' suffit-il pour les connexions en localhost ?
Non. Le host % matche les connexions réseau (TCP), pas la connexion par socket Unix. Une connexion arrivant en @'localhost' a besoin d’un grant explicite @'localhost' ; sinon elle échoue. Vérifiez-le avec SHOW GRANTS FOR 'user'@'localhost';.
Comment forcer une connexion TCP avec mariadb ou dans une DATABASE_URL Symfony ?
En ligne de commande, ajoutez -h 127.0.0.1 --protocol=TCP car un port comme :3306 ne suffit pas à forcer le TCP si le host littéral reste localhost. Dans DATABASE_URL, écrivez toujours 127.0.0.1 plutôt que localhost pour viser le grant @'%' et éliminer la classe de bug.

Partager cet article :

Article précédent
1NCE Flat 10 ans : 500 Mo TOTAL sur la décennie, pas annuel
Article suivant
Traccar `position.attributes` : bien plus que lat/lon