刚装完MySQL,第一件该干的事不是急着建库建表,而是跑一遍 mysql_secure_installation。这个脚本估计大家都见过,但说实话,很多新手跑的时候是纯“下一步机器”,每一步的英文提示到底在问什么、选了Y之后会发生什么、选N又会留下什么隐患,完全没概念。这篇就把这个脚本从执行过程到原理,从头到尾拆一遍,尽量让你跑完脚本之后,心里清楚MySQL到底被“拧紧”了哪些地方。
1. 刚装完的MySQL到底有多“裸”:为什么要先跑安全初始化
1.1 默认状态下的四个高危入口
用包管理器或官方仓库装完MySQL之后,服务能起来,但你如果直接看初始状态,会发现这东西基本等于门没锁:
- 可能存在匿名账号(
user=''),也就是说,本机任何用户不需要用户名密码,直接执行mysql就能连进数据库。这不是开玩笑,早期版本确实是这样。 - root账号访问控制很松。在某些安装方式下,root的密码为空,或者走的是本机
auth_socket认证,只要能ssh到服务器,你就是root。 - 自带一个对所有人开放的
test库。默认授权表里,test库和test_%开头的库被授权给了匿名用户,别人连上来第一件事可能就是拿test库做跳板。 - 如果服务监听了公网地址,3306端口一暴露,扫描器发现MySQL版本后,最先尝试的就是空密码、root/root、root/123456这一类组合。
这四个问题单看都是小毛病,合在一起就是灾难。很多“MySQL被入侵”的案例,回溯一下,根本原因就是新装完没跑安全初始化,匿名账号加空密码,等于给攻击者递钥匙。
1.2 脚本那五件事,其实是五道安全闸门
mysql_secure_installation 一共做五件事,我习惯把它理解成五道闸门:
- 设置或确认root密码,并视情况启用密码强度校验组件;
- 删除匿名账号;
- 禁止root远程登录;
- 删除test库及其访问权限;
- 刷新权限表,让前面的修改立即生效。
前四道关掉高危入口,第五道是确保修改落地。后面我会按执行顺序,把每一步的交互提示和你应该怎么选,逐个讲清楚。
有一个容易被忽略的点:这个脚本默认是交互式执行,它不会偷偷改你已经存在的数据,也不会帮你配置远程访问、修改端口、加固防火墙。它的边界非常明确,只管上面五件事。有些人以为跑完脚本数据库就“绝对安全”了,那是误解。它只是把默认安装里明显不合理的权限入口封住。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 交互式执行全流程:每一步提示到底让你干什么
2.1 前置条件:服务先起来,别卡在socket连接上
跑脚本之前,MySQL服务必须已经在运行。很多人一执行 mysql_secure_installation 就报下面这个错:
code复制ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/tmp/mysql.sock' (2)
这个报错的意思是:脚本想通过Unix socket连本机的MySQL,但 /tmp/mysql.sock 这个socket文件不存在。MySQL连本机默认不走TCP,而是走socket文件,文件可能不在 /tmp 下。
排查顺序我建议这样:
- 先确认服务真的起来了:
systemctl status mysqld,或者ps -ef | grep mysqld; - 看进程有没有监听端口:
ss -lntp | grep 3306; - 找一下socket文件实际在哪:
find / -name "*.sock" 2>/dev/null | grep -i mysql; - 如果文件在
/var/lib/mysql/mysql.sock之类的位置,脚本应该能自动找到;如果脚本读的是默认路径,就用mysql_secure_installation --socket=/var/lib/mysql/mysql.sock指定一下。
不同安装方式的默认路径差异很大。Ubuntu的deb包默认的是 /var/run/mysqld/mysqld.sock,RHEL/CentOS的yum包默认是 /var/lib/mysql/mysql.sock,源码编译的默认经常是 /tmp/mysql.sock。这不是脚本本身的bug,是你没有提前确认环境的socket路径。脚本最开始会尝试用 mysqladmin 去探测连接,socket连不上就直接退出,不会给你慢慢检查的机会。
2.2 第一步:root密码与密码校验策略
脚本启动后,第一段提示通常是这样:
code复制Securing the MySQL server deployment.
Connecting to MySQL using a blank password.
如果你能直接进到这一步,说明当前root是空密码或者走socket免密认证。接下来它会问你是否设置root密码:
code复制VALIDATE PASSWORD COMPONENT can be used to test passwords
and improve security. ...
Do you wish to continue with the password provided?
Press y|Y for Yes, any other key for No:
这里是在问:要不要启用密码强度校验组件。如果你输入y,后面设置密码的时候,它按强度帮你把关;输入n,就只走MySQL最基础的密码长度检查。
我几乎每次都选y,原因很简单:密码强度校验组件除了管root密码,还管以后所有新创建用户的密码强度。你设置一个“带大小写+数字+特殊字符”的密码可能觉得麻烦,但如果没有这个组件拦着,团队里很容易出现 123456 这种密码。选y之后,它会让你选择密码强度等级:
code复制Please enter 0 = LOW, 1 = MEDIUM and 2 = STRONG:
- 0(LOW):密码长度至少8位;
- 1(MEDIUM):至少8位,还要包含大小写字母、数字、特殊字符;
- 2(STRONG):在MEDIUM基础上,密码不能出现在字典文件里。
个人建议选择1,也就是MEDIUM。LOW太弱,STRONG在某些环境下会拦掉太多合法密码,导致运维或者应用起不来。MEDIUM是性价比最高的档位,既挡住了最普遍的弱密码,又不会让团队集体骂人。
接下来就是设置新密码:
code复制New password:
Re-enter new password:
这里要说明:如果你之前root已经有密码,脚本会先让你输入当前root密码:
code复制Enter current password for root (enter for none):
输错了会直接报 Access denied for 'root'@'localhost',脚本就中断了。所以跑脚本前要确认自己知道当前root密码,或者当前确实是免密登录状态。
2.3 移除匿名账号:别让路人随便进你家门
设置完root密码后,下一个问题是:
code复制Remove anonymous users? (Press y|Y for Yes, any other key for No):
这一步是删掉所有用户名为空(user='')的账号。为什么匿名账号危险?因为匿名账号在权限匹配里的优先级虽然不高,但它意味着不需要任何身份凭证就能建立数据库连接。配合某些授权,匿名用户甚至有登录test库的权限。
实际操作中,我见过有人在生产库上发现 ''@'localhost' 和 ''@'server_hostname' 两个匿名账号,一查是当年装库的时候嫌麻烦,这个提示按了N。N的意思是“不删除”,等于在自己门上挂了一把假锁。这里没有任何理由选N,直接y。
2.4 禁止root远程登录:数据库管理员不该满世界飞
再往下:
code复制Disallow root login remotely? (Press y|Y for Yes, any other key for No):
这问的是:允不允许root账号从非本机地址登录。选y之后,脚本会删除或禁用所有Host不是localhost/127.0.0.1之类的root账号。如果你一开始创建过 root@'%' 这种账号,也会被一并干掉。
有些运维会在这个提示上犹豫,因为他们的业务就是要远程连数据库,而且连接账号就是root。我的建议是:业务连接绝对不要用root。原因不是root密码一定不安全,而是root的权限是ALL,一旦业务侧数据库账号泄露,攻击者拿到的就是整个MySQL实例的所有权限,所有库都能删。这不是危言耸听,很多删库跑路事故的起点就是业务代码里写死了root账号。
正确的做法是:选y禁止root远程,然后为业务单独创建账号,只授权需要的库和权限。比如:
sql复制CREATE USER 'app_user'@'192.168.1.%' IDENTIFIED BY 'StrongAppPassword2025!';
GRANT SELECT, INSERT, UPDATE, DELETE ON app_db.* TO 'app_user'@'192.168.1.%';
FLUSH PRIVILEGES;
这样即使app账号泄露,攻击面也只是 app_db 这一个库。如果确实需要远程做管理,可以临时创建带 GRANT OPTION 的管理账号,用完后删除,不要图省事把root放出去。
2.5 删除test库:默认后门的物理清除
接着提示:
code复制Remove test database and access to it? (Press y|Y for Yes, any other key for No):
默认安装会创建一个test库,并且授权给匿名用户。这个库平时开发调试用一下无所谓,但在生产服务器上就是一个后门:它让任何能连上MySQL的人,至少有一个可以“合法”读写操作的库。而且很多人习惯把不重要的数据、临时表都往test库里塞,时间一长,什么奇怪的东西都有。
选y之后,脚本会执行:
sql复制DROP DATABASE IF EXISTS test;
同时删除 mysql.db 表中所有和test库相关的授权记录。这个操作不会影响你自己建的库,所以放心y。
2.6 刷新权限表:让改动立即生效
最后一个问题:
code复制Reload privilege tables now? (Press y|Y for Yes, any other key for No):
这一步内部执行的是 FLUSH PRIVILEGES;。MySQL在启动时会一次性把 mysql.user 等权限表读入内存,平时你用 CREATE USER、GRANT 修改权限,MySQL自己会感知并更新内存,但如果你直接改表(比如用 INSERT INTO mysql.user),就必须手动刷新。脚本前面几步删除账号、修改Host,本质上都在改权限表,所以最后必须刷新,不然前面等于白做。
这个提示同样是选y。选n的话,脚本退出后,理论上前面的改动还在表里,但当前运行实例的内存权限数据还是旧的,行为会非常诡异。
3. 非交互模式:批量初始化服务器时的自动化方案
3.1 手动交互的两大痛点
只用一台服务器,手动跑一遍脚本没问题。但如果你在云上同时开通了五台、十台MySQL,或者需要给一批测试环境反复初始化,手动模式就有两个明显的痛点:
- 容易漏选。提示一堆,脚本跑完也不汇总你到底选了y还是n,排查的时候只能回头重新看。
- 容易卡住。如果你通过SSH远程执行,中途网络抖动,脚本可能在某个输入框里一直等你,但人已经不在终端前了。
所以批量场景下,必须用非交互方式执行。
3.2 管道输入和expect脚本
最朴素的做法是把答案通过管道喂给脚本。比如:
bash复制mysql_secure_installation <<EOF
n
NewStrongPassword2025!
NewStrongPassword2025!
y
y
y
y
EOF
这串EOF里的内容会按顺序对应脚本的每次提问。但这里要特别提醒:不同版本的MySQL,交互顺序是有差异的。5.7和8.0之间,有的版本会先问root当前密码,有的版本会先问要不要启用密码校验组件,甚至同一个版本在不同发行版上,行为都不一样。管道方案一旦顺序不对,脚本就会卡住或者报错,非常考验运气。
我在生产环境更推荐用 expect 来驱动,因为它可以按提示内容动态响应,不依赖固定顺序。下面是个可用的模板:
bash复制#!/usr/bin/expect
set timeout 10
spawn mysql_secure_installation
expect "Enter current password for root"
send "\r"
expect "Set root password"
send "y\r"
expect "New password:"
send "NewStrongPassword2025!\r"
expect "Re-enter new password:"
send "NewStrongPassword2025!\r"
expect "Remove anonymous users"
send "y\r"
expect "Disallow root login remotely"
send "y\r"
expect "Remove test database"
send "y\r"
expect "Reload privilege tables"
send "y\r"
expect eof
这里说的是“当前root无密码”的情况,所以 Enter current password for root 后直接回车。如果root有密码,就把 send "\r" 改成 send "你的当前密码\r"。模板里每个 expect 都是匹配提示中的关键字,不用管顺序,所以比固定管道稳定得多。
用expect之前先确认系统装了:
bash复制sudo yum install -y expect # RHEL/CentOS
sudo apt install -y expect # Debian/Ubuntu
3.3 自动化之后必须做验证
脚本执行完,自动化脚本也输出“done”了,但真正的工作还没结束。建议立刻做三组验证:
- 本机用root新密码登录:
mysql -uroot -p,能进就是第一步成功; - 再试一次
mysql -u不带用户名,看匿名登录是否已经被拒绝; - 从另一台机器上尝试用root远程登录,确认连接被拒绝。
有一个常见误区是:脚本跑完,mysql -uroot -p 可能还是报错。原因往往不是密码错了,而是你在有auth_socket认证的发行版上(典型的如Ubuntu的apt包),root账号默认走socket免密认证,脚本设置密码之后,你需要用 sudo mysql 进到MySQL里把root的认证方式改回 caching_sha2_password 或 mysql_native_password,比如:
sql复制ALTER USER 'root'@'localhost' IDENTIFIED WITH caching_sha2_password BY '你的密码';
这也解释了一个现象:为什么有些人跑完脚本,用密码死活登录不上,但用 sudo mysql 却能直接进。这不是脚本没生效,而是认证插件变了。
3.4 Docker环境里的另一套逻辑
如果你用Docker跑MySQL,情况又不一样。官方 mysql:8.0 镜像启动时会自动执行一段初始化脚本,它根据环境变量处理root密码、建库、建用户:
bash复制docker run --name mysql8 \
-e MYSQL_ROOT_PASSWORD=你的root密码 \
-e MYSQL_DATABASE=app_db \
-e MYSQL_USER=app_user \
-e MYSQL_PASSWORD=应用密码 \
-p 3306:3306 \
-d mysql:8.0
容器首次启动时,root远程登录默认是禁用的,匿名用户会被清掉,test库也不存在,所以严格来说,官方镜像不需要再跑一遍mysql_secure_installation。但如果你自己基于镜像做定制,或者在容器里手贱导入了旧的全量数据,还是值得跑一次脚本再确认一遍。
另外提醒一点:容器里的root密码通过环境变量传的时候,注意别写进docker-compose文件再提交到代码仓库,那等于把密码公开了。
4. 执行之后权限表到底变在哪:用SQL验证脚本效果
4.1 直接查mysql.user表,眼见为实
脚本跑完,我喜欢直接查权限表确认变化。用root登录后:
sql复制SELECT user, host, plugin, authentication_string, account_locked
FROM mysql.user\G
跑脚本之前,你大概率能看到类似这样的记录(具体Host和插件因版本而异):
| user | host | plugin | 说明 |
|---|---|---|---|
| root | localhost | auth_socket / caching_sha2_password | 本机root |
| root | 127.0.0.1 | caching_sha2_password | 本机root |
| 空字符串 | localhost | 无密码 | 匿名账号 |
| 空字符串 | 主机名 | 无密码 | 匿名账号 |
跑完脚本后,匿名账号那两行应该消失,root的Host列表如果之前有 %,也会被处理掉,只留下localhost这类本机地址。
有很多人跑完脚本发现root确实不能远程了,但表里还有 root@'127.0.0.1' 和 root@'::1',这是正常的。脚本保留这些是为了本机通过TCP方式连接时也能用,不等于远程开放。
4.2 mysql.db表里的test授权也被清掉了
还有一个容易被忽略的地方是 mysql.db 表:
sql复制SELECT * FROM mysql.db WHERE db='test'\G
正常跑完脚本后,这个查询结果应该是空。如果还有记录,说明脚本执行没完全成功,或者中间你手动加回去过。
MySQL权限匹配的顺序是先看 mysql.user,再看 mysql.db,再看 mysql.tables_priv 等细粒度表。匿名账号和test库的授权之所以危险,是因为它在最粗的维度上给了“所有人”某个库的访问权。删掉这两条记录,相当于把权限匹配入口收敛到了具体用户名。
4.3 对存量业务的实际影响
跑脚本前,很多DBA会担心一件事:会不会影响已经在跑的应用?我的经验是:
- 如果你应用连接数据库用的是独立账号,比如
app_user,脚本完全不受影响,因为脚本只动root、匿名账号、test库; - 如果应用用的是root账号,那就要特别注意。脚本禁掉root远程之后,应用连不上是必然的。这种环境说明应用账号管理早就该整改了,正好趁这个机会改掉;
- 脚本最后会执行
FLUSH PRIVILEGES,这个操作会刷新权限缓存,但不会中断已经建立的连接。长连接在线上的应用,只要连接已经建立,不会因为这个而被断开,只有新连接会按新权限判断。
所以结论是:在业务连接账号不是root的前提下,脚本可以放心跑,影响范围很小。
4.4 脚本不会碰的东西
顺便泼一盆冷水:脚本不会改 bind-address,也不会帮你配置防火墙。很多人在本地跑完脚本,然后从另一台机器连不上MySQL,第一反应是“脚本把我远程禁了”,其实多半是:
- MySQL只监听了127.0.0.1;
- 防火墙没放行3306;
- 云安全组没开放端口。
这三个问题脚本一概不管。检查远程连接问题,应该先看监听地址:
sql复制SHOW VARIABLES LIKE 'bind_address';
再看操作系统防火墙和云安全组。不要动不动就怀疑脚本把远程搞坏了。
5. 常见报错和误操作排查:我的踩坑记录与恢复方法
5.1 socket连接失败的排查顺序
前面提到过 ERROR 2002,这是新手阶段最高频的报错。我见过最离谱的一次,是服务端socket文件权限被改成了600,MySQL进程能正常读,但运行 mysql_secure_installation 的用户不是mysql用户,也没权限访问,结果报错误导出了一大堆方向。
排查顺序应该是:
systemctl status mysqld或service mysql status,确认服务活着;ss -lntp | grep 3306,确认TCP监听正常(注意socket连接不一定走TCP);- 看配置文件里的socket路径:
grep -r socket /etc/my.cnf /etc/mysql/; - 手动用完整socket路径连接:
mysql -uroot -p -S /var/lib/mysql/mysql.sock; - 如果这个命令能进,说明脚本只是没读到socket路径,加
--socket=参数即可。
5.2 密码策略太强导致设置失败
选了密码强度校验组件后,如果输入一个简单密码,会提示不符合策略要求,让你重新输入。这不是脚本卡死,是校验组件正常工作。
调试环境下,你可能真的需要一个简单密码。可以在MySQL里临时调低策略:
sql复制SET GLOBAL validate_password.policy = LOW;
SET GLOBAL validate_password.length = 8;
注意这是全局动态变量,但重启后会丢失。想让策略持久化,就写进配置文件:
ini复制[mysqld]
validate_password.policy=LOW
validate_password.length=8
不同MySQL版本里这个变量的命名有点区别,5.7版本常见的是 validate_password_policy,8.0组件形式是 validate_password.policy。设置前先查一下当前版本支持哪种写法。
5.3 管道脚本踩过的“y泥潭”
之前提到管道脚本对不同版本兼容性差,我实际踩过一次:MySQL 8.0的某个发行版,在设置root密码之前会先问“是否安装密码校验组件”,而我的管道答案第一个是n,结果那个n被当成“不启用校验组件”,后面的所有输入全部错位,脚本直接进入异常状态。
遇到这种问题,最快的处理方式:退出当前终端,重新进入,用expect脚本替代固定管道。expect只看提示关键字,不看顺序,能省掉很多这种“版本差异导致的玄学问题”。
5.4 误删root或把root Host改坏的恢复思路
还有一种情况:有人为了“让root能远程”,手动把 mysql.user 表里的Host改成 %,或者干脆误删了root记录,然后下不了台。这里给一个标准的恢复流程:
- 停止MySQL服务:
systemctl stop mysqld; - 在配置文件
[mysqld]段加一行:skip-grant-tables,同时建议在同一行加skip-networking,避免在无权限校验状态下暴露在网络里; - 启动服务:
systemctl start mysqld; - 无密码进入:
mysql -uroot; - 先执行一次
FLUSH PRIVILEGES;,让权限表生效; - 重置root密码:
sql复制ALTER USER 'root'@'localhost' IDENTIFIED BY '新密码';
如果是MySQL 5.7以下版本,语法可能是:
sql复制UPDATE mysql.user SET authentication_string=PASSWORD('新密码') WHERE User='root';
FLUSH PRIVILEGES;
- 退出,去掉配置文件里的
skip-grant-tables和skip-networking,重启服务。
这里要特别强调:skip-grant-tables 状态下MySQL几乎不设防,一定要加 skip-networking,不然整个IP段的人都能无密码连进来,那不是恢复,是自爆。
5.5 远程登录的正确姿势与错误示范
网上很多教程教人改 mysql.user 表把root的Host改成 %,然后改 bind-address=0.0.0.0,让root从任何机器都能登录。这个操作在隔离的内网测试环境里问题不大,但在公网环境等于把最高权限账号裸奔。
我的习惯是:
- root一定只留localhost;
- 需要远程的应用账号,Host写得越具体越好,能写
192.168.1.%就不要写%; - 每个应用独立账号,权限最小化,只给业务库的DML权限;
- 重要实例开启审计日志,至少能在出事后追溯是哪个账号、从哪个Host、执行了什么语句。
跑完 mysql_secure_installation 之后,最有价值的不是那五个y,而是它逼着你想清楚:root是给本机管理员用的,应用连接是该用独立低权限账号的。这个意识比脚本本身重要得多。
如果你刚接触MySQL,我的建议是:新装实例后,先跑一遍脚本,再用第4部分的SQL验证一次;如果是批量环境,就把脚本和验证命令一起固化成一个初始化模板。这样以后无论开多少台实例,底线都是齐的。
