单靠mysql自身无法真正封禁未授权网段,必须用iptables -i input -s 192.168.5.0/24 -p tcp --dport 3306 -j drop在链首阻断tcp握手,且需配合user@'192.168.5.%'、bind-address与skip_name_resolve协同生效。

单靠 MySQL 自身无法真正封禁未授权网段的连接请求——必须在 TCP 握手前就阻断,这只能靠系统防火墙(iptables/firewalld)完成。
iptables 封禁 IP 段必须用 -I 插入链首并限定 --dport 3306
MySQL 在收到 SYN 包时就会响应(哪怕后续鉴权失败),如果防火墙规则排在 ACCEPT 后面,请求早已抵达 mysqld。常见错误是写 iptables -A INPUT -s 192.168.5.0/24 -j DROP,结果被前面的 ACCEPT 规则放行。
- 正确命令:
iptables -I INPUT -s 192.168.5.0/24 -p tcp --dport 3306 -j DROP(-I确保插入最前,--dport 3306防止误杀 SSH/HTTP) - Linux 内核默认不支持 CIDR 写法中的
/24,需确认已加载xt_iprange模块;若不可靠,改用范围匹配:iptables -I INPUT -m iprange --src-range 192.168.5.1-192.168.5.254 -p tcp --dport 3306 -j DROP - 封完立刻验证:
telnet 192.168.5.10 3306应超时或报Connection refused—— 若返回Connection refused,说明 mysqld 已响应,防火墙没生效;理想状态是连接超时或直接被拒绝 - 持久化保存(Debian/Ubuntu):
iptables-save > /etc/iptables/rules.v4;CentOS 7+ 请用firewalld替代
mysql.user 的 Host 字段不支持 CIDR,只认 % 和 _
执行 CREATE USER 'u'@'192.168.5.0/24' 或 UPDATE mysql.user SET Host = '192.168.5.0/24' 完全无效。MySQL 把它当纯字符串,而客户端发来的是 192.168.5.42,两者不匹配。
- 允许 C 段的唯一合法写法是:
'u'@'192.168.5.%'(注意是点加%,不是/24) - 这个写法有边界风险:会匹配
192.168.5.999(MySQL 静默截断为192.168.5.99),也会匹配192.168.5.abc.example.com - 不能对已有
'u'@'%'执行REVOKE ... FROM 'u'@'192.168.5.%'—— 权限表里根本不存在这个 host 组合,语句静默失败 - 安全做法:先
DROP USER 'u'@'%',再CREATE USER 'u'@'192.168.5.%'并GRANT,MySQL 8.0+ 不允许直接UPDATE mysql.user
bind-address 和 skip_name_resolve 必须协同生效
bind-address 不是“IP 限制开关”,它只控制监听地址。设为 0.0.0.0 却没配防火墙,Host 再严也没用;设为 127.0.0.1 则彻底屏蔽所有远程连接。
- 确认实际监听地址:
ss -tlnp | grep :3306,若输出含*:3306或0.0.0.0:3306,说明监听了所有接口,必须依赖防火墙 - 启用
skip_name_resolve = ON强制 MySQL 只用 IP 匹配Host字段,避免 DNS 反查把192.168.5.42解成域名后导致匹配失败 - 三者必须对齐:防火墙 DROP 规则 +
bind-address监听范围 +user@host白名单,缺一不可
最容易被忽略的是验证环节:很多人加了 iptables 规则就以为万事大吉,但不测 telnet 就不知道规则是否真生效;还有人以为删掉 'root'@'%' 就安全了,却忘了 mysqld 仍在监听公网端口,攻击者仍能探测服务存在。封禁不是配置动作,而是“让目标 IP 连都连不上”的结果——这个结果必须亲手验证。











