刚帮一个朋友处理完线上 MySQL 被扫库的事故,又想起了这个几乎每个 MySQL 新手都会遇到、但很少真正重视的命令:mysql_secure_installation。很多人装完 MySQL 第一件事就是急着建库、导数据、连业务,安全脚本要么跳过要么“一路回车”,等出事了才回头看文档。这篇我就用实际执行的经验,把这个脚本从头到尾拆一遍,包括每一步会问什么、为什么要这么选、哪些环境不能无脑跑、以及跑完之后还需要哪些补充动作。
mysql_secure_installation是 MySQL 官方自带的安全初始化脚本,用来在刚装完 MySQL 之后,把那些开箱即用的“不安全默认设置”一次性收掉。它的核心价值就一句话:把数据库从“能跑”变成“能放心跑”。无论你是用 rpm、apt 装的 MySQL,还是用二进制包解压部署的,只要 MySQL 版本带了这个脚本(5.7 到 8.x 都有),都应该在正式使用前执行一遍。下面我按实际执行时会遇到的每一项交互,逐条说明怎么选、为什么。
1. 新装 MySQL 的第一道坎:默认状态有多“裸”
先说个最典型的场景。你刚在服务器上装完 MySQL,systemctl start mysqld一看服务正常,mysql -uroot -p也能进去,好像一切搞定。但这时候如果你的服务器有公网 IP,或者在内网里安全域划分不够细,MySQL 的默认状态就是门户大开。默认安装的 root 账号通常允许从任意主机连接(取决于安装方式,有些甚至还带着空密码),默认还附带一个任何人都能访问的 test 库,匿名用户在某些发行版里也是存在的。
我见过不少新手直接把阿里云或腾讯云的 MySQL 端口 3306 暴露在公网,然后用 root 弱密码(甚至空密码)跑业务。这类实例被自动扫描工具盯上只是时间问题,轻则数据被删勒索,重则直接被加密、被植入后门。mysql_secure_installation就是为这个场景准备的,它本质上是一个交互式加固向导,逐项帮你处理掉高风险配置。
那它具体做了什么?我梳理了一下,主要覆盖五个方面:
- 给 root 设置密码,或重新校验已有 root 密码强度。
- 删除默认的匿名用户。
- 限制 root 只能从 localhost 登录(禁止远程 root)。
- 删除默认的 test 数据库及对它的访问权限。
- 刷新权限表,让上述变更立即生效。
这五条听起来简单,但每一条背后都有真实的安全事故做注脚。匿名用户如果不清掉,同网段的任何人不需要密码就能以匿名身份连上来;root 可以远程登录的话,暴力破解工具会优先拿 root 当目标;test 库本身虽然无害,但它默认对所有人开放,等于多了一个可探测的入口。所以这脚本不是走过场,是真能挡掉相当一部分低级攻击。
执行方式也很简单,在 shell 里敲:
mysql_secure_installation如果你用的是 MariaDB,命令也是一样的,但交互项措辞略有差异。接下来它会用一问一答的方式带着你走完全部流程,下面我按实际执行顺序把每一项拆开讲。
2. 交互项逐条拆解:每一步背后都有讲究
脚本在不同版本里问的问题顺序和措辞会有一点点差别,但核心逻辑稳定。我就按 MySQL 8.0 系列最常见的交互顺序来说。
2.1 密码校验策略:VALIDATE PASSWORD COMPONENT
这一步问的是:Press y|Y for Yes, any other key for No:,问你是否要安装密码校验插件。安装后,它会强制 root 口令满足一定的复杂度要求。MySQL 8.0 里这个组件叫validate_password,策略分三档:
- LOW:只检查长度,默认至少 8 位。
- MEDIUM:长度 + 数字 + 大小写 + 特殊字符。
- STRONG:在 MEDIUM 基础上,还要检查包含至少一个字典单词或自定义单词列表。
我建议直接选是,并且策略选 MEDIUM 或 STRONG。尤其是生产环境,root 口令强度就是数据库的第一道门锁。开发环境如果你嫌烦,至少要选 LOW,长度下限别低于 8 位。
这里有个实际经验:如果你是在已有实例上执行这个流程,当前 root 密码本身不符合新策略的话,脚本会要求你先改掉旧密码再继续。这种情况下先准备一个满足策略的新密码,免得在中途被卡住。
2.2 设置 root 密码
接着它会要求输入 root 新密码并确认。如果你已经有密码,这一步相当于重置。注意这里它校验的不只是“你记不记得住”,而是密码复杂度。实测中很多人栽在这个环节——密码里带@、#这类字符时,在 shell 脚本交互式输入没问题,但如果之后要在命令行里拼mysql -p'密码',转义问题会害你折腾半天。我建议密码里尽量避免单引号和空格,其他特殊字符可以保留。
这个环节没有捷径,但在自动化部署时你可以不手动交互,而是用以下方式设置:
mysqladmin -u root password '新密码'或者在进入 MySQL 后用 ALTER USER 来改:
ALTER USER 'root'@'localhost' IDENTIFIED BY '新密码';2.3 删除匿名用户
脚本接着问:Remove anonymous users?(是否删除匿名用户)。这个必须选 Y。匿名用户意味着任何能连到 3306 端口的人,都可以在不知道账号密码的情况下先建立连接,然后再尝试提权或探测库表。虽然 MySQL 的匿名用户通常权限很低,但它属于一个不必要的攻击面。
有的发行版(比如 Debian 系的 MySQL 包)默认就不创建匿名用户,所以这步可能不会出现。如果出现,说明你的初始安装包含了匿名账户,选 Y 删除就是了。
2.4 禁止 root 远程登录
Disallow root login remotely?这一项是争议比较多的。它做的事情是移除'root'@'%'这类允许远程连接的账号,只保留'root'@'localhost'。
生产环境我强烈建议选 Y。root 只用于本机管理,业务连接全部走独立账号。这样即使应用侧密码泄露,也只是业务库的权限,不是整个实例的控制权。开发环境如果确实有远程管理需求,也应该创建专用管理账号,而不是放行 root 远程。
如果因为历史原因需要远程 root(比如数据库在 Docker 里,宿主机要连进去管理),正确的做法不是在这步选 N,而是通过配置项和账号白名单限制来源 IP,并且开启 SSL 连接。这个后面单独说。
2.5 删除 test 库和测试相关的访问权限
Remove test database and access to it?建议选 Y。默认的 test 库存在两个问题:一是它对所有用户开放(包括匿名用户),二是它常用于攻击者存放临时表。删掉它不会影响任何正常业务,需要测试环境自己单独建库即可。
我见过有些老项目把临时数据直接丢在 test 库里的,跑这个脚本之前最好检查一下SHOW DATABASES LIKE 'test%';是否有遗留数据,有就先备份再删。
2.6 刷新权限表
最后一步问Reload privilege tables now?,选 Y。它会执行FLUSH PRIVILEGES,让前面的权限变更立即生效。这个操作在 MySQL 里不算重,但对一致性很重要——不刷新的话,某些权限修改在部分场景下要到下一次连接或重启才生效,排查问题时容易产生“明明改了怎么没用”的错觉。
刷完之后脚本会显示一条成功提示并退出。到这里,一个“干净”的初始状态就建立好了。
3. 执行过程中的差异与意外情况
上面讲的是理想流程。实际执行中,你会发现不同版本、不同安装方式之间差异不小,我把自己遇到过的几种情况列出来。
3.1 找不到命令或提示命令不存在
用二进制包解压部署 MySQL 时,mysql_secure_installation通常位于 MySQL 安装目录的bin下,且不会自动加入 PATH。你需要全路径执行:
/usr/local/mysql/bin/mysql_secure_installation或者先export PATH=/usr/local/mysql/bin:$PATH再执行。用 rpm 或 apt 安装的一般已经加入 PATH,直接执行即可。
3.2 Socket 连接失败:Can’t connect to local MySQL server through socket
脚本默认通过 Unix socket 连接本机的 MySQL。如果你在容器里跑,或者 MySQL 配置了 socket 路径,可能出现连接失败。解决方式有几种:
- 确认 MySQL 服务已经在运行:
systemctl status mysqld或service mysql status。 - 在脚本执行时指定 socket 路径。MySQL 8.0 的
mysql_secure_installation支持通过环境变量或配置文件方式指定,更简单的办法是先在/etc/my.cnf里把 socket 路径配置好,让脚本读到。 - 个别情况下,脚本还会读取
~/.my.cnf里的客户端配置,如果你之前创建过这个文件,里面指向了错误的 socket 路径,也会造成连不上。
3.3 root 密码策略设置只有 LOW 和 MEDIUM,没有 STRONG
MySQL 8.0 里,如果你没安装完整的 validate_password 组件,脚本可能只提供 LOW 和 MEDIUM。实际上完整的组件安装后 STRONG 档位才可用。安装方式是:
INSTALL COMPONENT 'file://component_validate_password';然后SHOW VARIABLES LIKE 'validate_password.policy';查看当前策略。如果想调到 STRONG,需要同时设置validate_password.dictionary_file(字典文件),不然设置会报错。
3.4 已有业务数据时执行脚本要非常小心
mysql_secure_installation不是只能在新装环境跑,已有实例也可以跑,但必须评估影响。最典型的情况是:你有一个老项目,连接数据库用的就是 root 账号,且项目配置里写的是远程连接地址。这时如果脚本把 root 的远程登录禁用掉,业务就断了。
我的建议是,对已有实例执行前先做三件事:
- 打开
general_log或查询performance_schema里的连接记录,确认当前有哪些账号在连、从哪里连。 - 把业务账号建好并授权,确保不依赖 root。
- 在低峰期操作,并且先在测试环境完整演练一遍。
3.5 自动化执行:echo 管道与 expect
生产环境中有大量实例需要统一加固时,手动一个个跑显然不现实。这里提供两种自动化方案,但注意密码会暴露在进程参数或脚本文件中,须做好权限管理。
第一种,用echo管道传入回答:
mysql_secure_installation <<EOF y 2 新密码 新密码 y n y y EOF顺序对应:是否启用密码组件、选择策略级别、输入 root 密码、确认密码、是否删除匿名用户、是否禁用 root 远程(n 表示保留)、是否删除 test 库、是否刷新权限表。
第二种,用 expect:
#!/usr/bin/expect spawn mysql_secure_installation expect "VALIDATE PASSWORD" send "y\r" expect "policy" send "2\r" expect "password" send "新密码\r" send "新密码\r" expect "Remove anonymous users" send "y\r" ...这两种方式我都用过,expect更稳,但需要安装额外依赖。echo管道在密码含特殊字符时会踩坑,建议密码先临时改成简单字母数字组合再执行,执行后再改成正式密码。
4. 脚本之外:N 个容易被忽略的加固动作
mysql_secure_installation处理的是基础项,但它管不到的地方其实更多。我自己给客户做数据库巡检时,发现很多实例虽然跑了安全脚本,防护状态仍然存在明显短板。以下几个是我认为最值得补的。
4.1 监听地址:别让 MySQL 暴露在所有网卡上
默认配置下,MySQL 可能监听0.0.0.0:3306。安全脚本不会管这件事。正确做法是在/etc/my.cnf里设置:
[mysqld] bind-address=127.0.0.1如果业务需要远程连接,绑定内网 IP 或专网 IP,不要把 3306 直接暴露到公网。Docker 环境里,则通过端口映射控制暴露范围,一般只映射到宿主机回环地址,再由反向代理转发。
4.2 独立业务账号和最小权限
root 只保留本机管理用途,业务账号按库授权。比如业务只需要读写一个库,就只授予SELECT, INSERT, UPDATE, DELETE,不要给ALL PRIVILEGES,更不能给GRANT OPTION。这样即使数据库账号被注入或泄露,攻击面也被限制在一个数据库内。
4.3 开启连接加密
MySQL 8.0 默认自带 SSL,但要确认是否真的生效。用管理员账号执行:
SHOW VARIABLES LIKE 'have_ssl'; SHOW VARIABLES LIKE 'ssl_ca';如果有输出,说明 SSL 已启用。为了让各客户端默认走加密连接,可以在配置里调整相关选项,同时创建用户时用REQUIRE SSL限制指定账号必须使用加密连接。这个对于防止链路嗅探、账号密码被截获很有意义。
4.4 审计与日志
MySQL 8.0 有自己的审计插件(MySQL Enterprise Audit),社区版则可通过general_log做临时排查,或借助第三方工具做 SQL 审计。实际运维中,我至少建议开启以下日志:
log_error:错误日志,默认开启,用于故障排查。log_bin:二进制日志,既用于主从复制,也用于误操作后的时间点恢复。slow_query_log:慢查询日志,配合长查询阈值调优,日常性能排查必备。
安全脚本不会帮你开这些,但它们对“事故后恢复”和“入侵后溯源”的作用极大。等真出事了你再想开,可能已经晚了。
4.5 备份与恢复演练
这一点和安全脚本没有直接关系,但不能不提。任何安全加固都无法保证 100% 防住攻击,而备份是最后一道防线。我见过太多公司备份了但从没恢复过,等到需要恢复时发现备份文件已损坏。建议定期做一次完整的mysqldump或物理备份,并在测试环境演练恢复流程。
5. 最后分享几条实战体会
写到这里,把几个和这个脚本相关的经验再沉淀一下。
第一,不要让mysql_secure_installation变成一次性的“仪式”。很多团队装完 MySQL 跑一遍就彻底忘了它的存在,但等 MySQL 大版本升级、迁移、或者从开发库转生产库时,新环境的安全基线可能又回到了默认状态。我把这个脚本当作环境验收清单的一部分,每次部署都要在文档里打勾确认。
第二,密码策略设得太强也未必是好事。STRONG 策略在 MEDIUM 基础上增加了字典检查,人难记、自动化脚本也容易踩坑。我一般对 root 用 STRONG,业务账号用 MEDIUM,管理账号用 STRONG。密码尽量放在密钥管理服务里,不要明文写在配置文件或代码仓库中。
第三,执行脚本时如果提示当前密码不符合策略,先别急着把策略调低。检查是不是只设置了validate_password.length,而没处理其他变量。合理做法是完整安装 validate_password 组件后再调策略档位,避免后续出现配置不一致的情况。
第四,Docker 环境下执行这个脚本有个小坑:容器里的 MySQL 通常不装 expect 一类的交互工具,直接用docker exec -it mysql mysql_secure_installation会因为没有 TTY 而报错。可以先docker exec -it mysql bash进入容器,再执行脚本。如果镜像做了精简,可能连脚本都没有,这时就得手动执行对应的 SQL 语句,比如:
DELETE FROM mysql.user WHERE User=''; DROP DATABASE IF EXISTS test; DELETE FROM mysql.db WHERE Db='test' OR Db='test\\_%'; ALTER USER 'root'@'localhost' IDENTIFIED BY '新密码'; FLUSH PRIVILEGES;这一套 SQL 其实和脚本干的事一模一样。理解了脚本的底层逻辑,手动加固也完全不困难。
第五,也是我最想强调的一点:别指望一个脚本解决所有安全问题。它更像是给你清理了房间里的明面垃圾,但防火墙、账号权限、加密、备份这些“结构性安全”还得靠日常运维投入。把安全脚本跑完只是一个起点。