:防火墙与bind-address排查)
本地 Windows 上敲下mysql -h 192.168.172.130 -u root -p回车之后光标转了十几秒最后甩出来一行ERROR 2002 (HY000): Cant connect to server on 192.168.172.130 (115)。绝大多数人看到这个报错的第一反应是MySQL 服务挂了于是登上服务器systemctl restart mysqld重启完再连还是同一行字。我第一次遇到的时候也在服务状态上耗了半小时后来才明白这条报错里真正值钱的信息压根不是2002而是括号里那个孤零零的115。这篇就把这个115从头到尾拆开讲一遍包含网络层、服务端监听、账号授权、防火墙四条链路的完整排查顺序以及每一步为什么这么查、查出来的结果分别意味着什么。适合正在搭测试环境、虚拟机里跑数据库、或者第一次把 MySQL 暴露给局域网的同学零基础也能跟着一步步走完。1. 别急着重启服务先把报错文本读明白1.1 ERROR 2002 说明连接连认证阶段都没进去MySQL 客户端抛出的错误码是分层的位置不同含义天差地别。1045 (28000): Access denied for user是服务端已经接上了 TCP、走完了握手、只是在账号密码这一关被拒1049 Unknown database是连上了、认证过了、库名写错了。而2002属于最外层客户端在建立连接这一步就失败了压根没摸到服务端的门把手。这里有个细节常让人困惑有人看到的是2002加 IP有人看到的是2003 Cant connect to MySQL server on ...。不同版本的客户端库对这两个码的归属略有差异2002传统上对应 socket 连接失败2003对应 TCP 连接失败但实际发版过程中两者混用的现象一直存在。所以别在编号上纠结看到2002或者2003加一个 IP 地址直接按连接层失败来处理就对了。这一点非常关键因为它直接决定了你的排查方向。如果是1045你的战场在mysql.user表如果是2002/2003你的战场在网络和监听上去翻权限表纯属浪费时间。1.2 括号里的 115 才是真正的定位线索MySQL 客户端在报错时会把操作系统的errno原封不动带出来这个数字比 MySQL 自己的错误码精确得多。Linux 上常见的几个值含义如下errno名称含义典型成因111ECONNREFUSED连接被明确拒绝端口没人监听对端回了 RST或防火墙用 REJECT 策略113EHOSTUNREACH主机不可达路由表缺失、网段配错110ETIMEDOUT连接超时中途设备丢包超时耗尽115EINPROGRESS操作正在进行中客户端在超时窗口内没等到任何响应就放弃了115这个名字叫 Operation now in progress看着像是还在连但在 MySQL 客户端这个场景下它实际表达的是还没连完我就不等了。原因是客户端使用了非阻塞的connect()配合自己的连接超时机制当超时时间到了而 SYN 包始终没有得到任何回应时内核返回的就是EINPROGRESS。换句话说115等于数据包石沉大海。这一点是整篇文章的分水岭如果是端口没人监听内核会立刻回一个 RST你看到的是111你看到115说明连 RST 都没等到包在某处被静默丢掉了。找到是谁丢的包问题就解决了九成。1.3 192.168.172.130 这个地址本身就透露了环境192.168.172.130这个地址段很有辨识度。VMware Workstation 在创建 NAT 网络时会自动分配一个192.168.x.0/24网段其中第三段172通常是 VMware 自己挑的网段号宿主机上对应的虚拟网卡一般是192.168.172.1而那台虚拟机往往就是.130或者.128。这不是玄学是装机模板的惯性很多人用同一个虚拟机镜像反复搭环境网络是 DHCP 自动分配的拿到.130的概率不低。所以看到这个 IP你应该先确认三件事——虚拟机到底开没开、虚拟机的网络模式是 NAT 还是桥接、宿主机的虚拟网卡有没有被防火墙拦住。这三件事在纯物理机的环境里根本不存在但在虚拟机环境里是最高频的元凶。我见过不止一次问题出在虚拟机关机了或者挂起了客户端自然等不到任何响应直接给你一个115。想快速确认网段在虚拟机里跑一条ip -4 addr show在宿主机上Windows跑ipconfig对照一下vmnet8那张网卡的地址如果宿主机是192.168.172.1、虚拟机是192.168.172.130两者同段说明虚拟网络本身配置是对的问题就落在后面几节要讲的地方了。2. 判断路通不通网络可达性的正确探查顺序2.1 ping 通了千万别急着下结论很多人习惯先ping 192.168.172.130通了就觉得网络没问题然后一头扎进 MySQL 的配置里。这个判断方法只对了一半。ping走的是 ICMP 协议MySQL 走的是 TCP 3306这两条路经过的过滤规则可能完全不同。云主机的安全组里ICMP 全放通、3306 不放通是极其常见的组合。所以 ping 通只能证明 IP 层可达不能证明 3306 端口可达。但反过来ping不通的价值极大。如果 ICMP 都回不来说明网络层就有问题虚拟机没开机、网段配错、虚拟网卡被禁用等等后面的服务端排查可以全部跳过。我一般的顺序是先 ping 一次通了就往下走不通就先修网络。这是一个极低成本、极高信息量的第一刀。2.2 用端口级探测拿到真实结论真正能说明问题的是端口探测。Linux 和 macOS 上用ncWindows 上用Test-NetConnection# Linux / macOS nc -zv -w 3 192.168.172.130 3306# Windows PowerShell Test-NetConnection -ComputerName 192.168.172.130 -Port 3306结果怎么读这里给你一张对照表比翻文档快探测结果含义下一步succeeded / open端口真的通了问题在账号授权去看mysql.userConnection refused有 RST 返回端口没监听查服务是否启动、是否只监听回环Connection timed out无任何响应包被丢查防火墙、安全组、虚拟网络模式No route to host路由不可达查网段、路由表、网卡状态对照本篇的报错如果你的探测结果是timed out那就和115的结论完全一致——包被静默丢弃。这时候排查重心是谁在丢包而不是MySQL 为什么不接受连接。这个区分能让你的排查效率提升一大截因为丢包和拒绝的修复手法完全不一样。注意nc加-w 3是为了把等待时间压到 3 秒。默认情况下端口探测可能要等十几秒甚至更久反复试几轮会把你的耐心耗光。养成加超时参数的习惯。2.3 从服务端本机反向验证一刀切开两类问题在192.168.172.130这台机器上跑两条命令# 看 3306 到底在监听哪个地址 ss -lntp | grep 3306 # 用 TCP 方式连本地注意是 127.0.0.1 不是 localhost mysql -h 127.0.0.1 -P 3306 -u root -p这里有个几乎所有人都会踩的坑-h localhost和-h 127.0.0.1走的不是同一条路。MySQL 客户端对localhost有特殊处理会优先走 Unix socket 文件比如/var/lib/mysql/mysql.sock完全绕开 TCP 协议栈。所以你用localhost测通了并不代表 3306 端口是通的。用127.0.0.1测出来的结果才是有效结论配合ss的输出就能直接分成两类本机127.0.0.1能连、外部连不上 → 问题在监听地址或防火墙往下看第 3、5 节本机127.0.0.1也连不上 → 服务压根没起来或者 socket 路径不对看服务日志2.4 抓包什么时候才值得用前面几步都做过、结论却互相矛盾的时候就该抓包了。别一上来就 tcpdump那是浪费时间。# 在服务端抓看 SYN 有没有到 tcpdump -i any -nn port 3306 -c 50抓包结果的解读非常干脆只看到客户端的 SYN、没有服务端的 SYN-ACK也没有 RST → 包在服务端本机就被防火墙 DROP 了连 SYN 都看不到 → 包在到达这台机器之前就被丢了问题在虚拟网络或中间链路看到了 SYN 和 RST → 端口没监听服务没起来三条结论对应三个完全不同的修复动作比任何猜测都可靠。我在虚拟机环境里排查这类问题时抓包几乎是一锤定音的手段因为虚拟网络那一层看不见摸不着只有包能告诉你真相。3. 监听地址MySQL 到底有没有对外开口3.1 看懂 ss 输出里的三个地址ss -lntp | grep 3306的输出常见形态有三种含义完全不同LISTEN 0 151 127.0.0.1:3306 0.0.0.0:* users:((mysqld,pid1234,fd21)) LISTEN 0 151 0.0.0.0:3306 0.0.0.0:* users:((mysqld,pid1234,fd21)) LISTEN 0 151 [::]:3306 [::]:* users:((mysqld,pid1234,fd21))第一种只在回环地址上监听任何来自外部的连接都进不来这是 MySQL 的默认行为之一也是最常见的原因。第二种监听所有 IPv4 地址外部可以连。第三种是 IPv6 版本很多发行版的包默认会开一般不影响 IPv4 的连通性但如果客户端用了 IPv6 解析而服务端只开了 IPv4也会出现诡异现象。看到第一种问题基本就锁定了服务端从头到尾就没打算让外人连。这时候你需要的不是重启服务是改配置。3.2 bind-address 改哪里、怎么改不同发行版和安装方式的配置文件位置差别很大这是很多人改了半天没生效的根本原因。常见位置如下安装方式典型配置文件CentOS / RHEL 系 rpm 安装/etc/my.cnfUbuntu / Debian apt 安装/etc/mysql/mysql.conf.d/mysqld.cnf源码编译安装编译时指定的my.cnf通常在/etc/my.cnfDocker 官方镜像通过挂载配置或命令行参数传入改法就是在[mysqld]段落下加一行[mysqld] bind-address 0.0.0.00.0.0.0是一个通配地址意思是所有本机网卡都监听包括虚拟网卡、物理网卡、回环。如果只想对某一个网段开放也可以写具体地址比如bind-address 192.168.172.130。MySQL 8.0 之后还有个容易忽略的点除了bind-address还有一个mysqlx-bind-address那是 X Protocol默认 33060 端口的配置和传统的 3306 是两码事。你只是想让 3306 通不用管它但如果排查时看到 33060 相关的东西别被带偏。改完必须重启才生效sudo systemctl restart mysqld # 或者 sudo systemctl restart mysql服务名到底是mysqld还是mysql用systemctl list-units | grep -i mysql确认一下写错了会报 Unit not found白白浪费一轮。提示改配置前先cp my.cnf my.cnf.bak。我见过有人把手写配置粘错位置导致 mysqld 直接起不来回滚都没备份可回。这个习惯成本极低收益极高。3.3 skip-networking 是个一票否决开关有一个参数比bind-address更狠叫skip-networking。它一旦被打开MySQL 会完全关闭 TCP 监听只保留 Unix socket 通信。这种配置一般是出于安全加固的考虑在某些安全基线模板里会被默认加上。表现就是ss -lntp | grep 3306什么都不输出netstat也看不到任何东西但mysql -u root -p用 socket 方式连得好好的。如果你遇到这种情况去配置文件里搜一下skip-networking注释掉它重启问题解决。顺带说一句skip-networking和bind-address127.0.0.1的效果在外部连不上这一点上是一样的但成因不同排查时要用ss区分。如果ss有输出但地址是回环那是bind-address的锅如果ss完全没有 3306那是skip-networking或者服务没起来。4. 账号授权能连上之后才轮到它说话4.1 mysql.user 里的 host 字段才是真正的门禁bind-address和防火墙解决的是能不能连到 3306账号授权解决的是连上了之后让不让你进。前者失败报2002/2003后者失败报1045。顺序上这两个问题不会同时暴露因为前者不解决你根本看不到后者。一旦端口通了就可能撞上1045 Access denied。根因在mysql.user表里每条账号记录都是用户名 来源主机的组合SELECT user, host, plugin FROM mysql.user;你会看到类似这样的输出---------------------------------------- | user | host | plugin | ---------------------------------------- | root | localhost | caching_sha2_password | | app | % | caching_sha2_password | ----------------------------------------rootlocalhost这条记录只允许从本机连接哪怕你密码输对了从远程连也会被拒。想让远程连就得有一条 host 是%或者具体网段的记录。4.2 匹配规则是最具体优先不是简单的包含关系MySQL 在匹配账号时会从所有 host 值里挑出最具体的那一条。192.168.172.130比192.168.172.%具体192.168.172.%又比%具体。这一点在排查时有个很实际的影响如果你同时存在app192.168.172.130密码是 A和app%密码是 B从.130这台机器连过来时会匹配到更具体的那条密码要用 A。很多人明明改了%那条的密码连过去还是报密码错原因就在这里。授权语句的完整写法CREATE USER app192.168.172.% IDENTIFIED BY 你的强密码; GRANT ALL PRIVILEGES ON your_database.* TO app192.168.172.%;关于FLUSH PRIVILEGES这里要澄清一个流传很广的误解使用CREATE USER、GRANT、ALTER USER这类 DCL 语句时MySQL 会自动刷新权限缓存不需要手动执行FLUSH PRIVILEGES。只有当你直接用INSERT、UPDATE去改mysql.user表的时候才必须手动刷一次。这个区别理解了你就不会在每句话后面都习惯性敲一遍了。4.3 认证插件引发的另一类连不上还有一个报错长得像连接问题实际上是认证插件问题Authentication plugin caching_sha2_password cannot be loadedMySQL 8.0 把默认认证插件从mysql_native_password换成了caching_sha2_password一些老版本的客户端库、老版本的图形化工具、老版本的驱动不认识这个新插件就报这个错。它和115完全是两回事别混在一起查。如果确实需要兼容老客户端可以针对某个账号单独改插件ALTER USER app% IDENTIFIED WITH mysql_native_password BY 你的强密码;但我要提醒一句这属于降低安全性的兼容手段只建议在测试环境或者确实无法升级客户端时使用。生产环境里更合理的做法是升级客户端库8.0 以后的驱动基本都支持新插件了。5. 防火墙115 这个错误号最大的嫌疑人5.1 Linux 防火墙的三套体系包到底有没有被丢看的就是防火墙。Linux 上现在有三套并存的东西排查时要知道自己在跟哪一套打交道firewalldCentOS 7、RHEL 系默认# 看当前规则 sudo firewall-cmd --list-all # 永久开放 3306 sudo firewall-cmd --permanent --add-port3306/tcp sudo firewall-cmd --reload注意--permanent和--reload的配合。只加--permanent不 reload 不生效只写运行时规则不加--permanent重启就没了两个都写才行。这个坑我踩过不止一次。ufwUbuntu 系sudo ufw status verbose sudo ufw allow 3306/tcpiptables底层前两者的实际执行者sudo iptables -L -n --line-numbers这里有个很重要的区分DROP 和 REJECT 造成的报错完全不同。DROP是把包悄悄丢掉客户端等不到任何回应最后给你115REJECT是会回一个 ICMP 或 RST客户端立刻收到111 Connection refused。所以当你看到115去 iptables 里找DROP相关的规则看到111去找REJECT。这个对应关系能省下大量猜测时间。注意修改防火墙规则之前先确认自己还有 SSH 通道能进去或者有控制台可用。手滑把自己关在门外的经历每个运维大概都有一两次。5.2 虚拟网络模式与宿主机防火墙回到虚拟机这个场景。VMware 有三种常见的网络模式对连通性的影响完全不同模式虚拟机 IP 来源宿主机能否直连虚拟机局域网其他机器能否连桥接Bridged与物理网络同段能能NATVMware 虚拟网段能默认不能需端口转发仅主机Host-onlyVMware 虚拟网段能不能如果你的目标是本机 Windows 去连虚拟机三种模式都可以。但如果是局域网里另一台机器要连NAT 和仅主机模式就必须配端口转发或者改桥接。还有一层容易被忽略的Windows 宿主机自己的防火墙。VMware 会装虚拟网卡vmnet1仅主机和vmnet8NATWindows 防火墙可能把这两个网卡识别为公用网络从而施加更严格的限制。排查时可以在 Windows 上临时关闭防火墙做一次对照实验。# 临时关闭仅用于排查用完记得开回来 Set-NetFirewallProfile -Profile Domain,Public,Private -Enabled False这个操作只做对照验证完立刻恢复。用系统级防火墙做实验必须谨慎。5.3 容器场景下还有一层端口映射如果 MySQL 跑在 Docker 里还有一层 NAT 要穿docker run -d --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD你的密码 \ mysql:8.0-p 3306:3306这个参数是把宿主机的 3306 映射到容器里的 3306缺了它容器内部监听再好外部也连不上。排查容器场景时的正确姿势是先钻进容器里测docker exec -it mysql8 mysql -h 127.0.0.1 -u root -p容器内能连、宿主机连不上问题在端口映射或宿主机防火墙容器内都连不上问题在容器内的 MySQL 配置。6. 一次完整的排查复盘从 115 到定位到根因6.1 环境与现象说一个我实际处理过的案例。环境是 Windows 11 宿主机 VMware Workstation Ubuntu 22.04 虚拟机MySQL 8.0 是从 apt 装的。现象就是本文标题那句报错客户端等大约 15 秒后返回115。6.2 逐层验证的完整链路我把每一步的命令、结果和当时的判断列成表你可以照着这个顺序走一遍步骤执行位置命令结果判断1Windowsping 192.168.172.130通网络层可达继续2WindowsTest-NetConnection ... -Port 3306TcpTestSucceeded: False端口层不通3Ubuntusystemctl status mysqlactive (running)服务是活的4Ubuntuss -lntp | grep 33060.0.0.0:3306监听地址没问题5Ubuntumysql -h 127.0.0.1 -u root -p成功服务端本地完全正常6Ubuntusudo ufw statusactive规则里没有 3306高度可疑7Ubuntusudo ufw allow 3306/tcp规则已添加再次从 Windows 测试8WindowsTest-NetConnection ... -Port 3306TcpTestSucceeded: True端口通了9Windowsmysql -h 192.168.172.130 -u root -pERROR 1045变成账号问题10UbuntuCREATE USER app192.168.172.% ...成功授权完成11Windows用新账号连接成功收工6.3 这个案例最值得记住的地方注意第 8 步和第 9 步的衔接。防火墙一开报错从2002/115变成了1045。这是绝大多数人第一次遇到时会懵掉的地方——我明明只是开了个防火墙怎么报错变了其实这是好消息说明你解决了一个问题暴露出了下一个问题。排查这类问题的本质就是一层一层剥洋葱网络层 → 端口层 → 服务监听层 → 防火墙 → 账号授权层。每剥开一层报错就会变一次报错不变说明这一层还没过。理解了这个模型你就不会在报错变化时慌乱反而会把它当成进度条。6.4 验证阶段别偷懒修完之后我习惯做三件事确认# 1. 服务端确认监听还在 ss -lntp | grep 3306 # 2. 从远端做一次端口级确认 nc -zv -w 3 192.168.172.130 3306 # 3. 实际跑一条查询确认权限也没问题 mysql -h 192.168.172.130 -u app -p -e SELECT COUNT(*) FROM your_database.your_table;第三条特别重要。端口通、账号能登录不代表这个账号对目标库有权限。1044 Access denied for user ... to database是权限粒度问题和连接问题又是两回事。一次把三件事都确认了才算真正收工。7. 修完还是连不上几个藏得比较深的坑7.1 配置改了但没重启或者被别的文件覆盖了MySQL 读配置是多文件叠加的/etc/my.cnf里的配置可能被/etc/my.cnf.d/*.cnf或者/etc/mysql/conf.d/*.cnf里的内容覆盖后读取的文件优先级更高。你改了 A 文件实际生效的是 B 文件这种情况非常普遍。验证真实生效值的办法是从服务端查SHOW VARIABLES LIKE bind_address; SHOW VARIABLES LIKE port;数据库里显示什么就是实际生效的什么。别猜查。7.2 SELinux 在中间又拦了一道CentOS / RHEL 系默认开启 SELinux即使防火墙放通了、bind-address也改对了SELinux 依然可能阻止 MySQL 监听非标准端口。快速验证可以临时切到宽容模式getenforce sudo setenforce 0如果切完之后立刻能连那就是 SELinux 的问题正确做法是用semanage port -a -t mysqld_port_t -p tcp 3306永久放行而不是把 SELinux 一直关着。临时验证可以长期关闭不建议。7.3 客户端自己的配置文件在捣鬼这个坑极其隐蔽。MySQL 客户端也会读配置文件如果你本机Windows 或 Linux 客户端的my.ini或~/.my.cnf里有这么一段[client] host 127.0.0.1 port 3306那么即使你在命令行里写了-h 192.168.172.130某些场景下也可能被配置文件里的值干扰。排查时可以用--no-defaults跳过所有配置文件再试一次mysql --no-defaults -h 192.168.172.130 -u app -p如果这样能连上说明问题在你的客户端配置文件里和服务器一点关系都没有。7.4 连接超时设置让人误判MySQL 客户端默认的connect_timeout在某些版本上是比较长的。如果你觉得每次都要等很久才报错可以主动缩短让排查节奏快一点mysql --connect-timeout3 -h 192.168.172.130 -u app -p三秒内得不到响应就直接报错这样你可以快速迭代验证而不是每试一次都要盯着屏幕等十几秒。这个技巧在反复调试防火墙规则的时候特别有用。8. 一份可以直接抄走的排查清单把整个过程压成一份清单下次再遇到2002 115从上往下走一遍就行确认对端活着ping目标 IP。不通就先修网络别往下走。端口级探测nc -zv -w 3 IP 3306。refused查服务timed out查防火墙。服务端自检systemctl status mysql确认服务状态ss -lntp | grep 3306确认监听地址。区分 localhost 与 127.0.0.1本机测试一律用127.0.0.1避免被 socket 通信误导。查 bind-address配置改成0.0.0.0改完重启再用SHOW VARIABLES确认生效。查 skip-networking配置文件里搜一遍有就注释掉。查 Linux 防火墙firewalld / ufw / iptables 三选一注意--permanent和--reload的配合。查宿主机与虚拟网络虚拟机开没开、网络模式是什么、Windows 防火墙有没有拦vmnet8。查容器映射-p 3306:3306有没有写容器内测一遍。最后查账号SELECT user, host, plugin FROM mysql.user;确认有对应来源的账号。这份清单的价值在于顺序。很多人卡住不是因为不会查而是因为顺序错了——先去翻权限表查了半天没结果其实问题在网络层权限表你连看都看不到。永远从最外层往里查这是排查连接类问题的铁律。我个人在这些年的经验里115这个错误号出现时真正的原因分布大概是这样的防火墙类含虚拟网络和宿主机防火墙占了大头其次是虚拟机压根没开或者挂起服务端bind-address问题排第三skip-networking属于偶发但一旦遇到就特别唬人的类型。至于账号授权问题它压根不会给你115只会给你1045——所以下次再看到115你可以直接跳过权限表这一环省下的时间够泡杯茶了。