Skip to content
ohsudo
Databases

MySQL ERROR 1045 (28000): Access denied for user — find which part is wrong

Fix MySQL "ERROR 1045 Access denied for user": check the user@host account, the password, socket vs TCP, option files, and Docker init passwords.

Beginner5 min readApplies to MySQL 8.0 · MySQL 8.4 LTS · MariaDB 10.6+ · Ubuntu · Debian · RHEL / Rocky / AlmaLinux · Docker

by OhSudo ·

You try to connect and MySQL says no:

$ mysql -u app -p shop
Enter password:
ERROR 1045 (28000): Access denied for user 'app'@'localhost' (using password: YES)

The message looks like “wrong password”, and often it is. But MySQL gives the same message for every login failure, on purpose, so it doesn’t help attackers. Read the three parts of it carefully — each one is a clue:

  • 'app': the user name the server received.
  • @'localhost': the host the server thinks you are connecting from. In MySQL, an account is a user and a host: 'app'@'localhost' and 'app'@'%' are two different accounts, with different passwords.
  • (using password: YES): a password was sent. NO means none was sent at all.

TL;DR

Terminal window
mysql -u app -p -h 127.0.0.1 # -p alone: type the password at the prompt
sudo mysql # admin login on Ubuntu/Debian (socket auth)
-- as an admin: which accounts exist for this user, and how do they log in?
SELECT user, host, plugin FROM mysql.user WHERE user = 'app';
-- set a new password for the exact account
ALTER USER 'app'@'%' IDENTIFIED BY 'new-password-here';

1. “using password: NO” — no password was sent

The client didn’t send a password, so the server refused the login. Usual causes:

  • You forgot -p. mysql -u app shop sends no password.
  • The app’s config has an empty or missing password variable (check the environment the app really runs with, not your shell).

Use -p without the password after it, and type it at the prompt. Passwords on the command line end up in your shell history and in the process list.

2. Wrong password (or the shell changed it)

If you are sure the password is right, check that it reaches MySQL unchanged. In a shell, $, !, & and spaces have special meanings:

Terminal window
mysql -u app -pS3cret$2024 shop # the shell replaces $2 -> the password is wrong
mysql -u app -p'S3cret$2024' shop # single quotes: sent exactly as written
mysql -u app -p shop # best: type it at the prompt

The same applies to passwords in .env files, YAML and connection URLs — in a URL, characters like @, : and / must be URL-encoded (@ → %40).

Also check that no option file sends a different user or password without you noticing:

Terminal window
mysql --print-defaults

This prints the options the client reads from ~/.my.cnf, /etc/mysql/… and similar files. Options there apply to every mysql command you run.

3. The right user, but the wrong host

The account is user@host, and MySQL picks the most specific matching host. Check what exists:

SELECT user, host, plugin FROM mysql.user WHERE user = 'app';
+------+-----------+-----------------------+
| user | host | plugin |
+------+-----------+-----------------------+
| app | 10.0.0.% | caching_sha2_password |
+------+-----------+-----------------------+

Here app may only connect from 10.0.0.x. From the server itself you are localhost, and no 'app'@'localhost' account exists → 1045. Typical cases:

  • The account was created for localhost, and the app connects from another machine or container (or the other way around).
  • There are two accounts, for example 'app'@'localhost' and 'app'@'%', with different passwords. From the server itself, localhost is more specific, so it wins.
  • localhost and 127.0.0.1 are not the same thing: -h localhost connects through the Unix socket, -h 127.0.0.1 through TCP. Depending on the server’s settings, they can match different accounts.

Once you are logged in, MySQL tells you which account it matched:

SELECT USER(), CURRENT_USER();

USER() is who you said you are; CURRENT_USER() is the account MySQL chose.

Create the account for the host you really connect from, and grant it what it needs. Use '%' only if the network around the server is protected (a firewall, a private network):

CREATE USER 'app'@'10.0.1.%' IDENTIFIED BY 'new-password-here';
GRANT SELECT, INSERT, UPDATE, DELETE ON shop.* TO 'app'@'10.0.1.%';

The SQL Dialect Reference shows the same operation in MySQL, MariaDB, PostgreSQL, SQL Server and Oracle.

4. Docker: the password in the environment changed later

A classic with the official mysql and mariadb images:

services:
db:
image: mysql:8.4
environment:
MYSQL_ROOT_PASSWORD: new-password # changed from old-password
MYSQL_USER: app
MYSQL_PASSWORD: new-app-password # changed too
volumes:
- db-data:/var/lib/mysql

These variables are used only the first time, when the data directory is empty. After that, the users and passwords live in the volume, and changing the variables does nothing. The database still has the old passwords — and your app now uses the new ones.

Two ways out:

  • Log in with the old password and change it with ALTER USER (section 6).
  • If the data doesn’t matter (a fresh dev setup), remove the volume so the image initializes again: docker compose down -v. This deletes all the data in it.

5. root on Ubuntu/Debian: use sudo, not a password

On Ubuntu and Debian, MySQL’s root usually logs in with the auth_socket plugin (unix_socket on MariaDB): no password, but you must be the Linux root user. So:

Terminal window
sudo mysql

works, while mysql -u root -p as a normal user fails with ERROR 1698 (28000): Access denied for user ‘root’@‘localhost’ — a different code than 1045, which is your hint. Don’t change this to a password for the app’s sake: create a separate user for the application (section 3) and keep root for administration.

6. Reset a password

If you can log in as an admin (for example with sudo mysql), set a new password for the exact account from section 3:

ALTER USER 'app'@'%' IDENTIFIED BY 'new-password-here';

No restart and no FLUSH PRIVILEGES are needed after ALTER USER.

Warning

Lost the root password too? The official procedure starts MySQL with --skip-grant-tables, which disables all permission checks — anyone who can connect gets full access. Add --skip-networking so only local connections are possible while you do it, and restart normally right after. Follow the steps in the MySQL manual (“How to Reset the Root Password”) for your version.

Shell, we have a problem!

Similar, but different: ERROR 1698 (28000) — the account uses socket authentication (section 5). ERROR 2059 or “Authentication plugin ‘caching_sha2_password’ cannot be loaded” — your client or driver is too old for MySQL 8’s default authentication: update it. ERROR 1524 “Plugin ‘mysql_native_password’ is not loaded” — MySQL 8.4 disables that old plugin by default; move the account to caching_sha2_password (ALTER USER … IDENTIFIED WITH caching_sha2_password BY '…'). ERROR 1044 Access denied for user … to database — the login worked, but the account has no rights on that database: that’s a GRANT problem.

Found a mistake? Let us know at contact@ohsudo.com.

Tools

Try it yourself

Articles

Related articles