MySQL 1130报错排查:Host not allowed远程连接被拒的完整解决方案
1. 先搞懂这条报错卡在了连接流程的第几步1.1 握手过程中MySQL为什么把你拒之门外多数人第一次在Navicat、DBeaver或者命令行里看到Host is not allowed to connect to this MySQL server时第一反应是检查网络、检查防火墙、检查端口通不通。但我要先把结论放在前面看到这条报错TCP连接已经通了MySQL服务器已经收到你的握手请求甚至已经识别出你的来源IP了。它是在授权表里找不到匹配记录才把你拒掉的。MySQL远程连接的完整流程大致是这样客户端发起TCP连接服务器accept之后开始MySQL协议握手紧接着做两件事——先用Host和User去mysql.user表里找匹配记录找不到就直接返回错误找到记录之后才校验密码。你看到的这条报错错误码是1130对应的就是第一阶段被拒即主机白名单里没有你。所以当你看到这个报错时请先放下防火墙排查手册。TCP三次握手已经完成服务器已经在应用层和你对话了。问题出在MySQL自身的访问控制上——mysql.user表里没有一条记录能在Host列上匹配你当前连接所用的IP或主机名。1.2 最容易踩中这个报错的几个真实场景我最近几年处理这种报错几乎都集中在下面几类场景上新装MySQL后直接远程连root。这是最典型的。无论是Linux用rpm装还是Windows的msi安装包默认情况下MySQL只会为rootlocalhost创建账号目的就是防远程暴力破解。你拿着root去远程连只要没有手动授权过必然报1130。Docker容器里的MySQL创建用户时Host写死了容器IP。很多人用docker run启动MySQL之后再进容器里创建一个用户Host写成了类似172.17.0.2这种容器地址。可你在宿主机上连接时来源IP并不是容器的IP而是经Docker NAT转换后的地址于是同样报Host not allowed。从生产库导出数据到本地开发库账号体系没同步。生产库里授权了appuser10.20.30.%本地环境却是从另一台机器连过来的本地库的授权表里自然没有这个来源报错也跟着来了。用了内部域名做Host授权但客户端实际通过IP连接。比如你授权了userdb-client.internal可客户端配置连接的是192.168.1.50MySQL在解析时没有匹配到对应关系也会拒你。这些场景背后有一个共通的判断方法先去查mysql.user表看看目标用户有哪些Host范围再对比你实际连接来源基本就能锁定问题。1.3 与Access denied这类报错的分界线新手很容易把几个报错混在一起我在这上面接过不少半个小时的排查最后发现是自己密码错了的求助。这里做一个简单的区分报错特征错误码含义Host is not allowed to connect to this MySQL server1130授权表里没有匹配的Host记录密码环节还没走到Access denied for user xxxxxx (using password: YES)1045Host匹配上了但密码不对或者密码插件不兼容Cant connect to MySQL server on xxx (timed out)2003TCP层都没通通常是防火墙、bind-address或端口映射问题Unknown MySQL server host2005DNS解析失败直接把域名解析绕开更省事分清这四条线之后你面对这条报错时心里就有底了这不是网络故障也不是密码问题就是授权表的问题。接下来要做的就是沿着授权这条线一步步排查把到底是谁在什么主机上、被拒绝了这件事看清楚。2. 排查链路从网络层一路确认到授权表2.1 第一步确认MySQL真的在端口上等你虽然我前面说看到这条报错说明TCP已经通了但严谨起见还是先做一次连通性确认。毕竟有些时候你看到的报错是应用层包装过的中文提示可能把别的错误混进来。在客户端机器上执行telnet mysql-server-ip 3306或者用更直观的方式nc -vz mysql-server-ip 3306如果端口通你会看到类似Connected to mysql-server-ip的反馈。如果超时那说明连接根本没到MySQL进程这时候才需要回头看防火墙、安全组、Docker端口映射和bind-address。MySQL监听地址也是个常见坑。用下面这条命令看服务器监听的到底是0.0.0.0还是只有本机回环地址SHOW VARIABLES LIKE bind_address;如果是127.0.0.1那无论授权表怎么写远程都连不进来。需要修改配置文件为0.0.0.0并重启MySQL。不过你要注意如果已经看到了1130报错说明bind-address这块大概率没问题因为服务器已经响应你了。2.2 第二步直接查mysql.user表里到底有没有你的位置这是整个排查链路里最关键的一步比什么工具都管用。先在MySQL服务器本机登录mysql -uroot -p然后执行SELECT user, host, plugin FROM mysql.user;你会看到类似下面的输出---------------------------------------------------- | user | host | plugin | ---------------------------------------------------- | root | localhost | caching_sha2_password | | mysql.session | localhost | caching_sha2_password | | myapp | 10.0.0.% | caching_sha2_password | ----------------------------------------------------这里的关键就是看你想用的那个账号在Host列上有没有能覆盖你源IP的记录。假设你从192.168.1.100连过来账号是root表里只有rootlocalhost那就毫无悬念地会收到1130。主机名匹配规则不只是精确匹配MySQL还支持通配符比如%表示任意主机192.168.1.%表示匹配网段web01.example.com表示匹配特定主机名。还有一个常用的确认手段在服务器本机执行SELECT CURRENT_USER();和SELECT USER();分别查看当前连接实际生效的账号以及你当前来源IP对应的账号视角。这能帮你判断我明明授权了%为什么还是连不上这类诡异问题。2.3 第三步别忽略bind-address和DNS反向解析如果user表里已经有合适的授权但还是报1130那问题多半出在host匹配的参数上。下面两个变量需要重点看SHOW VARIABLES LIKE skip_name_resolve; SHOW VARIABLES LIKE bind_address;skip-name-resolve这个变量很有意思。默认情况下它是OFF意味着MySQL会对每个连接做DNS反向解析把客户端IP反向解析成主机名再拿主机名去和授权表的Host列比对。如果你的授权表里写的是userlocalhost或者usersome-hostname但客户端通过IP连接且反向解析失败或解析出的主机名对不上那就会拒绝连接。解决思路有两个方向一是把授权表里的Host改成IP或IP段二是在配置里加上skip-name-resolveON让MySQL跳过反解直接拿IP地址做匹配。后者在互联网环境里能显著降低每次连接的握手延迟但代价是授权表里就只能用IP写Host了写主机名会失效。这一层的坑比较隐蔽因为表面上你确实授权了甚至授权的是%但配合DNS反解实际匹配逻辑可能完全不是你想象的那样。我建议生产环境直接开启skip-name-resolve把授权全部收敛到IP维度少一个变量就少一类事故。3. 三种修复方法按场景选而不是瞎试3.1 用GRANT授权最推荐但要按版本区分写法绝大多数情况下解决1130的正确做法是给指定用户补一条Host授权。注意MySQL 5.7和8.0在语法上有明显差异。MySQL 5.7及更早版本可以用简洁的单条语句创建用户的同时授权GRANT ALL PRIVILEGES ON *.* TO root% IDENTIFIED BY YourPassword WITH GRANT OPTION; FLUSH PRIVILEGES;MySQL 8.0之后GRANT语句不再支持隐式建用户。你需要分两步CREATE USER root% IDENTIFIED BY YourPassword; GRANT ALL PRIVILEGES ON *.* TO root% WITH GRANT OPTION; FLUSH PRIVILEGES;如果你只是想给特定库授权而不是放开全部权限可以把*.*替换成mydb.*这样更安全。实际工作中我强烈建议不要直接放开root远程而是创建一个专用账号只授予需要访问的库CREATE USER devuser192.168.1.% IDENTIFIED BY StrongPassword; GRANT SELECT, INSERT, UPDATE, DELETE ON myapp.* TO devuser192.168.1.%; FLUSH PRIVILEGES;FLUSH PRIVILEGES这条很多人会纠结有没有必要。我的理解是执行GRANT/CREATE USER时MySQL会直接更新授权表并同步刷新内存里的授权缓存理论上不执行FLUSH也能生效但如果你是通过UPDATE mysql.user这种方式手工改表那就必须执行FLUSH PRIVILEGES否则新连接不会重新加载授权数据。稳妥起见改完授权我都会习惯性执行一次代价几乎为零。3.2 手动改mysql.user表老办法的边界在哪里有些老资料会教你直接改表UPDATE mysql.user SET host % WHERE user root; FLUSH PRIVILEGES;这个做法在5.x时代很常见但在8.0里我不建议作为首选。原因有两个一是mysql.user表的字段在不同版本有差异直接改表绕过了MySQL自身的权限变更日志容易漏掉一些关联表的同步二是升级到8.0后直接改mysql.user表如果有语法或字段理解错误可能导致整个认证体系出问题。不过它有一个适用场景当你需要批量调整某个用户的所有Host记录时UPDATE确实比逐条GRANT高效。比如你要把某个账号从只允许某个IP改成允许整个网段可以写UPDATE mysql.user SET host 192.168.1.% WHERE user myapp; FLUSH PRIVILEGES;改完之后务必检查一下结果确认没有把别的账号误伤SELECT user, host FROM mysql.user WHERE user myapp;另外提醒一句如果表里同一个用户同时存在myapplocalhost和myapp%两条记录这是很正常的。MySQL在匹配连接时会按精确度排序localhost或具体IP会优先于%匹配并不会冲突。你不用特意删掉某一条。3.3 全部放开root的%授权能救急但有代价时间紧任务重很多人的第一反应是把root直接授权成%。我理解这种救急操作但我要负责任地说清楚这等于把你的数据库大门钥匙挂在了门口。root%意味着任何来源IP只要能猜对密码就能以最高权限登录。一旦数据库放在了有公网IP的机器上扫描工具分分钟就能探测到3306端口然后就是持续的暴力破解尝试。我见过太多因为这种救急操作导致的数据安全事故很多还是在客户环境里。如果确实需要远程管理正确姿势是CREATE USER admin办公网固定IP IDENTIFIED BY 复杂密码; GRANT ALL PRIVILEGES ON *.* TO admin办公网固定IP WITH GRANT OPTION; FLUSH PRIVILEGES;把来源IP限定到你的办公网出口IP或至少限定到某个可信网段比如10.0.0.0/8。这样即使密码泄露攻击者也必须来自可信网络风险等级完全不一样。如果是云服务器环境还有一个必须同步检查的点云平台的安全组规则是否允许了0.0.0.0/0访问3306端口。安全组和MySQL授权表是两道独立的门两道门都应该只对可信IP开放哪个都不能图省事。4. 修复之后更容易翻车的几个地方我替你们先踩了4.1 Docker端口映射后来源IP却不是你以为的那个IP使用Docker部署MySQL的场景1130报错的成因往往比裸机部署更绕。很多人进入容器创建用户时看到自己用的是172.17.0.3就顺手把Host写成了这个IP。结果在宿主机上用localhost或宿主机IP去连接还是被拒。原因在于Docker的网络模型。当你用-p 3306:3306映射端口时宿主机上的客户端连接经过Docker的iptables转发到达容器内的MySQL时来源IP已经变成了Docker网桥的网关地址通常是172.17.0.1而不是客户端真正的IP。所以你在容器里授权user172.17.0.3永远等不到这个来源。解决方式有两类在授权时把来源写成Docker给宿主机分配的网关地址也就是user172.17.0.1。更推荐的做法是在宿主机上使用--network host方式启动MySQL容器或者通过环境变量、初始化SQL从一开始就把账号授权好。初始化SQL可以放在容器的/docker-entrypoint-initdb.d/目录下MySQL首次启动时会自动执行这样账号体系从出生就是对的。Docker场景有个核心原则授权表里的Host永远以MySQL进程实际看到的来源IP为准而不是你的直觉。拿不准的时候可以在授权表里先建一条user%接一次连接执行SELECT USER();看返回结果再收敛成具体IP。4.2 skip-name-resolve会对授权表产生连锁影响我在前面提过skip-name-resolve这里单独展开一下因为它是一个非常容易改一处、炸一片的参数。当skip_name_resolveOFF默认时MySQL会对每个TCP连接做反向DNS解析。如果反向解析超时连接建立耗时可能高达好几秒这种情况下你会观察到能连但非常慢的怪现象。而当skip_name_resolveON时MySQL不再反解主机名这时授权表里凡是Host列写主机名的记录全部失效只有IP和%有效。我遇到过一起现场排障开发反馈我明明授权了但连不上排查半天发现是运维在优化配置时开了skip_name_resolve而授权表里清一色写的是内网机器名。这种问题从报错上完全看不出区别唯一办法就是对比配置变更记录和授权表内容。如果你决定要开skip_name_resolve我的建议是规范化授权表把所有的Host列统一改成IP地址或CIDR网段彻底放弃主机名匹配。这一步做完之后连接速度和排障清晰度都会有明显提升。4.3 MySQL 8认证插件让一些老客户端莫名失败这条严格来说不会报1130但它经常出现在同一个排障流程里让人误以为还是授权问题。MySQL 8默认的认证插件是caching_sha2_password而很多老版本的图形客户端比如某些旧版Navicat、老版JDBC驱动只支持mysql_native_password。你授权也对了、密码也对了客户端却报Authentication plugin caching_sha2_password cannot be loaded这类错误。解决办法通常是把该用户的认证插件切回旧版ALTER USER myapp192.168.1.% IDENTIFIED WITH mysql_native_password BY YourPassword; FLUSH PRIVILEGES;不过这只是兼容性过渡方案。新项目我建议优先升级客户端驱动到支持caching_sha2_password的版本因为新版插件在安全性上明显更强。实在要兼容老旧客户端也可以用上面的命令单独处理某个账号别把整库的默认认证方式改掉。还有一种情况值得提一下MySQL 8.0.34版本开始弃用了mysql_native_password插件未来版本可能直接移除。如果你维护的是比较新的8.0版本尽量别再用这个兼容插件兜底能升级客户端就升级客户端。4.4 授权表的你以为是和实际生效之间的差距有时候你以为自己已经授权了实际上MySQL选中的是另一条记录。授权表匹配有个优先级规则越精确的Host反而越会被优先匹配到。比如你同时有user192.168.1.%和user%两条记录来自192.168.1.50的连接会命中前者而不是后者。如果前者的密码和后者的密码设置不一致你拿后者的密码去连就会莫名其妙被拒绝。遇到这种情况用SELECT CURRENT_USER();看实际生效的账号名能直接看出到底落到了哪条记录上。另外要养成习惯一个账号尽量只维护一条Host记录要么具体IP要么网段要么%避免多条记录互相干扰。5. 授权策略的长期维护建议少让后人替你背锅5.1 设计授权时把最小够用当成默认准则排查过太多1130之后我现在的习惯是第一次创建账号时就想清楚这个账号将来会在哪里被使用。给应用服务器用的账号Host就写那台应用服务器的IP给开发同事用的账号Host就写办公网出口IP给自动化运维脚本用的再单独开一个只读账号Host限定到跳板机。举例说明一套典型的电商系统数据库授权看起来像这样| user | host | privileges | |---------------|-----------------|--------------------------------| | app_rw | 192.168.1.10 | SELECT, INSERT, UPDATE, DELETE | | app_readonly | 192.168.1.% | SELECT | | admin_op | 10.0.0.5 | ALL PRIVILEGES |每个账号的用途和来源都清晰可查后人接手的时候不需要猜。你在授权表里省下的每一分钟思考都会在将来某次故障排查时十倍偿还。5.2 定期review授权表别让账号堆积成隐患线上环境跑得久了mysql.user表里很容易堆出一堆过期账号。比如某个外包同事走了他的账号还在某个项目下线了它的数据库账号还在。这些账号本身就是风险敞口。我的习惯是每季度做一次授权表检查主要看三件事有没有Host列为%且权限是ALL PRIVILEGES的高危账号有没有超过半年没用过但还活着的账号有没有已经不属于当前业务架构的库和账号检查方法也简单直接查用户表再对照业务资料逐个确认。确认无用的账号执行DROP USER xxxxxx;别只删一半。5.3 把授权变更纳入规范化操作流程最早我改授权都是业务方喊一声我随手一条GRANT就发过去了事后经常忘记录。后来吃过亏现在所有授权变更都走统一的SQL脚本留档备查。每个新增账号的脚本大概长这样-- 变更说明电商应用新服务器加入需要访问订单库 CREATE USER order_app10.0.8.25 IDENTIFIED BY StrongPassword; GRANT SELECT, INSERT, UPDATE, DELETE ON order_db.* TO order_app10.0.8.25; FLUSH PRIVILEGES;脚本文件按日期和用途命名放在专门的目录里。这样下次有人说我这边连不上数据库我能先翻脚本确认账号是否存在、来源IP对不对比直接去线上瞎猜快得多。另外再分享一个小技巧写授权语句时把Host字段里的IP段用引号括起来虽然不括也不会报错但标准化之后整个SQL脚本看起来整齐很多出问题也更容易一眼看出来。关于1130这个报错我这些年最深的体会是它对新手像一堵墙对老手只是一个信号。信号背后的核心永远只有一条——MySQL的授权表里没有你的位置。搞清楚你在以什么身份从哪来、该被授予什么权限然后按最小够用的原则补上授权这个报错就会彻底变成你日常工具箱里最顺手的一个工具。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →