VillageSQL is a drop-in replacement for MySQL with extensions.
All examples in this guide work on VillageSQL. Install Now →
Hardening by Deployment Context
Not every hardening step carries the same risk/friction tradeoff. Use this table to prioritize:| Step | Internet-facing production | Internal/private network | Dev / staging |
|---|---|---|---|
Run mysql_secure_installation | Required | Required | Recommended |
| Remove anonymous users | Required | Required | Recommended |
| Restrict root to localhost | Required | Required | Recommended |
| Remove test database | Required | Required | Optional |
validate_password component | Required | Required | Optional — can block quick iteration |
| Password expiration policy | Required | Recommended | Skip |
| Least-privilege app accounts | Required | Required | Recommended |
Restrict host in grants (no '%') | Required | Required | Optional |
REQUIRE SSL on accounts | Required | Recommended | Skip |
| Custom CA-signed TLS certificates | Required | Optional — self-signed acceptable | Skip |
bind-address to internal IP | Required | Recommended | Optional |
Disable local_infile | Required | Recommended | Optional |
skip_name_resolve | Recommended | Recommended | Optional |
| Lock or drop unused accounts | Required | Required | Optional |
'%' host grants in place is acceptable only if the instance is unreachable from outside your laptop or CI environment. If staging shares a network with production data, treat it as internal/private at minimum.
mysql_secure_installation
Run this immediately after installing MySQL on any server that isn’t a local dev machine:- Set the root password (if not already set)
- Remove anonymous users
- Disallow remote root login
- Remove the test database
- Reload privilege tables
Root Account Restrictions
Root should only connect fromlocalhost. Verify and enforce this:
'%' host entry, remove it:
Remove Anonymous Users and Test Database
Password Policy
MySQL 8.0+ includes thevalidate_password component, which enforces password complexity rules. It must be installed before use — mysql_secure_installation offers to install it automatically. To install manually:
LOW (length only), MEDIUM (length + character classes), STRONG (+ dictionary check).
Set password expiration for accounts that might be forgotten:
Least-Privilege User Accounts
Application accounts should have the minimum privileges needed. See MySQL User Management for the full GRANT syntax. Common patterns:'%'.
Auditing Existing Privileges
Review all accounts and their privileges:SSL/TLS Connections
MySQL 8.0 enables SSL/TLS by default and auto-generates certificates on startup. Verify it’s active:Network Access
Restrict MySQL to listen only on necessary interfaces. Inmy.cnf:
127.0.0.1 and never expose port 3306 externally. Firewall rules are a second layer of defense, not a substitute for restricting the bind address.
Disabling Risky Features
Turn off features your application doesn’t use:my.cnf:
Frequently Asked Questions
Is the default MySQL 8 install secure?
More secure than MySQL 5.7 defaults, but still not hardened for production. MySQL 8.0 generates random root passwords and enables SSL by default, which are improvements. You still need to: verify the root password is strong, remove anonymous users, restrict host access, and configure thevalidate_password component.
Should I run MySQL as root?
Never. MySQL should run as a dedicated low-privilege system user (typicallymysql). Running the server process as root means a MySQL exploit could lead to full system compromise.
Troubleshooting
| Problem | Solution |
|---|---|
ERROR 1045: Access denied for user 'root'@'%' | Root remote login is correctly blocked — create a named admin user with specific host access |
Application can’t connect after adding REQUIRE SSL | Client needs to use SSL flags: --ssl-mode=REQUIRED; verify server certificates are valid |
validate_password rejecting a password | Check current policy: SHOW VARIABLES LIKE 'validate_password%'; ensure the password meets all requirements |
| Can’t find root password on a new install | MySQL 8.0 logs the temporary root password to the error log on first start — find the log path with SELECT @@GLOBAL.log_error;, then grep 'temporary password' <path> |
Host '%' grants still visible after DELETE | Run FLUSH PRIVILEGES after any direct modifications to mysql.user |

