Home / Tutorials / MySQL

MySQL · Database · Auth blocked

Fix: ERROR 1045 (28000): Access denied for user

Error ERROR 1045 (28000): Access denied for user

By the Erzon engineering team · A step-by-step fix, not a forum thread

ERROR 1045 is MySQL rejecting authentication for the specific user@host pair it resolved your connection to. The message quotes both halves, and the host half is where most of the confusion lives: MySQL treats 'app'@'localhost', 'app'@'10.0.0.5', and 'app'@'%' as entirely separate accounts, each with its own password and privileges. A password that works from one machine can fail from another because a different account matched. Read the quoted pair carefully before touching any passwords.

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

What this error means

When a connection arrives, MySQL sorts all accounts by host specificity and picks the first match for your username and source address. Authentication then runs against that one account only. Error 1045 means that account’s check failed: wrong password, an account that exists for a different host than the one you arrived from, an authentication plugin mismatch, or using password: NO because the client sent no password at all. The parenthetical matters: using password: NO means the credentials never reached the server, which points at the client configuration, not the account.

Common causes

  • The password is wrong for the specific matched account, even if it is right for a same named account on another host.
  • No account exists for the source host: 'app'@'localhost' exists but the app now connects over TCP from 10.0.0.5.
  • using password: NO: the client or a config file sent an empty password, often a missing environment variable that silently became an empty string.
  • MySQL 8’s caching_sha2_password default rejecting old client libraries that only speak mysql_native_password.
  • The connection arrives via a proxy, container gateway, or NAT, so the source IP MySQL sees is not the one you expect.
  • Special characters in the password mangled by shell quoting or URL encoding in the connection string.
  • skip_name_resolve toggled, changing whether hostname based accounts ('app'@'web1.internal') can ever match.

How to fix it

STABILIZE first. If production is locked out, identify the exact user@host in the error and reset that account’s password or create the missing account; both take effect immediately without a restart.

  1. DIAGNOSE: reproduce with the CLI so you control every variable, and note the exact quoted pair in the failure.
mysql --protocol=TCP -h db.internal -P 3306 -u app -p appdb
  1. DIAGNOSE: as an admin user, list which accounts actually exist for that username.
SELECT user, host, plugin FROM mysql.user WHERE user = 'app';

Compare the host values against the host quoted in the error. If nothing matches the source (for example the error says 'app'@'10.0.0.%' territory but only 'app'@'localhost' exists), the account is missing, not the password.

  1. FIX a missing account: create it for the network the application really connects from, and grant only what it needs.
CREATE USER 'app'@'10.0.0.%' IDENTIFIED BY 'use-a-generated-secret';
GRANT SELECT, INSERT, UPDATE, DELETE ON appdb.* TO 'app'@'10.0.0.%';
FLUSH PRIVILEGES;
  1. FIX a wrong password: reset it for the exact pair that matched.
ALTER USER 'app'@'10.0.0.%' IDENTIFIED BY 'use-a-generated-secret';
  1. FIX a plugin mismatch: if the client library predates MySQL 8, prefer upgrading the driver. If you need immediate compatibility, switch that one account’s plugin.
ALTER USER 'app'@'10.0.0.%' IDENTIFIED WITH mysql_native_password BY 'use-a-generated-secret';
  1. DIAGNOSE using password: NO: the client sent no password. Check the application’s environment and any option files the client silently reads.
mysql --print-defaults

Then verify the deployed environment variable is set in the running process, not just in your shell.

How to prevent it

  • Manage MySQL users and grants in code (migrations or Terraform) so accounts exist for the right hosts on every environment.
  • Standardize on one account host pattern per application (for example the VPC CIDR) instead of a mix of localhost, IPs, and %.
  • Store passwords in a secrets manager and fail startup loudly when a credential resolves empty, so using password: NO never reaches the database.
  • Prefer upgrading client libraries over downgrading server authentication plugins.
  • Grant least privilege per schema; never reuse the root account for applications.
  • Log the resolved connection target and user at application startup so a misrouted connection is visible in seconds.

When to call a senior engineer

Call for help when logins fail only from some machines and the user@host matching does not add up, when a proxy or container network layer hides the true source address, or when an authentication plugin migration has to happen across a fleet of services without downtime. Erzon engineers can trace exactly which account each connection matches, clean up the accumulated user table, and leave your grants managed in code so access denied stops being a production surprise.

Related errors we fix

Still down after this?

Want a senior engineer to just fix it?

Paste this exact error into the form. A senior engineer replies within one business hour with what is likely wrong and a flat quote. Triage is free.

Book a fix

← Back to all error fixes

Still down?

Get a senior engineer on it now

Leave your email and a line about what is happening. A senior engineer replies within one business hour with what is likely wrong and a flat quote. Triage is free.

Or email [email protected]. Same engineers, same one-hour clock.