Fix: Access denied for user 'root'@'localhost' (MySQL ERROR 1045)
Part of: Docker, DevOps & Infrastructure
Quick Answer
How to fix MySQL 'ERROR 1045 (28000): Access denied for user root@localhost (using password: YES/NO)' on Linux, macOS, Windows, and Docker. Covers password reset, auth plugin issues, skip-grant-tables recovery, MySQL 8 vs 5.7 differences, and host mismatches.
The Error
You try to connect to MySQL and get:
ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: YES)Or one of these variations:
ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: NO)Access denied for user 'myuser'@'localhost' (using password: YES)Access denied for user 'root'@'127.0.0.1' (using password: YES)SQLSTATE[HY000] [1045] Access denied for user 'root'@'localhost' (using password: YES)All of these mean the same thing: MySQL rejected your login credentials. The server is running and reachable, but it won’t let you in.
Why This Happens
MySQL authenticates users based on three things: username, host, and authentication method (password or plugin). A mismatch in any of these causes ERROR 1045.
Common causes:
- Wrong password. The password you’re providing doesn’t match what MySQL has stored for that user.
- The root account uses
auth_socketorunix_socketinstead of a password. On Ubuntu/Debian, MySQL and MariaDB often configure root to authenticate via the OS user, not a password. Runningmysql -u root -pfails because root expects socket auth, not a password. caching_sha2_passwordplugin incompatibility. MySQL 8.0+ defaults tocaching_sha2_password, which older clients and libraries don’t support.- Host mismatch. MySQL treats
'root'@'localhost','root'@'127.0.0.1', and'root'@'%'as different accounts. You may have set the password for one but are connecting as another. - The user doesn’t exist. The user account was never created, or was dropped.
- Docker MySQL root password not set or not matching. The
MYSQL_ROOT_PASSWORDenvironment variable doesn’t match what you’re using to connect. - Application connection string has wrong credentials. The password in your
.envfile, config, or connection string is stale or incorrect.
Version history that changes the failure mode
Authentication errors look identical across versions but the underlying cause shifts dramatically:
- MySQL 5.7 reached end of life on 31 October 2023. No more security patches and no more
mysql_native_passworddefaults from the official MySQL packages going forward. Many production sites running 5.7 in 2024–2026 still seeAccess deniedbecause the upgrade to 8.x changed the default auth plugin under them. - MySQL 8.0 (April 2018) made
caching_sha2_passwordthe default. This is the single biggest source of “I just upgraded and now nothing connects.” Old PHP, oldmysql-connector-python, JDBC drivers before MySQL Connector/J 8.0, and any Node.js code using themysqlpackage (instead ofmysql2) cannot speak the new protocol and fail with ERROR 1045 or “Authentication plugin cannot be loaded.” - MySQL 8.4 LTS (April 2024) removed
default_authentication_plugin. It was deprecated in 8.0.27 and is now replaced byauthentication_policy. Config snippets copied from 8.0-era tutorials silently break — MySQL ignores the unknown option and usescaching_sha2_passwordno matter what you wrote. - MySQL 8.4 dropped the
mysql_native_passwordplugin from the default load list. The plugin still ships, but you must enable it explicitly with--mysql-native-password=ONormysql_native_password=ONinmy.cnfbefore users with that plugin can connect. Existing accounts usingmysql_native_passwordfail withPlugin 'mysql_native_password' is not loadeduntil you re-enable it. - MariaDB diverged. MariaDB 10.4+ uses the
unix_socketplugin for root by default and never adoptedcaching_sha2_password. Code that targets “MySQL or MariaDB” interchangeably should not assume identical auth — MariaDB syntax (ALTER USER ... IDENTIFIED VIA mysql_native_password USING PASSWORD(...)) differs from MySQL 8 syntax. - Docker
mysql:8.4andmysql:9.ximages. The official MySQL images for 8.4+ inherit the 8.4 auth changes. Pinning your Compose file tomysql:8.4after running onmysql:8.0for years can break every existing client at once.
Fix 1: Use the Correct Password
Start with the obvious. Make sure you’re using the right password.
If you set a root password during installation:
mysql -u root -pMySQL will prompt for the password. Type it carefully — the terminal won’t show any characters.
If you’re passing the password on the command line (not recommended for production):
mysql -u root -p'yourpassword'Note: no space between -p and the password.
“using password: NO” means you didn’t provide a password at all. Either add -p to prompt for one, or check your connection string.
“using password: YES” means you provided a password, but it’s wrong.
Fix 2: Fix the Auth Plugin (auth_socket / unix_socket)
On Ubuntu, Debian, and many Linux distributions, MySQL and MariaDB configure the root account to use auth_socket (MySQL) or unix_socket (MariaDB) authentication by default. This means root can only log in as the OS root user — no password required, but only via sudo.
This works:
sudo mysqlThis fails with ERROR 1045:
mysql -u root -pCheck the current auth plugin
sudo mysql -e "SELECT user, host, plugin FROM mysql.user WHERE user='root';"You’ll see something like:
+------+-----------+-----------------------+
| user | host | plugin |
+------+-----------+-----------------------+
| root | localhost | auth_socket |
+------+-----------+-----------------------+Switch root to password authentication
sudo mysqlFor MySQL 8.0+:
ALTER USER 'root'@'localhost' IDENTIFIED WITH caching_sha2_password BY 'your_new_password';
FLUSH PRIVILEGES;For MySQL 5.7 or if your clients don’t support caching_sha2_password:
ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'your_new_password';
FLUSH PRIVILEGES;For MariaDB:
ALTER USER 'root'@'localhost' IDENTIFIED VIA mysql_native_password USING PASSWORD('your_new_password');
FLUSH PRIVILEGES;Now mysql -u root -p works with your new password.
Why this matters: On Ubuntu and Debian, the default
auth_socketplugin for root is a deliberate security choice, not a misconfiguration. It ensures only the OS root user (viasudo) can access the MySQL root account, eliminating password-based brute force attacks entirely. Before switching to password auth, consider whether creating a separate admin user is the better approach for your use case.
Alternative approach: Instead of switching root to password auth, create a separate admin user and keep root on socket auth. This is actually more secure:
CREATE USER 'admin'@'localhost' IDENTIFIED BY 'strong_password';
GRANT ALL PRIVILEGES ON *.* TO 'admin'@'localhost' WITH GRANT OPTION;
FLUSH PRIVILEGES;Fix 3: Reset the Root Password (skip-grant-tables)
If you’ve lost the root password entirely, reset it using safe mode.
Step 1: Stop MySQL
Linux (systemd):
sudo systemctl stop mysql
# or for some distributions:
sudo systemctl stop mysqldmacOS (Homebrew):
brew services stop mysqlWindows:
net stop MySQL80Step 2: Start MySQL with —skip-grant-tables
This starts MySQL without any authentication checks.
Linux:
sudo mysqld_safe --skip-grant-tables --skip-networking &The --skip-networking flag prevents remote connections while the server is running without authentication. This is critical for security.
macOS:
sudo mysqld_safe --skip-grant-tables --skip-networking &Windows: Open an elevated Command Prompt:
mysqld --skip-grant-tables --skip-networkingStep 3: Connect and reset the password
mysql -u rootFor MySQL 8.0+:
FLUSH PRIVILEGES;
ALTER USER 'root'@'localhost' IDENTIFIED BY 'your_new_password';For MySQL 5.7:
FLUSH PRIVILEGES;
ALTER USER 'root'@'localhost' IDENTIFIED BY 'your_new_password';For older MySQL versions (5.6 and below):
FLUSH PRIVILEGES;
SET PASSWORD FOR 'root'@'localhost' = PASSWORD('your_new_password');FLUSH PRIVILEGES must come first when running with --skip-grant-tables, or the ALTER USER command fails.
Step 4: Restart MySQL normally
Kill the safe mode process and restart:
sudo killall mysqld
sudo systemctl start mysqlTest the new password:
mysql -u root -pFix 4: Fix caching_sha2_password Issues (MySQL 8.0+)
MySQL 8.0 changed the default authentication plugin from mysql_native_password to caching_sha2_password. Many older clients, libraries, and ORMs don’t support this plugin and fail with ERROR 1045 or a related authentication error.
Symptoms
Your client shows one of these:
Authentication plugin 'caching_sha2_password' cannot be loadedERROR 1045 (28000): Access denied for user 'root'@'localhost'Option A: Switch the user to mysql_native_password
ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'your_password';
FLUSH PRIVILEGES;Option B: Change the server default for all new users
Edit my.cnf (or my.ini on Windows):
[mysqld]
default_authentication_plugin=mysql_native_passwordOn MySQL 8.4+, this setting was replaced:
[mysqld]
authentication_policy=mysql_native_passwordRestart MySQL after editing.
Option C: Update your client
The better long-term solution is to update your MySQL client library to one that supports caching_sha2_password:
- PHP: Use
mysqlnd(PHP 7.4+) or upgrade to PHP 8.x. - Python: Use
mysql-connector-python8.0+ orPyMySQL1.0+. - Node.js: Use
mysql2instead ofmysql. - Java: Use MySQL Connector/J 8.0+.
Fix 5: Fix Host Mismatches
MySQL treats these as completely different user accounts:
'root'@'localhost'— connects via Unix socket or TCP tolocalhost'root'@'127.0.0.1'— connects via TCP to127.0.0.1'root'@'%'— connects from any host
You may have set a password for 'root'@'%' but you’re connecting to localhost, which uses the 'root'@'localhost' account (with a different — or no — password).
Check which user accounts exist
SELECT user, host, plugin FROM mysql.user;Create the missing account or fix the password
If 'root'@'127.0.0.1' is missing:
CREATE USER 'root'@'127.0.0.1' IDENTIFIED BY 'your_password';
GRANT ALL PRIVILEGES ON *.* TO 'root'@'127.0.0.1' WITH GRANT OPTION;
FLUSH PRIVILEGES;If the password is wrong on one of them:
ALTER USER 'root'@'127.0.0.1' IDENTIFIED BY 'your_password';
FLUSH PRIVILEGES;How to tell which host you’re connecting from
mysql -u root -p(no-hflag) uses the Unix socket on Linux/macOS, which matches'root'@'localhost'.mysql -u root -p -h 127.0.0.1uses TCP, which matches'root'@'127.0.0.1'.mysql -u root -p -h localhostuses the Unix socket on Linux, TCP on Windows.
To force TCP even with localhost:
mysql -u root -p -h 127.0.0.1 --protocol=TCPFix 6: Fix Docker MySQL Root Password
Docker MySQL containers set the root password via environment variables at first startup only. Changing the environment variable after the data volume already exists has no effect.
Check your docker-compose.yml or docker run command
services:
db:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: your_password
ports:
- "3306:3306"Or:
docker run -d --name mysql -e MYSQL_ROOT_PASSWORD=your_password -p 3306:3306 mysql:8.0Common Docker mistakes
Connecting from the host to the container:
mysql -u root -p -h 127.0.0.1 -P 3306Use 127.0.0.1, not localhost. On Linux, localhost tries the Unix socket, which doesn’t connect to the container.
Connecting from another container:
Use the service name as the hostname, not localhost (localhost resolves to the container itself, not the database container):
mysql://root:your_password@db:3306/mydbPassword was changed but old volume persists:
If you change MYSQL_ROOT_PASSWORD and the container still rejects your login, the password was set during the first initialization and is stored in the data volume.
Fix it by removing the volume and reinitializing:
docker compose down -v
docker compose up -dWarning: This deletes all data in the MySQL volume. Export your data first if needed.
Or reset the password inside the running container:
docker exec -it mysql mysql -u root -p -e "ALTER USER 'root'@'%' IDENTIFIED BY 'new_password'; FLUSH PRIVILEGES;"MYSQL_ALLOW_EMPTY_PASSWORD vs MYSQL_ROOT_PASSWORD:
If you set MYSQL_ALLOW_EMPTY_PASSWORD=yes, root has no password. Connect with:
mysql -u root -h 127.0.0.1Not with -p.
Fix 7: Fix Application Connection Strings
Your code connects fine from the command line but your app gets ERROR 1045. The problem is usually in the connection string or environment variable.
Check your connection string format
mysql://user:password@host:port/databaseCommon issues:
- Special characters in the password need to be URL-encoded.
p@ss#wordbecomesp%40ss%23word. - The port is wrong. Default is
3306, but Docker may map it to a different port. - The host is wrong. Inside Docker, use the container/service name. From the host, use
127.0.0.1.
Check your environment variables
Make sure your app is actually reading the right config. A common mistake is having the password in .env but the app reading from a different config file, or the environment variable not being loaded.
# Print the DATABASE_URL (mask the password in production)
echo $DATABASE_URLPHP (PDO) specific
// Wrong -- password has special characters that break parsing
$pdo = new PDO('mysql:host=localhost;dbname=mydb', 'root', 'p@ss');
// Right -- use the options array
$pdo = new PDO('mysql:host=127.0.0.1;dbname=mydb;charset=utf8mb4', 'root', 'p@ss');Note: PHP’s PDO with localhost uses the Unix socket. Use 127.0.0.1 to force TCP, especially in Docker setups.
Still Not Working?
Real-world scenario: A developer sets
MYSQL_ROOT_PASSWORD=secretindocker-compose.yml, runsdocker compose up, and connects successfully. A week later, they change the password tonewsecretand restart the container. The connection fails with ERROR 1045. The MySQL Docker image only readsMYSQL_ROOT_PASSWORDon first initialization — the old password is baked into the data volume. Runningdocker compose down -vanddocker compose upreinitializes with the new password (but destroys all data).
The anonymous user is shadowing your account
MySQL may have an anonymous user (''@'localhost') that takes priority over your named account. This is common on older MySQL installations.
Check for anonymous users:
SELECT user, host FROM mysql.user WHERE user = '';If any rows return, delete them:
DROP USER ''@'localhost';
DROP USER ''@'%';
FLUSH PRIVILEGES;Run mysql_secure_installation to clean up other default security issues.
MySQL is reading a different config file
MySQL reads options files from multiple locations. A stale password in ~/.my.cnf or /etc/mysql/conf.d/ can override what you’re typing.
Check which config files MySQL reads:
mysql --help | grep -A 1 "Default options"Look for a [client] section with a hardcoded password:
[client]
user=root
password=old_wrong_passwordRemove or update it.
The user exists for a different host only
You have 'myuser'@'%' but the connection resolves to localhost, and MySQL prefers more specific host matches. A 'myuser'@'%' account does not match connections from localhost if there’s any other entry for localhost (even the anonymous user).
Fix it by creating the localhost-specific account:
CREATE USER 'myuser'@'localhost' IDENTIFIED BY 'your_password';
GRANT ALL PRIVILEGES ON mydb.* TO 'myuser'@'localhost';
FLUSH PRIVILEGES;MySQL data directory has wrong permissions
After a manual installation, upgrade, or restore, the MySQL data directory may be owned by the wrong user:
ls -la /var/lib/mysqlIt should be owned by mysql:mysql. Fix it:
sudo chown -R mysql:mysql /var/lib/mysql
sudo systemctl restart mysqlSELinux is blocking MySQL (RHEL/CentOS/Fedora)
SELinux may prevent MySQL from reading its data files or socket:
sudo ausearch -m avc -ts recent | grep mysqlIf you see denials:
sudo setsebool -P mysql_connect_any 1
sudo restorecon -rv /var/lib/mysqlTemporary password expired (fresh MySQL 8.0 install)
On fresh MySQL 8.0 installations (especially on RHEL-based systems), MySQL generates a temporary root password. You must change it before doing anything else.
Find the temporary password:
sudo grep 'temporary password' /var/log/mysqld.logLog in with it:
mysql -u root -pMySQL forces you to change it immediately:
ALTER USER 'root'@'localhost' IDENTIFIED BY 'YourNewStrongPassword!1';The new password must meet MySQL’s validate_password policy (uppercase, lowercase, digit, special character, at least 8 characters). To lower the policy for development:
SET GLOBAL validate_password.policy = LOW;
SET GLOBAL validate_password.length = 6;MySQL 8.4 silently disabled mysql_native_password
If you upgraded from MySQL 8.0 to 8.4 (or pulled the mysql:8.4 Docker image), every account using mysql_native_password stops working with Plugin 'mysql_native_password' is not loaded. The plugin is no longer loaded by default in 8.4.
Re-enable it in my.cnf:
[mysqld]
mysql_native_password=ONOr migrate the accounts to caching_sha2_password (preferred long-term):
ALTER USER 'app'@'%' IDENTIFIED WITH caching_sha2_password BY 'your_password';
FLUSH PRIVILEGES;If your client library can’t speak caching_sha2_password, upgrade it first — drivers from 2019 onward all support it.
default_authentication_plugin ignored on MySQL 8.4+
If you copied a config snippet from an MySQL 8.0 tutorial:
[mysqld]
default_authentication_plugin=mysql_native_passwordMySQL 8.4 silently ignores this option (it was removed) and uses caching_sha2_password regardless. Replace it with:
[mysqld]
authentication_policy=mysql_native_passwordCheck the server startup log — MySQL writes a deprecation warning when it sees the old option.
MySQL Workbench saved the wrong password
MySQL Workbench, DBeaver, TablePlus, and similar GUIs cache passwords in the OS keychain. After you change a password on the server, the GUI keeps offering the cached value until you delete the saved entry. Re-enter the password explicitly rather than reusing the saved connection.
Proxy / load balancer in front of MySQL strips the source host
If you’re connecting through ProxySQL, ProxySQL Cluster, or MySQL Router, MySQL sees the proxy’s IP — not your client’s. Your 'user'@'10.0.1.5' grant doesn’t match a connection coming from 10.0.0.20 (the proxy). Grant access from the proxy’s IP, or use '%' if the proxy is the only entry point.
Verify what MySQL actually sees:
SELECT USER(), CURRENT_USER(), @@hostname;USER() shows what you asked to log in as. CURRENT_USER() shows which grant matched. If they differ, MySQL matched a different host pattern than you expected.
Related: If you’re troubleshooting database connectivity beyond authentication, see Fix: PostgreSQL Connection Refused. For Docker networking and file issues, see Fix: Docker COPY Failed: File Not Found and Fix: Docker Permission Denied. If your app can’t read the database credentials from environment variables, see Fix: Environment Variable Is Undefined.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: MySQL ERROR 2002 (HY000): Can't connect to local MySQL server through socket
How to fix MySQL ERROR 2002 (HY000) when the MySQL client can't connect through the Unix socket file on Linux, macOS, Docker, and WSL.
Fix: MySQL Replication Not Working — Replica Lag, Stopped Replication, or GTID Errors
How to fix MySQL replication issues — SHOW REPLICA STATUS errors, relay log corruption, GTID configuration, replication lag, skipping errors, and replica promotion.
Fix: MySQL Full-Text Search Not Working — MATCH AGAINST Returns No Results
How to fix MySQL full-text search issues — FULLTEXT index creation, minimum word length, stopwords, boolean mode vs natural language mode, InnoDB vs MyISAM, and LIKE fallback.
Fix: MySQL Index Not Being Used — Query Optimizer Skipping Indexes
How to fix MySQL indexes not being used by the query optimizer — EXPLAIN output, implicit conversions, function on columns, composite index order, cardinality issues, and forcing indexes.