A default MySQL installation has several security weaknesses: a root account that may be accessible remotely, anonymous user accounts, a test database anyone can access, and no audit logging of who accessed what data. Securing MySQL is essential before any production deployment and covers four main areas: access control (who can connect from where), authentication (strong passwords, disabling passwordless accounts), audit logging (who ran what queries), and reducing the attack surface (disabling features not in use). MySQL 8 includes the mysql_secure_installation wizard for basic hardening, the validate_password plugin for password policy enforcement, and the community audit log plugin. This guide covers all major MySQL hardening steps for RHEL 9.
Prerequisites
- MySQL 8 installed and running on RHEL 9
Step 1 — Run mysql_secure_installation
mysql_secure_installation
# Select: strong password policy, remove anonymous users,
# disallow remote root login, remove test database, reload privileges
Step 2 — Enable Password Validation Plugin
mysql -u root -p <<'SQL'
-- Check current password policy
SHOW VARIABLES LIKE 'validate_password%';
-- Set strong policy
SET GLOBAL validate_password.policy = STRONG; -- Requires upper, lower, digit, special char
SET GLOBAL validate_password.length = 12;
SET GLOBAL validate_password.mixed_case_count = 1;
SET GLOBAL validate_password.number_count = 1;
SET GLOBAL validate_password.special_char_count = 1;
SQL
# Make settings permanent in /etc/my.cnf.d/security.cnf
[mysqld]
validate_password.policy = STRONG
validate_password.length = 12
validate_password.mixed_case_count = 1
validate_password.number_count = 1
validate_password.special_char_count = 1
Step 3 — Remove All Anonymous and Unused Accounts
mysql -u root -p <<'SQL'
-- Identify all user accounts
SELECT user, host, authentication_string != '' AS has_password FROM mysql.user;
-- Remove anonymous accounts
DELETE FROM mysql.user WHERE user='';
-- Remove remote root (if not already done)
DELETE FROM mysql.user WHERE user='root' AND host != 'localhost';
-- Drop test database
DROP DATABASE IF EXISTS test;
FLUSH PRIVILEGES;
SQL
Step 4 — Enforce Least-Privilege User Accounts
# Never use root for application connections
# Grant only what the app needs
mysql -u root -p <<'SQL'
-- Web app user: only DML on its own database
CREATE USER 'webapp'@'127.0.0.1' IDENTIFIED BY 'AppPassword123!';
GRANT SELECT, INSERT, UPDATE, DELETE ON myapp.* TO 'webapp'@'127.0.0.1';
-- Read-only reporting user
CREATE USER 'reporter'@'10.0.1.20' IDENTIFIED BY 'ReportPass456!';
GRANT SELECT ON myapp.* TO 'reporter'@'10.0.1.20';
FLUSH PRIVILEGES;
SQL
Step 5 — Enable Audit Logging
# MySQL Community audit log (logs all queries for compliance)
# /etc/my.cnf.d/audit.cnf
[mysqld]
plugin-load-add = audit_log.so
audit_log_policy = ALL # Log all connections and queries
audit_log_format = JSON
audit_log_file = /var/log/mysql/audit.log
audit_log_rotate_on_size = 100M
systemctl restart mysqld
tail -f /var/log/mysql/audit.log | python3 -m json.tool | head -30
Step 6 — Disable LOCAL INFILE and Bind to Localhost
# /etc/my.cnf.d/security.cnf
[mysqld]
local_infile = 0 # Prevent LOAD DATA LOCAL INFILE attacks
bind-address = 127.0.0.1 # Only listen locally unless remote access needed
skip_name_resolve = 1 # Don't perform DNS lookups (faster + more secure)
systemctl restart mysqld
Conclusion
Hardening MySQL on RHEL 9 involves enforcing strong password policies, removing anonymous and unused accounts, granting only necessary privileges to application users, enabling audit logging for compliance, disabling dangerous features like LOCAL INFILE, and binding MySQL to 127.0.0.1 when remote access is not needed. These measures address OWASP A07 (Identification and Authentication Failures) and A04 (Insecure Design) for database security.
Next steps: How to Configure PostgreSQL Remote Access and SSL on RHEL 9, How to Configure MySQL Primary-Replica Replication on RHEL 9, and How to Monitor MySQL with Prometheus on RHEL 9.