MySQL "Access denied for user" — Grant, Host, and Password Traps

Access denied means MySQL matched you to a user@host account and rejected the credentials — or no such account exists for your host. Get the host part right and it's usually solved.

What you'll see

Root causes

MySQL accounts are user@host pairs

'app'@'localhost' and 'app'@'10.0.0.%' are different accounts with different passwords/privileges. SELECT user, host FROM mysql.user; shows what exists.

(using password: NO) — the client didn't send one

Empty MYSQL_PASSWORD/DB_PASSWORD env, missing --password flag, or ~/.my.cnf not readable → MySQL never got a password to check.

Password set on a different account than the one connecting

ALTER USER 'app'@'%' IDENTIFIED BY ... but the client matches a more specific host entry ('app'@'localhost') with an old password — most-specific host rule wins.

Fix it

  1. See the exact accounts and host patterns
    sudo mysql -e "SELECT user, host, plugin FROM mysql.user WHERE user LIKE 'app%';"
  2. Set the password for the account the error names
    ALTER USER 'app'@'%' IDENTIFIED BY 'strong-pass'; FLUSH PRIVILEGES;
  3. Grant the needed schema access explicitly
    GRANT SELECT, INSERT, UPDATE, DELETE ON appdb.* TO 'app'@'%';   # '%' = any host — scope tighter on real networks
  4. Reproduce the app's exact attempt
    mysql -h <host> -u app -p -e 'SELECT current_user(), user();'   # current_user() = the matched account; user() = who you claimed to be

Field note

current_user() vs user() in MySQL: if they differ, a broader host account (or anonymous ''@'localhost') matched first — that's the root of many phantom denials. caching_sha2_password (default in MySQL 8) breaks older clients/drivers; create the user with mysql_native_password or upgrade the connector.

Common questions

Why does the error say user 'x'@'10.0.4.7' when I connected as x@'%'?

MySQL rewrites your identity to the matched account. The shown host is your actual source IP — the account pattern that matched may be a different, more specific one than you intended. Compare current_user() after connecting.

Does FLUSH PRIVILEGES matter after ALTER USER?

Not for ALTER/GRANT via SQL — those take effect immediately. FLUSH matters only when you edit grant tables directly with INSERT/UPDATE.

Ship it right the first time

Our most-documented failures, packaged as ready-to-ship starter kits: Docker, Kubernetes, and Terraform.

Browse the template store →

One-time. Yours to modify. Instant download from the NinjaOps template store.