ERROR 1045 (28000): Access denied for user 'username'@'hostname' (using password: YES) is returned by MySQL when authentication fails. The server understood the connection request but rejected it — either the credentials are wrong, the user account does not exist for that host, the account is locked, or the required privilege is not granted.
Impact
Access denied errors prevent the application from connecting entirely. They are returned before any query is executed, so the impact is a hard failure at connection time. In production this surfaces as application startup failures or database errors on the first request after a deployment or configuration change.
Common Causes
- Wrong password in the application's connection string or environment variable
- User exists but only for a different host — MySQL matches
user + hostas a unit:'app'@'10.0.1.5'and'app'@'%'are different accounts - User account does not exist at all (forgot to create it after provisioning a new environment)
- Password was recently rotated on the database but not updated in the application configuration
- Authentication plugin mismatch: MySQL 8.0 defaults to
caching_sha2_password, but older client libraries (libmysqlclient < 8.0, legacy JDBC drivers) only supportmysql_native_password - Account is locked:
ALTER USER ... ACCOUNT LOCKwas set or the account hitfailed_login_attemptslimit - Password expired:
ALTER USER ... PASSWORD EXPIREwas set ordefault_password_lifetimehas elapsed skip-grant-tablesmode was active during a previous session and was removed — accounts created without proper privilege flush may be inconsistent- SSL/TLS requirement on the account (
REQUIRE SSL) not satisfied by the connecting client - The client is connecting via Unix socket to a different hostname than expected, or a proxy is changing the source IP
Troubleshooting and Resolution Steps
Confirm which account MySQL sees the connection as:
-- After successfully connecting as root or admin SELECT user, host FROM mysql.user WHERE user = 'target_user';MySQL selects the most specific matching row. If the client connects from
10.0.1.5and the account is'app'@'10.0.1.%', it matches. If there is also an'app'@'%'row, the more-specific subnet mask wins.List all accounts and their hosts to spot host mismatches:
SELECT user, host, account_locked, password_expired, plugin FROM mysql.user WHERE user = 'target_user' ORDER BY host;Check whether the user exists for the connecting host. If not, create it:
-- Create with a specific host CREATE USER 'app'@'10.0.1.%' IDENTIFIED BY 'secure_password'; -- Or for any host (less restrictive) CREATE USER 'app'@'%' IDENTIFIED BY 'secure_password'; -- Grant required privileges GRANT SELECT, INSERT, UPDATE, DELETE ON mydb.* TO 'app'@'%'; FLUSH PRIVILEGES;Reset the password if it was rotated or forgotten:
ALTER USER 'app'@'%' IDENTIFIED BY 'new_secure_password';In MySQL 5.7 (alternative syntax):
SET PASSWORD FOR 'app'@'%' = PASSWORD('new_secure_password');Fix authentication plugin mismatch (the most common MySQL 8.0 upgrade issue). Client libraries that do not support
caching_sha2_passwordget error 1045 even with the correct password:-- Check the plugin for the user SELECT user, host, plugin FROM mysql.user WHERE user = 'app'; -- Change to the legacy plugin for compatibility ALTER USER 'app'@'%' IDENTIFIED WITH mysql_native_password BY 'secure_password'; -- Or create new users with the legacy plugin CREATE USER 'app'@'%' IDENTIFIED WITH mysql_native_password BY 'secure_password';The right long-term fix is to upgrade the client library to one that supports
caching_sha2_password. Falling back tomysql_native_passwordis a workaround — the plugin was deprecated in MySQL 8.4 and removed in MySQL 9.0.To make
mysql_native_passwordthe server default (not recommended for new deployments):[mysqld] default_authentication_plugin = mysql_native_passwordUnlock a locked account:
ALTER USER 'app'@'%' ACCOUNT UNLOCK;Fix an expired password:
ALTER USER 'app'@'%' IDENTIFIED BY 'new_password' PASSWORD EXPIRE NEVER; -- Or set a policy ALTER USER 'app'@'%' PASSWORD EXPIRE INTERVAL 365 DAY;Check SSL requirements on the account:
SHOW GRANTS FOR 'app'@'%';If the output includes
REQUIRE SSLorREQUIRE X509, the client must connect with TLS. Either update the connection string to include SSL, or remove the requirement:ALTER USER 'app'@'%' REQUIRE NONE;Find the connecting hostname that MySQL sees. The error message shows the exact host string MySQL matched. If it shows an IP where you expected a hostname (or vice versa),
skip_name_resolvemay be affecting resolution:SHOW VARIABLES LIKE 'skip_name_resolve';With
skip_name_resolve=ON, MySQL never performs DNS resolution — all host matching uses IP addresses. Accounts created with a hostname (e.g.,'app'@'dbclient.internal') never match when this is on.Recover root access when password is unknown (requires server access):
# Stop MySQL systemctl stop mysql # Start with grant tables bypassed (no auth) mysqld --skip-grant-tables --skip-networking & # Connect without a password mysql -u root # Reset the password FLUSH PRIVILEGES; ALTER USER 'root'@'localhost' IDENTIFIED BY 'new_root_password';Then stop and restart MySQL normally.
Grant privileges after creation. A user with no grants gets error 1044 (Access denied for database), not 1045, but the two are often confused:
SHOW GRANTS FOR 'app'@'%'; -- Typical application grants GRANT SELECT, INSERT, UPDATE, DELETE ON mydb.* TO 'app'@'%'; -- For schema migrations (use sparingly) GRANT ALTER, CREATE, DROP, INDEX, REFERENCES ON mydb.* TO 'migrations'@'%'; FLUSH PRIVILEGES; -- required after direct mysql.user table edits; not needed after GRANT
Additional Information
- MySQL matches accounts using the most specific host pattern. The specificity order is: exact IP > exact hostname > subnet wildcard >
'%'. When two rows have the same user and overlapping host patterns, the more specific one wins. If this is unexpected, runSELECT user, host FROM mysql.user ORDER BY user, hostto see the full list. - The message
(using password: YES)/(using password: NO)in the error tells you whether a password was sent at all.NOwith a password in the connection string usually means the client driver is not sending it (check the driver'spasswordparameter name). FLUSH PRIVILEGESis required only when you modify the grant tables directly withINSERT/UPDATE/DELETE.GRANT,REVOKE,CREATE USER, andALTER USERstatements flush automatically.- In MySQL 8.0.27+, the failed login tracking variables (
failed_login_attempts,password_lock_time) can lock an account after repeated failed attempts. CheckSHOW CREATE USER 'app'@'%'to see if this is configured.
Frequently Asked Questions
Q: The password is definitely correct — why is it still failing? A: The most common non-password causes are (1) the account exists for a different host than the one connecting, (2) the authentication plugin on the server does not match what the client supports, and (3) the account is locked or the password is expired.
Q: I can connect as root but not as the app user. What should I check first?
A: Run SELECT user, host, plugin, account_locked, password_expired FROM mysql.user WHERE user = 'app' as root, then match the host against the client's IP address. A plugin mismatch (caching_sha2_password vs. an old driver) is the most common cause on MySQL 8.0.
Q: I created the user with IDENTIFIED BY 'password' but I still get access denied.
A: Check whether you also ran GRANT ... TO 'user'@'host'. Creating a user does not grant any privileges. Also verify that the @'host' exactly matches the host the client connects from.
Q: Can I grant from any IP without creating separate accounts?
A: Yes — use 'user'@'%' as the host wildcard. For security, prefer subnet masks ('user'@'10.0.%') over '%' in production to limit which networks can authenticate.
Q: After using --skip-grant-tables for recovery, I can log in but GRANT commands fail. Why?
A: In --skip-grant-tables mode, the privilege system is entirely disabled. Run FLUSH PRIVILEGES; first to re-enable it in the current session, then run your ALTER USER or GRANT statements.