Why a Fresh MySQL Install Is Not Secure by Default
A brand-new MySQL installation on Ubuntu is built for convenience, not security. It ships with anonymous users that can connect without credentials, a test database anyone can write to, and a root account with no password and wide-open remote access. Every one of those defaults is a door an attacker can walk through. If your database holds anything that matters, closing those doors is the first thing you do after apt install finishes.
MySQL ships a small interactive script for exactly this job: mysql_secure_installation. It walks you through setting a root password, removing anonymous users, blocking remote root logins, dropping the test database, and reloading privileges. The whole thing takes about five minutes. This guide walks through every prompt the script shows, explains what each choice actually does, and shows you how to verify the hardening worked. It is written for Ubuntu 24.04 and 22.04 running MySQL 8.x, the versions you will meet on most VPS and cloud servers today.
Prerequisites
- A server running Ubuntu 24.04 or 22.04 with
sudoaccess. - MySQL Server 8.x already installed and running. If you have not installed it yet:
The second command makes sure MySQL starts automatically after a reboot. Withoutsudo apt update && sudo apt install -y mysql-server sudo systemctl enable --now mysqlenable, a server restart leaves your database offline. - About ten minutes and a strong password you do not reuse anywhere else.
Step 1: Run the Security Script with sudo
On Ubuntu, the MySQL root account authenticates with the auth_socket plugin by default. That means root can only connect from the local Unix socket as the operating-system root user, not with a password. Because of this, you must run the script with sudo. Without it you will get an "Access denied" error before the first prompt.
sudo mysql_secure_installation
This launches the interactive wizard. Every prompt below appears in order. Read each one before answering, because a couple of them have consequences you cannot easily undo without touching the privilege tables by hand.
Step 2: Decide on the VALIDATE PASSWORD Component
The first question is:
Would you like to setup VALIDATE PASSWORD component?
Press y|Y for Yes, any other key for No: y
The VALIDATE PASSWORD component rejects weak passwords for every new account and every password change. It has three policy levels:
- LOW (0): only checks password length (minimum 8 characters).
- MEDIUM (1): checks length plus numeric, mixed-case, and special characters.
- STRONG (2): checks everything MEDIUM does, plus compares the password against a dictionary file.
For a production or internet-facing server, answer y and choose MEDIUM. STRONG is fine too, but it can be annoying on development machines where you rotate test passwords often. If this is a local dev box, you can safely answer n and skip it. You can install or remove the component later at any time, so this choice is not permanent.
If you choose MEDIUM or STRONG, pick your root password before running the script. The script will reject "password123" style entries and make you start over.
Step 3: Set the Root Password
Next the script asks for the current root password and whether to change it:
Please enter 0 = LOW, 1 = MEDIUM and 2 = STRONG: 1
Please set the password for root here.
New password:
Re-enter new password:
Estimated strength of the password: 100
Do you wish to continue with the password provided?(Press y|Y for Yes, any other key for No) : y
On a fresh Ubuntu install, root has no password yet, so press Enter when asked for the current one. Type a long, random password when prompted. If the strength estimate comes back low, say n at the confirmation and try again rather than keeping a weak one.
One Ubuntu-specific catch: if your root account uses auth_socket, this step sets a password that root will never actually use for login, because socket authentication ignores passwords entirely. That is harmless, but if your application connects to MySQL over TCP with the root credentials (a common setup with Docker containers and remote apps), you need root to use password authentication instead. After the script finishes, run:
sudo mysql -e "ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'Your-Strong-Password-Here';"
sudo mysql -e "FLUSH PRIVILEGES;"
This switches root from auth_socket to password authentication. Only do this if you have a reason; for most server setups, keeping auth_socket and creating a separate application user (shown in Step 8) is the safer pattern.
Step 4: Remove Anonymous Users
Remove anonymous users? (Press y|Y for Yes, any other key for No) : y
A default MySQL install creates anonymous user accounts (empty username) that anyone can use to connect without a password. They have almost no privileges, but they still let an attacker probe your server, run SHOW DATABASES, and attempt privilege escalation tricks. There is no legitimate reason to keep them. Always answer y.
Step 5: Disallow Root Login Remotely
Disallow root login remotely? (Press y|Y for Yes, any other key for No) : y
This restricts the root account to connections from localhost only. Root over the network is one of the most attacked combinations on the internet; bots scan for open port 3306 and hammer it with root/password guesses around the clock. Answer y here without hesitation. If you genuinely need remote database access, create a limited user for a specific host (see Step 8) instead of exposing root.
Step 6: Remove the Test Database
Remove test database and access to it? (Press y|Y for Yes, any other key for No) : y
MySQL ships a database literally called test that any user can create tables in and write to. It exists for the manual's examples and has no place on a real server. Answer y. If you have an application database that happens to be named test, rename it first, because this step drops it without asking twice.
Step 7: Reload the Privilege Tables
Reload privilege tables now? (Press y|Y for Yes, any other key for No) : y
... Success!
All done!
MySQL caches grants in memory. Reloading the privilege tables (equivalent to running FLUSH PRIVILEGES;) makes every change you just made take effect immediately. Always answer y. If you answer n here, run sudo mysql -e "FLUSH PRIVILEGES;" yourself before you consider the job done.
Step 8 (Recommended): Create a Dedicated Application User
The script hardens the defaults, but the job is not finished. Your applications should never connect as root. Create one user per application, limited to the databases it needs:
sudo mysql
CREATE DATABASE myapp_db CHARACTER SET utf8mb4;
CREATE USER 'myapp'@'localhost' IDENTIFIED BY 'Another-Strong-Password';
GRANT ALL PRIVILEGES ON myapp_db.* TO 'myapp'@'localhost';
FLUSH PRIVILEGES;
EXIT;
Each command does one thing: create the database, create the user with a password, grant that user full control over only its own database, and apply the changes. If one application gets compromised, the attacker cannot touch your other databases. This separation is the single most effective habit in MySQL security.
While you are at it, confirm MySQL only listens on localhost unless you have a reason for remote connections:
sudo ss -tlnp | grep 3306
You should see 127.0.0.1:3306. If you see 0.0.0.0:3306, MySQL is reachable from the whole network. Lock it down in /etc/mysql/mysql.conf.d/mysqld.cnf by setting bind-address = 127.0.0.1, then restart with sudo systemctl restart mysql.
Verify It Worked
Do not trust the script's "Success!" message alone. Check each change yourself:
- Anonymous users are gone:
This should return an empty set. Any rows mean anonymous accounts still exist.sudo mysql -u root -p -e "SELECT user, host FROM mysql.user WHERE user='';" - Root cannot connect remotely:
Every root row should showsudo mysql -u root -p -e "SELECT user, host FROM mysql.user WHERE user='root';"localhost(or127.0.0.1) in the host column. A%there means remote root is still allowed. - The test database is gone:
sudo mysql -u root -p -e "SHOW DATABASES;"testshould not be in the list. - The root password works: log in once with
mysql -u root -pand type the password you set. Getting in confirms the credential is active; getting locked out here is far better than discovering it during an emergency.
Common Issues and Fixes
1. ERROR 1698 (28000): Access denied for user 'root'@'localhost'
This is the most common error on Ubuntu and it happens when you run mysql_secure_installation without sudo. The root account uses auth_socket, so only the OS root user can connect. Fix: run sudo mysql_secure_installation. If you see this error inside scripts, prefix every mysql call with sudo or switch root to password authentication as shown in Step 3.
2. ERROR 1819 (HY000): Your password does not satisfy the current policy requirements
VALIDATE PASSWORD rejected your chosen password. Either pick a stronger one (longer, with numbers, mixed case, and symbols for MEDIUM) or lower the policy temporarily with SET GLOBAL validate_password.policy = LOW;. Do not lower it permanently on a production server; the policy exists to stop exactly the password you just tried.
3. The script keeps rejecting every password I type
You are probably fighting the STRONG policy, which checks passwords against a dictionary file and rejects anything resembling a real word. Switch to MEDIUM when the script asks for the policy level, or generate a random 20-character password with openssl rand -base64 20 and paste it in.
4. My application can no longer connect as root after hardening
That is the hardening working as intended: remote root login is now blocked and root may use auth_socket. Do not undo the hardening. Instead, create a dedicated user for the application as shown in Step 8, and point your app's config at those credentials. If the app runs on another server, create the user with the app server's IP in the host part, for example 'myapp'@'203.0.113.10', and open port 3306 in your firewall only for that IP.
5. "Can't connect to MySQL server" after changing bind-address
If you set bind-address = 127.0.0.1 but your application connects over the network, you have locked yourself out remotely. Either connect locally (SSH into the server and use localhost) or change bind-address to the server's private IP and restart MySQL. Always restart with sudo systemctl restart mysql and confirm with sudo ss -tlnp | grep 3306.
Conclusion
mysql_secure_installation is five minutes of work that eliminates the most exploited MySQL defaults: empty root passwords, anonymous users, remote root access, and the world-writable test database. Run it on every new install, verify the results with the queries above, and give each application its own limited user. Those three habits cover the large majority of real-world MySQL compromises.
If you would rather have an expert lock down your database servers, XpertBees sets up and hardens MySQL, PostgreSQL, and full LAMP/LEMP stacks on Ubuntu servers every day. Reach out through the contact page and we will take the server-hardening checklist off your plate.



