Opening MySQL's port 3306 to the internet is like hanging a "Hack Me" sign on your server. Within minutes of enabling networking, you'll see hundreds of failed login attempts in your logs. Here's a typical attack pattern you might observe:
# Sample MySQL error log showing brute-force attempts
2023-11-15T04:23:17.123456Z 104280 [Note] Access denied for user 'root'@'203.0.113.45' (using password: YES)
2023-11-15T04:23:17.234567Z 104281 [Note] Access denied for user 'admin'@'203.0.113.45' (using password: YES)
2023-11-15T04:23:17.345678Z 104282 [Note] Access denied for user 'mysql'@'203.0.113.45' (using password: YES)
Let's implement multiple layers of protection:
1. Firewall-Level Protection with IPTables/Netfilter
For FreeBSD systems, use IPFW or PF firewall. Here's an IPFW example that limits connection attempts:
# Basic IPFW rules for MySQL protection
ipfw add 1000 allow tcp from trusted_networks to me 3306
ipfw add 1010 deny tcp from any to me 3306 in via eth0
ipfw add 1020 check-state
ipfw add 1030 deny tcp from any to me 3306 in established
ipfw add 1040 deny tcp from any to me 3306 in setup limit src-addr 5/60
2. MySQL Native Security Features
MySQL 5.x includes several built-in security mechanisms:
-- Create limited access users instead of using root
CREATE USER 'app_user'@'localhost' IDENTIFIED BY 'complex_password_123!';
GRANT SELECT, INSERT, UPDATE, DELETE ON app_db.* TO 'app_user'@'localhost';
-- Enable connection rate limiting (MySQL 5.6+)
SET GLOBAL max_connect_errors = 10;
SET GLOBAL max_user_connections = 20;
Fail2Ban Integration for MySQL
While denyhosts works for SSH, Fail2Ban is more versatile. Create this jail.local configuration:
[mysql-auth]
enabled = true
filter = mysql-auth
port = 3306
logpath = /var/log/mysql/error.log
maxretry = 3
findtime = 600
bantime = 86400
And the corresponding filter (/etc/fail2ban/filter.d/mysql-auth.conf):
[Definition]
failregex = ^\d{6} \d+ $$Note$$ Access denied for user '.*'@'' $using password: YES$$
ignoreregex =
SSL/TLS Encryption Enforcement
Force all remote connections to use SSL:
-- MySQL configuration
[mysqld]
ssl-ca=/etc/mysql/ca-cert.pem
ssl-cert=/etc/mysql/server-cert.pem
ssl-key=/etc/mysql/server-key.pem
require_secure_transport=ON
-- User-specific SSL requirements
ALTER USER 'remote_user'@'%' REQUIRE SSL;
Regularly check your protection measures:
# Check current connections
mysqladmin processlist
# Review authentication attempts
grep "Access denied" /var/log/mysql/error.log | awk '{print $NF}' | sort | uniq -c | sort -nr
# Verify firewall rules
ipfw show
Remember that security is an ongoing process. Regularly audit your MySQL users, permissions, and firewall rules to maintain protection against evolving brute-force techniques.
Opening MySQL's port 3306 to the internet is like leaving your front door unlocked in a bad neighborhood. Within minutes of enabling network access, you'll see thousands of authentication attempts in your logs:
# Sample MySQL error log entries
2023-05-15T03:47:21.123456Z 45 [Note] Access denied for user 'root'@'203.0.113.42' (using password: YES)
2023-05-15T03:47:21.234567Z 46 [Note] Access denied for user 'admin'@'203.0.113.42' (using password: YES)
Implement strict firewall rules before anything else. On FreeBSD, use IPFW or PF:
# IPFW example (FreeBSD)
ipfw add 15000 deny tcp from any to me 3306 in via em0
ipfw add 15010 allow tcp from 192.168.1.0/24 to me 3306 in via em0
While denyhosts works for SSH, fail2ban is more versatile. Create a MySQL jail in /usr/local/etc/fail2ban/jail.d/mysql.conf
:
[mysql]
enabled = true
filter = mysql
port = 3306
logpath = /var/log/mysql/error.log
maxretry = 3
bantime = 86400
MySQL itself offers several security features often overlooked:
-- Set connection limits and delay
SET GLOBAL max_connect_errors = 3;
SET GLOBAL connect_timeout = 5;
SET GLOBAL interactive_timeout = 30;
-- Create restricted admin account
CREATE USER 'secure_admin'@'192.168.1.%' IDENTIFIED BY 'complex-password-here';
GRANT ALL PRIVILEGES ON *.* TO 'secure_admin'@'192.168.1.%' REQUIRE SSL;
While not a permanent solution, changing ports reduces script-kiddie attacks:
# In my.cnf
[mysqld]
port = 33901
Combine this with iptables redirect for backward compatibility:
iptables -t nat -A PREROUTING -p tcp --dport 3306 -j REDIRECT --to-port 33901
For critical systems, implement X.509 certificate authentication:
CREATE USER 'secure_user'@'%'
REQUIRE SUBJECT '/C=US/ST=California/L=San Francisco/O=MyCompany/CN=db-client';