MySQL Error Guide: 'ERROR 1045 (28000)' Access Denied for User
Fix MySQL ERROR 1045 (28000) Access denied for user: diagnose wrong passwords, host-mismatched grants, auth plugin issues, anonymous users, and missing privileges.
- #mysql
- #troubleshooting
- #errors
- #authentication
Overview
ERROR 1045 (28000) is MySQL’s authentication failure. The server received your connection, looked up the account in the mysql.user table keyed on (user, host), and rejected the credentials. The most common message is:
ERROR 1045 (28000): Access denied for user 'app'@'10.0.1.45' (using password: YES)
The account is identified by both the username and the host the connection appears to come from. 'app'@'localhost' and 'app'@'%' are two different accounts with potentially different passwords and privileges. The (using password: YES/NO) tells you whether a password was actually sent — NO usually means your client never picked up the password.
It occurs at connect time: from the mysql CLI, an application connection pool, a replication CHANGE MASTER/CHANGE REPLICATION SOURCE, or a backup tool like mysqldump.
Symptoms
- Client cannot connect; immediate
Access denied for user. - Application pool throws 1045 on startup or after a credential rotation.
- The same password works from one host but not another.
(using password: NO)appears even though you believe you set a password.
mysql -u app -p -h db01.internal
Enter password:
ERROR 1045 (28000): Access denied for user 'app'@'10.0.1.45' (using password: YES)
Common Root Causes
1. Wrong password (or password not actually sent)
The simplest cause: the password is wrong, or the client never sent one (using password: NO).
mysql -u app --password=wrongpass -h 127.0.0.1
ERROR 1045 (28000): Access denied for user 'app'@'127.0.0.1' (using password: YES)
If you see using password: NO, your -p flag, .my.cnf, or app config did not supply the password. Note there is no space after -p on the CLI.
2. Host mismatch — the grant is for a different host
The account exists but not for the host you are connecting from. Connecting via 127.0.0.1 (TCP) vs localhost (socket) resolves to different host rows.
SELECT user, host, plugin FROM mysql.user WHERE user = 'app';
+------+-----------+-----------------------+
| user | host | plugin |
+------+-----------+-----------------------+
| app | localhost | mysql_native_password |
| app | 10.0.1.% | caching_sha2_password |
+------+-----------+-----------------------+
A connection from 10.0.2.7 matches neither row — Access denied. The grant must cover the source IP.
3. Anonymous user shadowing your account
A leftover anonymous account ''@'localhost' matches before 'app'@'%' because MySQL sorts host matches most-specific-first and an empty user on localhost is very specific.
SELECT user, host FROM mysql.user WHERE user = '';
+------+-----------+
| user | host |
+------+-----------+
| | localhost |
+------+-----------+
The anonymous user has no password, so your real password is rejected. mysql_secure_installation removes these.
4. Authentication plugin mismatch (caching_sha2_password)
MySQL 8.0 defaults to caching_sha2_password. Older clients/connectors that only speak mysql_native_password, or non-TLS connections without the server public key, get denied.
SELECT user, host, plugin FROM mysql.user WHERE user = 'app';
+------+------+-----------------------+
| user | host | plugin |
+------+------+-----------------------+
| app | % | caching_sha2_password |
+------+------+-----------------------+
ERROR 1045 (28000): Access denied for user 'app'@'10.0.1.45' (using password: YES)
If the connector cannot do the SHA2 exchange, switch the account to mysql_native_password or upgrade the driver and use TLS.
5. The account simply does not exist
A typo in the username, or the account was never created on this server (common after a failover to a replica that was provisioned separately).
SELECT COUNT(*) FROM mysql.user WHERE user = 'app';
+----------+
| COUNT(*) |
+----------+
| 0 |
+----------+
6. Stale privilege cache after manual edits
If someone edited the grant tables directly with INSERT/UPDATE instead of CREATE USER/GRANT, the in-memory privilege cache is stale until reloaded.
FLUSH PRIVILEGES;
After a FLUSH PRIVILEGES, direct table changes take effect. (Prefer CREATE USER/ALTER USER, which never require this.)
Diagnostic Workflow
Step 1: Read the exact host and password flag in the error
mysql -u app -p -h db01.internal 2>&1 | tail -1
ERROR 1045 (28000): Access denied for user 'app'@'10.0.1.45' (using password: YES)
Note the host as MySQL sees it (10.0.1.45) and whether a password was sent. These two facts drive everything below.
Step 2: List the account’s host rows and auth plugin
Connect as an admin (often root@localhost via socket) and inspect:
SELECT user, host, plugin, account_locked
FROM mysql.user WHERE user = 'app';
Confirm a row whose host pattern matches the IP from Step 1, and check account_locked = 'N'.
Step 3: Check for anonymous users and account locks
SELECT user, host FROM mysql.user WHERE user = '';
SELECT user, host, account_locked, password_expired
FROM mysql.user WHERE user = 'app';
+------+----------------+----------------+
| user | account_locked | password_expired |
+------+----------------+----------------+
| app | N | N |
+------+----------------+----------------+
A password_expired = Y also yields auth failures until the password is reset.
Step 4: Reset the password / fix the host as admin
-- Reset for the existing host pattern
ALTER USER 'app'@'10.0.1.%' IDENTIFIED BY 'NewStr0ng!Pass';
-- Or create the missing host grant
CREATE USER 'app'@'10.0.2.%' IDENTIFIED BY 'NewStr0ng!Pass';
GRANT SELECT, INSERT, UPDATE, DELETE ON appdb.* TO 'app'@'10.0.2.%';
Step 5: Verify the connection and the effective grants
mysql -u app -p -h db01.internal -e "SELECT CURRENT_USER();"
+----------------+
| CURRENT_USER() |
+----------------+
| app@10.0.1.% |
+----------------+
CURRENT_USER() shows which account row actually authenticated — confirm it is the one you intended.
Example Root Cause Analysis
An application that worked for months suddenly fails after a network change with:
ERROR 1045 (28000): Access denied for user 'app'@'10.0.5.12' (using password: YES)
The DBA checks the grant table:
SELECT user, host FROM mysql.user WHERE user = 'app';
+------+----------+
| user | host |
+------+----------+
| app | 10.0.1.% |
+------+----------+
The app was migrated to a new subnet (10.0.5.0/24) but the grant only covers 10.0.1.%. The password is correct (using password: YES), the account exists — it is purely a host-match failure.
Fix: add the new subnet and copy the privileges:
CREATE USER 'app'@'10.0.5.%' IDENTIFIED BY 'ExistingAppPass';
GRANT SELECT, INSERT, UPDATE, DELETE ON appdb.* TO 'app'@'10.0.5.%';
The app reconnects cleanly. The old 10.0.1.% grant is dropped once the migration completes.
Prevention Best Practices
- Use restrictive but stable host patterns (e.g. a
/24per app tier) instead of per-IP grants that break on every reschedule. Manage them as code so a subnet change updates grants in the same change set. - Run
mysql_secure_installationon every new server to drop anonymous users and the test database that cause silent shadowing. - Standardize on one auth plugin across the fleet and ensure all connectors support it. If you must keep legacy drivers, set
default_authentication_plugin=mysql_native_passwordconsciously, with TLS. - Always use
CREATE USER/ALTER USERrather than editingmysql.userdirectly, so you never depend on rememberingFLUSH PRIVILEGES. - Rotate credentials through a secrets manager and verify with a connection smoke test before retiring the old password.
- For fast triage, the free incident assistant can map a 1045 error line to the likely host/plugin cause. More in MySQL guides.
Quick Command Reference
# Reproduce and capture the exact host + password flag
mysql -u app -p -h db01.internal 2>&1 | tail -1
# Test TCP vs socket (different host rows!)
mysql -u app -p -h 127.0.0.1 # TCP -> matches an IP host pattern
mysql -u app -p -h localhost # socket -> matches 'localhost'
-- Inspect the account's host rows and plugin
SELECT user, host, plugin, account_locked, password_expired
FROM mysql.user WHERE user = 'app';
-- Find shadowing anonymous users
SELECT user, host FROM mysql.user WHERE user = '';
-- Reset password / add a host grant
ALTER USER 'app'@'10.0.1.%' IDENTIFIED BY 'NewStr0ng!Pass';
CREATE USER 'app'@'10.0.5.%' IDENTIFIED BY 'NewStr0ng!Pass';
GRANT SELECT, INSERT, UPDATE, DELETE ON appdb.* TO 'app'@'10.0.5.%';
-- Confirm which row authenticated
SELECT CURRENT_USER();
Conclusion
ERROR 1045 (28000) means MySQL found no (user, host) row that accepts the credentials you sent. The usual root causes:
- A wrong password, or the client never sent one (
using password: NO). - The grant exists for a different host than your connection’s source IP.
- An anonymous
''@'localhost'account shadowing the real one. - An auth-plugin mismatch (
caching_sha2_password) the connector cannot satisfy. - The account does not exist on this server at all.
- A stale privilege cache after direct edits to the grant tables.
Read the host and using password flag in the error first, then compare against mysql.user — the fix is almost always aligning the host pattern, password, or auth plugin.
Download the Free 500-Prompt DevOps AI Toolkit
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.