这事我熟。前阵子有位朋友在服务器上装好宝塔,MySQL 状态一片绿,可本机的 Navicat 怎么都连不上,一会儿报 2003,一会儿报 1130。他先是怀疑密码不对,改了几遍密码,又重启了几次数据库,问题依旧。最后我远程看了一眼,发现根本不是密码的事,而是 MySQL 默认只在本地“开门”,服务器防火墙、云安全组也都没给 3306 放行。这种场景我见过太多次了,今天就把“宝塔安装 MySQL 后开启远程连接”这条链路完整捋一遍,从账号权限、监听地址到防火墙、安全组,一步不落。
1. 为什么宝塔装好 MySQL 后本地怎么也连不上
1.1 先看现象:三种最常见的失败表现
很多人第一次远程连 MySQL,遇到的报错不外乎下面三种:
| 报错关键字 | 含义 | 大概率原因 |
|---|---|---|
2003 - Can't connect to MySQL server | 客户端连不上 MySQL 服务 | 网络不通、3306 未监听、防火墙拦截 |
1130 - Host '...' is not allowed to connect | MySQL 拒绝了当前主机的连接 | 账号 host 没配置好 |
1045 - Access denied for user '...'@'...' | 账号或密码错误 | 认证失败、权限不足 |
这三种报错的信息量完全不同。2003 往往意味着数据包根本到不了 MySQL,或者到了但没人应答;1130 说明数据包已经到了 MySQL,是 MySQL 自己根据账号的 host 设置把你拒了;1045 则是账号密码本身没过关。分清这一点,能省掉大量瞎折腾的时间。
我那位朋友最典型,密码改到键盘冒烟,看到的一直是 1130。因为密码根本没进入验证环节,MySQL 第一步就先看“你是从哪台机器来的”,来源不被允许,后面密码再对也没用。
1.2 背后的原因:MySQL 默认只接待“本地客人”
宝塔安装完 MySQL 之后,默认配置是非常保守的。层层叠叠的限制加在一起,导致远程连接基本不可能成功。我用一个类比来解释:MySQL 就像一栋有三道门禁的楼。
第一道是 MySQL 账号这层。系统默认的root账号绑定的 host 是localhost,翻译过来就是“只有住在楼里的人才可以进”。从远程发来的连接请求,即使密码正确,MySQL 看到来源 IP 不是本机,直接打发走。
第二道是 MySQL 服务监听层。默认的bind-address是127.0.0.1,意思是最多只在回环地址上听,外网网卡根本不报文。更极端的情况是配置文件里写了skip-networking,那 MySQL 干脆连 TCP/IP 都不开了,只用本地 socket 通信。
第三道是网络层。服务器的防火墙默认不会放行 3306,云厂商的安全组默认也不会放行 3306。就算 MySQL 服务把门敞开了,外面的访问请求也在小区门口就被保安拦住了。
这三道门任意一道没打开,你在本机用任何客户端都连不上。所以排查时必须一层层看,而不是简单换个密码就完事。
1.3 排查顺序:从面板到防火墙,一层层缩小范围
我的建议是不要一上来就乱改配置,先做一轮快速体检,确定问题出在哪一层。
第一步,先 SSH 登录服务器,用本地方式进 MySQL 看看账号情况:
mysql -uroot -p SELECT user, host FROM mysql.user;如果这里能看到root@localhost,其他 host 都是空的,基本可以断定账号层没开。
第二步,看 MySQL 实际监听在哪里:
ss -lntp | grep 3306如果看到127.0.0.1:3306,说明它只监听了本地回环。
第三步,查防火墙状态:
systemctl status firewalld sudo ufw status第四步,去云厂商控制台看安全组入方向规则里有没有 3306。
顺序执行完,问题出在哪个层面就非常清楚了。接下来再动手改,心里有数。
2. 开启远程连接前必须搞懂的账号权限模型
2.1 MySQL 账号里的 host 到底是什么意思
很多新手对 MySQL 账号的理解停留在“用户名 + 密码”,实际上 MySQL 账号是“用户名 + host”的组合。同一个用户名可以对应多个 host,比如'root'@'localhost'、'root'@'127.0.0.1'、'root'@'%',它们在mysql.user表里是三条完全不同的记录。
host 字段描述了“这个账号允许从哪台机器登录”。localhost只允许本机通过 socket 或回环地址访问;%表示任意来源;也支持具体的 IP,比如'app'@'192.168.10.20',还支持网段,比如'app'@'10.0.0.%'。
理解这个之后,1130 报错就很好解释了:MySQL 根据客户端的实际来源 IP,去找有没有对应的账号记录,找不到就直接拒绝。即使密码是对的,它也不会去看密码。
2.2 为什么 root@localhost 这么特殊
root是 MySQL 里的超级管理员,拥有所有数据库的所有操作权限。默认情况下它只绑定localhost,这本是安全设计。但很多人为了图省事,执行了一条GRANT ALL PRIVILEGES ON *.* TO 'root'@'%',等于把超级管理员账号直接暴露在公网上。
这有多危险?服务器只要把 3306 端口对公网开放,扫描器能在一分钟内发现这个端口,然后开始暴力猜密码。root一旦被破解,整个数据库就完全暴露在攻击者手里。所以我的观点很明确:远程连接不要用root,单独创建一个业务账号,给最小权限,用完还能随时回收。
MySQL 匹配账号时会根据 host 做优先级排序,具体规则比较精细,但你可以简单理解成:更具体的 host 优先匹配。比如你连接时同时存在'app'@'%'和'app'@'192.168.10.20',来自 192.168.10.20 的连接会命中后者。这个特性可以用来做精细控制。
2.3 GRANT 授权背后的机制,以及版本差异
授权命令的核心是把“从某个 host 来的某个用户”和“某些数据库的某些权限”绑定在一起。低版本 MySQL 里常见写法是:
GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY '123456';这条命令同时完成创建用户、设置密码、授予权限三个动作,非常方便。但 MySQL 8.0 开始,这种语法不再支持,必须先创建用户再授权,否则直接报错:
CREATE USER 'app_user'@'%' IDENTIFIED BY 'StrongPass!'; GRANT SELECT, INSERT, UPDATE, DELETE ON mydb.* TO 'app_user'@'%';权限类型上,ALL PRIVILEGES是全部权限,适合管理员;业务账号建议只给SELECT、INSERT、UPDATE、DELETE等实际用得到的权限,库名精确到mydb.*,表名精确到mydb.orders都行。
还有一点常被忽略:改完权限后执行FLUSH PRIVILEGES。虽然部分操作会自动刷新权限缓存,但手动执行一次更稳妥,尤其是直接修改mysql.user表的情况,不刷新可能导致修改不生效。
3. 在宝塔里一步步开启远程连接(推荐做法)
3.1 第一步:确认 MySQL 监听地址不是 127.0.0.1
宝塔装的 MySQL,配置文件位置因安装方式而异,常见路径是/etc/my.cnf或/www/server/mysql/etc/my.cnf。在宝塔的文件管理器里搜my.cnf就能找到。打开后重点看[mysqld]段的内容。
如果看到这一行:
bind-address = 127.0.0.1说明 MySQL 只在本地回环地址上监听,外部连接自然不进来。改成:
bind-address = 0.0.0.00.0.0.0表示监听所有网卡接口。如果没有这一行,可能默认就是监听所有接口;但一旦看到skip-networking,必须注释或删掉,因为它会直接关闭 TCP/IP 通信。
改完配置文件后重启 MySQL 让配置生效。宝塔面板软件商店里可以直接重启,命令行也可以:
systemctl restart mysqld # 或者 service mysqld restart重启后检查监听状态,确认变成了0.0.0.0:3306:
ss -lntp | grep 3306只有看到这行,才说明服务层已经“开门”。
3.2 第二步:创建专用账号并授权,不建议直接放开 root
这一步我推荐独立账号。先本地进入 MySQL:
mysql -uroot -p假设你的业务库叫mydb,需要给你的办公电脑放行,办公出口 IP 是192.168.10.20,可以这样:
CREATE USER 'app_user'@'192.168.10.20' IDENTIFIED BY 'Str@ng_Pass!2025'; GRANT SELECT, INSERT, UPDATE, DELETE ON mydb.* TO 'app_user'@'192.168.10.20'; FLUSH PRIVILEGES;如果连接来源不固定,暂时可以用%代替 IP,我建议后期有条件再收回来:
CREATE USER 'app_user'@'%' IDENTIFIED BY 'Str@ng_Pass!2025'; GRANT SELECT, INSERT, UPDATE, DELETE ON mydb.* TO 'app_user'@'%'; FLUSH PRIVILEGES;这里有两个容易被坑的地方。
第一个是 MySQL 8.0 的默认认证插件是caching_sha2_password,老版本的 Navicat、DBeaver 可能不支持,连接时报:
Authentication plugin 'caching_sha2_password' cannot be loaded解决办法是把该账号改为mysql_native_password,或者升级客户端。后者显然更合理,但临时处理可以执行:
ALTER USER 'app_user'@'%' IDENTIFIED WITH mysql_native_password BY 'Str@ng_Pass!2025';第二个是权限范围。初学者容易一上来就给*.*,感觉省事。但*.*表示所有库所有表,一旦账号泄露,整个实例都完了。除非是个人开发测试环境,否则不建议这么干。
3.3 第三步:放行 3306 端口,宝塔防火墙和系统防火墙双重检查
账号搞定不代表连接能通,网络层的门还没开。宝塔面板自带防火墙入口,在“安全”菜单里找到“防火墙”,添加一条端口规则,TCP 协议,端口填3306,来源建议填你的出口 IP,不要图方便直接填0.0.0.0/0。
但宝塔面板只是其中一层。很多服务器系统层面还有 firewalld 或 ufw,如果它们没有放行 3306,宝塔面板规则再漂亮也没用。
CentOS 系服务器用 firewalld 的话:
systemctl status firewalld firewall-cmd --permanent --add-port=3306/tcp firewall-cmd --reloadUbuntu / Debian 用 ufw:
ufw status ufw allow 3306/tcp还有 iptables 直接管理的场景:
iptables -L -n | grep 3306 iptables -A INPUT -p tcp --dport 3306 -j ACCEPTiptables 的规则要注意优先级,放行规则要放在拒绝规则之前。另外有些云镜像默认有安全加固,会有一套复杂的 iptables 脚本,手动加规则可能重启失效,这种情况建议去云控制台安全组统一管。
3.4 第四步:云服务器安全组放行,这层最容易漏
到了云环境,还必须检查安全组。以主流的几朵云为例,进入实例控制台,找到当前实例绑定的安全组,添加入方向规则:协议TCP,端口3306,来源根据实际情况填0.0.0.0/0或你的办公 IP。
这里最容易漏,因为很多人会下意识认为服务器上防火墙已经放行就完事了。实际上云厂商的安全组是在虚拟机外面的独立网络层拦截,相当于小区门口的门禁。你把房间门、楼道门都打开了,小区保安不放行,依然进不来。
三层放行的关系可以概括成:安全组是小区门禁,系统防火墙是单元楼门禁,MySQL 授权是房间门禁。三层都要通,远程连接才能真正建立。
我强烈建议安全组来源不要写0.0.0.0/0,至少先确认自己的公网出口 IP 是多少。查出口 IP 很简单,直接访问查询 IP 的网页就能看到。家里宽带如果是动态 IP,可以考虑后续配合 DDNS 解决,但那是另一个话题,至少别长期裸奔。
3.5 第五步:本机连接验证,先命令行后图形化工具
所有配置改完之后,别急着打开 Navicat 一顿瞎填。先在命令行里验证网络层通不通:
telnet 服务器公网IP 3306如果看到Connected to,说明 3306 端口从公网可以到达。如果一直卡住或超时,说明网络层仍有拦截,回到安全组和防火墙继续查。
端口通了,再用 MySQL 命令行验证账号授权:
mysql -h服务器公网IP -P3306 -uapp_user -p输入密码后能进入 MySQL 提示符,说明账号、密码、host、权限全部正确。这一步能成功,再用 Navicat、DBeaver 或 Workbench 连接就只是 GUI 配置问题。
图形工具配置时主要注意几个点:主机名填服务器 IP,端口填 3306,用户名填app_user,密码填授权时的密码。如果依然报错,优先看错误码是不是 2059,是的话回到 3.2 的认证插件处理。
4. 常见问题与排查技巧实录
4.1 授权后仍然 Access denied,问题出在哪
这是最让人崩溃的场景:明明执行了授权,mysql.user表里也能看到记录,但远程连的时候还是被拒。比较常见的原因有五个。
第一个是客户端实际出口 IP 和你授权的 IP 不一致。很多办公网络有多个出口,你以为自己从192.168.10.20出来,实际运营商 NAT 之后变成了另一个 IP。解决办法是先在服务器上看 MySQL 记录到的客户端 IP,或者临时授权%测一次,如果通了说明就是这个原因,再收敛 IP 范围。
第二个是mysql.user里同账号存在多条 host 记录,MySQL 按优先级匹配到了一条你没预期的记录,比如'app_user'@'localhost'优先于'app_user'@'%',导致连接走了另一套认证。
第三个是密码错误。注意区分 MySQL 的root密码、业务账号密码,还有宝塔面板的数据库密码,这三者不是一回事。
第四个是修改授权后没有FLUSH PRIVILEGES。在 MySQL 8.0 里用CREATE USER和GRANT一般会自动生效,但如果你手工改过表,或某些异常场景下缓存没刷新,执行一下也不会有坏处。
第五个是账号确实不存在。CREATE USER 和 GRANT 用混了,老语法在 8.0 里只执行了 GRANT 会直接报错,后续连接自然失败。
4.2 连接超时还是连接被拒绝,这两个区别可大了
错误信息里经常出现(10060)和(10061),很多人不区分,但它们指向的问题完全不同。
10060是操作超时,数据包发出去之后没有回应。常见原因是安全组没放行、系统防火墙把包直接丢弃、或者目标 IP 根本不对。这种情况下telnet通常会卡住不动,直到超时。
10061是 Connection Refused,说明目标主机收到了请求,但端口没有程序在监听,或者防火墙主动回了 RST 包。原因可能是 MySQL 没启动、bind-address配置导致 3306 没在公共网卡上监听、或者服务端口被占用。
排查时先看一眼服务状态:
systemctl status mysqld # 或 ps aux | grep mysqld再确认监听地址:
ss -lntp | grep 3306如果服务在跑,但监听地址是127.0.0.1:3306,那 10061 就很正常,回到 3.1 去改 bind-address。
4.3 改完配置不生效,可能是文件没找对
很多人改完my.cnf,重启 MySQL 后还是老样子,原因往往是改错了文件。一台机器上可能存在多个 MySQL 配置文件,MySQL 启动时按固定顺序读取,/etc/my.cnf、/etc/mysql/my.cnf、/www/server/mysql/etc/my.cnf都有可能。宝塔不同安装方式用的路径也不同。
先用命令看 MySQL 实际读取了哪些配置:
mysqld --verbose --help | grep -A 1 "Default options"看到输出里的文件路径列表,就知道该改哪个了。
另外还要确认你改的地方确实在[mysqld]段落下面,而不是[client]或[mysql]。配置写错段落,MySQL 根本不会读。改完配置文件后要有一次完整重启,热加载不一定对 bind-address 这种网络参数生效。
4.4 一行命令快速验证 3306 端口通不通
除了telnet,还有几个常用工具可以快速测试端口:
nc -vz 服务器IP 3306 nmap -Pn -p3306 服务器IPtelnet和nc主要用于 TCP 连通性测试,nmap还能顺便看到端口状态是 open、filtered 还是 closed。如果你在 Windows 上没有这些命令,可以安装对应工具,或者直接用系统自带Test-NetConnection -Port 3306配合 PowerShell。
这里提醒一下:不要单纯用 ping 判断服务器是否可达,因为很多云环境禁 ICMP,但 TCP 是通的。反过来也一样,ping 通不代表 3306 端口就放行了,端口层面还需要单独验证。
4.5 更稳妥的备选:用 SSH 隧道访问 MySQL
如果你的场景只是自己远程管理一下数据库,并不需要让公网直接访问 3306,那我推荐一个更稳的方案:SSH 隧道。它不需要在 MySQL 层面开放远程 host,甚至可以让 MySQL 继续保持bind-address = 127.0.0.1。
本机执行一条隧道命令:
ssh -L 13306:127.0.0.1:3306 root@服务器IP然后在本机连接127.0.0.1:13306,流量会通过 SSH 加密隧道转发到服务器的127.0.0.1:3306。MySQL 视角里这是本地回环连接,账号依然用root@localhost都没问题。
这样做的好处非常明显:公网上根本不开放 3306,安全组、系统防火墙都不需要额外放行,端口扫描扫不到,风险面大大缩小。缺点是 SSH 断了连接就断,适合个人运维或小范围团队使用,不适合高并发业务直连。
4.6 我的一些个人习惯与安全底线
最后分享一下我自己实践下来的习惯。第一,绝不给root建%记录。即使是内网环境,也要考虑到内网横向渗透的风险,用独立业务账号隔离。第二,业务账号的 host 能写具体 IP 就不写%,能写网段就不写公网全放开。第三,密码一定用随机生成的强密码,长度不低于 16 位,单独保存到密码管理器里。第四,定期在宝塔面板或命令行看一眼mysql.user表,清理掉不再使用的账号。
之前帮某公司排查过一次事故,就是服务器上root@%被扫描爆破成功,整库数据被加密勒索。虽然最后靠备份恢复了,但过程极其折腾。从那之后我一直坚持“最小权限 + 最小暴露面”的原则,宁可平时多改一次规则,也不要拿数据安全去赌。
远程连接本身并不复杂,把账号 host、bind-address、防火墙、安全组这几层之间的关系理顺,按照流程一步步操作,基本上十分钟就能搞定。真正麻烦的从来不是配置命令,而是出问题之后不知道去哪一层排查。希望这篇内容能帮你少踩几个坑。