MySQL 封禁 IP 段需网络层(iptables/firewalld)与数据库层(mysql.user 的 Host 字段)协同:iptables 必须用 -I 插入链首并限定 --dport 3306;Host 不支持 CIDR,只能用 % 和 _,如 'user'@'192.168.1.%';bind-address、防火墙、user@host 必须三者对齐,且启用 skip_name_resolve 避免 DNS 反查。

只靠 MySQL 自身无法真正封禁 IP 段,必须网络层(iptables/firewalld)和数据库层(mysql.user 表的 Host 字段)协同生效,缺一不可。
iptables 封禁 IP 段必须用 -I 插入链首并指定 --dport 3306
MySQL 在 TCP 握手阶段就响应 SYN 包,如果防火墙规则位置靠后或没限定端口,攻击者仍能探测到服务存在。
-
iptables -A INPUT -s 203.0.113.0/24 -p tcp --dport 3306 -j DROP是错的:-A 追加在末尾,大概率被前面的 ACCEPT 规则提前放行 - 正确写法是:
iptables -I INPUT -s 203.0.113.0/24 -p tcp --dport 3306 -j DROP(-I确保插入最前) - 务必加
--dport 3306:不加会误杀 SSH、HTTP 等其他服务 - 封完立刻验证:
telnet 203.0.113.5 3306应超时或直接拒绝;若返回Connection refused,说明 MySQL 已响应,防火墙没生效 - 持久化保存:
iptables-save > /etc/iptables/rules.v4(Debian/Ubuntu)或service iptables save(CentOS 6)
mysql.user 的 Host 字段不支持 CIDR,只能用 % 和 _
MySQL 解析 Host 时只认 SQL 模式通配符,不识别斜杠、子网掩码或正则。写 'user'@'192.168.1.0/24' 完全无效。
- 想放行 C 段:用
'user'@'192.168.1.%'(注意是点加 %,不是 /24) - 这个写法有边界风险:它会匹配
192.168.1.999(MySQL 静默截断为192.168.1.99),也无法阻止192.168.11.5 - 不能靠
REVOKE封禁已有账号:比如已有'user'@'%',再执行REVOKE ALL ON *.* FROM 'user'@'192.168.1.%'不起作用——权限记录里根本不存在这个 host 组合 - 真正有效的是删掉通配账号:
DROP USER 'user'@'%';,再重建精确账号:CREATE USER 'user'@'192.168.1.%' IDENTIFIED BY 'pwd'; - MySQL 8.0+ 不允许直接
UPDATE mysql.user,必须用DROP/CREATE流程
bind-address + 防火墙 + user@host 必须三者对齐
只改 my.cnf 里的 bind-address = 192.168.1.10 并不安全。如果服务器有多张网卡,而防火墙没关公网 3306 端口,攻击者仍可能从另一接口连上——只要 MySQL 监听了那个地址。
-
bind-address控制监听范围:设为127.0.0.1则只响应本地连接;设为内网 IP 则只监听该接口 - 确认实际监听地址:
ss -tlnp | grep :3306,若输出含*:3306或0.0.0.0:3306,才需要防火墙干预 - 防火墙规则顺序至关重要:放行可信 IP 的规则必须在
DROP规则之前,否则全被拦死 - 启用
skip_name_resolve = ON强制 MySQL 只用客户端 IP 地址匹配Host,禁用 DNS 反查,避免延迟与不确定性
最容易被忽略的是「测试路径」:telnet 超时 ≠ MySQL 层生效,Access denied for user 错误说明请求已抵达 mysqld,此时问题一定出在 mysql.user 的 Host 匹配逻辑或权限缺失,而不是防火墙。


















