max_connect_errors能拦截恶意扫描是因它统计单ip短时连续错误(如密码错、协议不匹配)并超阈值后自动阻断,但易误杀因nat共ip、应用重试、dns不稳定等导致合法错误叠加;mysql 8.0默认1000、5.7默认100,生产建议从300起步,需配合host_cache启用、connect_timeout计算阻断时长,并与max_user_connections协同防护。

直接改 max_connect_errors 参数就能拦截恶意扫描,但设太低会误伤正常用户,设太高等于没用——关键在平衡点和配套验证。
为什么 max_connect_errors 能拦扫描但容易误杀
MySQL 用这个参数统计单个 IP 在短时间内的连续连接错误次数(比如密码错、协议不匹配、访问拒绝),超阈值后自动把该 IP 标记为 blocked,后续连接直接拒绝并报错 Host 'xxx' is blocked because of many connection errors。它本质是靠 performance_schema.host_cache 表维护状态,不是防火墙,也不依赖外部工具。
常见误杀场景:
- 内网 NAT 环境下多个客户端共用一个出口 IP,错误叠加快
- 应用启动时批量重试连接(尤其 Spring Boot 默认重试 3 次)
- DNS 解析不稳定导致部分连接走错 host 或超时
所以不能盲目调低,默认值在 MySQL 8.0 是 1000,5.7 是 100,生产环境建议从 300 起步,再根据 aborted_connects 增长速率调整。
怎么安全地修改 max_connect_errors
临时生效(验证用):
SET GLOBAL max_connect_errors = 300;
永久生效(写进配置):
[mysqld] max_connect_errors = 300
注意两点:
- 修改后不用重启 MySQL,但需确保
skip_networking=OFF(默认就是 OFF,检查用SHOW VARIABLES LIKE 'skip_networking';) - 必须配合启用
host_cache,MySQL 5.7+ 默认开启,禁用会导致该参数完全失效
验证是否生效:
SHOW VARIABLES LIKE 'max_connect_errors';
怎么确认它真在起作用
光改参数没用,得看实际拦截行为:
- 查被拦的 IP:
SELECT HOST, COUNT_STAR, COUNT_ERROR FROM performance_schema.host_cache WHERE STATE = 'COUNTED';(COUNT_ERROR> 阈值即已触发) - 看日志有没有
host 'xxx' is blocked记录(默认记录在 error log,路径查SHOW VARIABLES LIKE 'log_error';) - 手动测试:用错误密码连同一 IP 超过设定次数,第 N+1 次应直接失败,错误码是
1129
特别注意:blocked 状态持续时间 = connect_timeout * 60 秒(默认 connect_timeout=10,所以是 600 秒),不是永久封禁,这点和防火墙策略不同。
单靠 max_connect_errors 不够,必须配用户级限连
它只防“错连”,不防“连对了但疯狂建连”的恶意行为。比如攻击者拿到弱口令后,用合法账号开 500 个连接打满 max_connections,max_connect_errors 完全不触发。
必须同步做:
- 给每个应用账号设
MAX_USER_CONNECTIONS,例如:ALTER USER 'app_api'@'%' WITH MAX_USER_CONNECTIONS 20; - 查配置是否落地:
SELECT User, Host, Max_user_connections FROM mysql.user WHERE User = 'app_api'; - 监控实时连接:
SELECT user, COUNT(*) FROM performance_schema.threads WHERE TYPE = 'FOREGROUND' GROUP BY user;
真正稳定的防护,是 max_connect_errors 拦异常试探 + MAX_USER_CONNECTIONS 控合法滥用,两者缺一不可。漏掉后者,等于大门装了门禁却忘了锁窗户。











