运维知识
悠悠
2026年8月6日

上线即裸奔?生产服务器+数据库安全加固全流程,我整理了一份可粘贴的实操手册

凌晨 1 点,群里炸了——"线上服务器被挖矿了,CPU 跑满!"

我冲过去一看,SSH 22 端口对公网全开着,root 账户密码是 123456,MySQL root 也没设密码,3306 端口也裸奔在公网上。这哪是服务器,分明是给黑客送自助餐。

这事发生之后,我花了整整一周,把新上线的所有机器从系统层到数据库层全部做了一遍安全加固。后来又过了一年多,经历过几次合规检查,没出过岔子。今天我就把这一整套流程完整地写出来,你只要照着复制粘贴,新机器上线就能用。

为什么必须在加固前就想清楚

很多人以为安全加固就是改个密码、关几个端口。我以前也这么想,直到被现实狠狠教育过。

之前有个项目,赶着上线,运维同学只改了 root 密码就发布了。结果一个月后,业务方反馈:有个用户信息表的数据每天凌晨 3 点会少几条。查了半天才发现,有个离职开发同学的账号还留在数据库里,权限是 ALL PRIVILEGES,他写的定时任务每天在偷偷删数据。

从那以后我才明白,安全加固不是"加几个配置项"的事,而是一套从账号体系、登录方式、权限分配到操作留痕的完整体系。今天聊的就是这套体系。

服务器层的加固,别上来就装软件

服务器加固的第一步,不是装杀毒软件,也不是装 auditd,而是从最基础的"门"开始——SSH、账号、防火墙。

SSH 这道门,要改 3 个地方

很多人 SSH 加固就是改个端口,22 改成 22222 就觉得万事大吉。其实远远不够。

首先是禁止 root 直接登录。你能想到的最危险的事就是让 root 能直接 SSH,因为一旦 root 密码泄露,黑客直接就是最高权限。改的方式很简单,编辑 /etc/ssh/sshd_config

PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes

这三行一起改,意思是 root 不能 SSH 登录、不能用密码登录、只能用密钥登录。改之前一定要先把自己的公钥放进 ~/.ssh/authorized_keys,不然改完你自己也进不去了,我亲眼见过有人改完被锁在门外两小时的惨剧。

然后是改端口。改端口不是为了防住真正的黑客,只是为了减少 SSH 暴力破解的扫描量,把 22 改成 5 位数的随机端口就行。

Port 58231

最后是限制允许登录的用户。新建一个普通用户,给 sudo 权限,平时用普通用户登录,需要 root 操作时再 sudo。

useradd deploy
echo "deploy ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers

改完之后 systemctl restart sshd,注意是 restart 不是 reload,因为我之前 reload 过几次发现某些参数不生效。

防火墙,不是装上就能用

很多公司装个 iptables 或者 firewalld 就觉得有防火墙了,但默认策略是 ACCEPT,相当于没装。

CentOS 7 用 firewalld 的话,先看下当前 zone:

firewall-cmd --get-default-zone
firewall-cmd --zone=public --list-all

默认 public 区域是放行所有出站、拒绝入站,SSH 默认放行。这种配置下,如果你装了 MySQL、Redis 这种服务,又没改默认配置,那 3306、6379 端口还是会暴露在公网上。

正确的做法是只放行业务需要的端口,其他全拒绝:

# 先放行需要的端口
firewall-cmd --permanent --add-port=58231/tcp   # SSH 改后的端口
firewall-cmd --permanent --add-service=http
firewall-cmd --permanent --add-service=https

# 重载
firewall-cmd --reload

# 确认下
firewall-cmd --zone=public --list-all

如果你用的是 iptables,思路也是一样——默认策略改 DROP,只放行白名单。这里有个坑,远程操作 iptables 之前一定要先放行 SSH 端口再加默认策略,否则你自己也会被踢出来,建议在控制台或带外管理(IPMI)操作。

账号密码策略,这 3 个文件要改

Linux 自带的密码策略很多人不会去碰,但安全合规检查一定会查 /etc/login.defs 和 PAM 配置。

/etc/login.defs 里重点改这几行:

PASS_MAX_DAYS   90      # 密码最长 90 天
PASS_MIN_DAYS   1       # 至少用 1 天才能改
PASS_MIN_LEN    12      # 最小长度 12 位
PASS_WARN_AGE   7       # 过期前 7 天提醒

然后装一下 libpam-pwquality(Debian/Ubuntu)或 libpwquality(CentOS)加强密码复杂度:

# CentOS
yum install -y libpwquality
# Ubuntu
apt install -y libpam-pwquality

编辑 /etc/pam.d/system-auth/etc/security/pwquality.conf

minlen = 12
dcredit = -1   # 至少 1 个数字
ucredit = -1   # 至少 1 个大写
lcredit = -1   # 至少 1 个小写
ocredit = -1   # 至少 1 个特殊字符

还有一件事——禁用不用的系统账号。比如 admlpsyncshutdown 这些系统账号,把它们的 shell 改成 /sbin/nologin

usermod -s /sbin/nologin adm
usermod -s /sbin/nologin lp
usermod -s /sbin/nologin sync

我之前见过一台机器被人用 sync 账号登录,就是因为这个账号的 shell 是 /bin/bash,虽然没密码但配合其他漏洞就能搞事情。

系统审计要打开,不是装上 auditd 就完事

光改账号、防火墙还不够,你得知道系统上发生过什么。Linux 自带的 auditd 就是干这个的,但默认可能没启动。

auditd 的核心配置

装好 auditd 之后(CentOS 用 yum install audit,Ubuntu 用 apt install auditd),编辑 /etc/audit/auditd.conf

max_log_file = 50         # 单个日志最大 50MB
max_log_file_action = rotate
num_logs = 10             # 保留 10 个日志文件
space_left = 20           # 磁盘剩余 20% 警告
space_left_action = email
action_mail_acct = root

这里有个重点——审计日志的存放路径。我建议单独挂一块磁盘或者目录给审计日志,权限设为 0600,属主 root:

mkdir -p /var/log/audit
chmod 0700 /var/log/audit

不然审计日志和系统日志混在一起,攻击者清理痕迹时把审计日志一起清掉就白干了。

关键审计规则,这是真正有用的部分

auditd 装好之后如果不加规则,等于没装。下面这套规则是我常用的,覆盖了最常见的安全事件:

# /etc/audit/rules.d/audit.rules

# 1. 监控账号和密码文件变化
-w /etc/passwd -p wa -k identity
-w /etc/shadow -p wa -k identity
-w /etc/group -p wa -k identity
-w /etc/sudoers -p wa -k identity
-w /etc/ssh/sshd_config -p wa -k sshd_config

# 2. 监控 SSH 配置和密钥
-w /etc/ssh/sshd_config -p wa -k sshd_config
-w /etc/ssh/ -p wa -k ssh_keys

# 3. 监控关键系统命令
-w /usr/bin/passwd -p x -k passwd_changes
-w /usr/sbin/useradd -p x -k user_creation
-w /usr/sbin/userdel -p x -k user_deletion
-w /usr/sbin/usermod -p x -k user_modification
-w /usr/bin/sudo -p x -k sudo_usage

# 4. 监控网络配置变化
-w /etc/issue -p wa -k system_locale
-w /etc/issue.net -p wa -k system_locale
-w /etc/hosts -p wa -k system_locale
-w /etc/sysconfig/network -p wa -k system_locale

# 5. 监控定时任务
-w /etc/crontab -p wa -k cron
-w /var/spool/cron -p wa -k cron

# 6. 监控登录登出
-w /var/log/lastlog -p wa -k logins
-w /var/log/tmplog -p wa -k logins
-w /var/log/faillog -p wa -k logins
-w /var/log/secure -p wa -k auth_log

改完之后重启 auditd:

service auditd restart
# 或者
systemctl restart auditd

查看审计日志用 ausearch

# 查看所有 sudo 操作
ausearch -k sudo_usage

# 查看指定用户的操作
ausearch -ua deploy

# 查看今天的所有事件
ausearch -ts today

有个坑——auditd 重启不要用 systemctl restart,要用 service auditd restart,否则会因为 auditd 守护进程特殊的设计出问题。这个细节我之前调了大半天才搞明白。

集中日志收集,别让日志只存在本地

单机审计有个致命问题:攻击者拿到 root 权限后,第一件事就是清日志。所以日志必须集中收集。

最常见的就是用 rsyslog 推到远程日志服务器。编辑 /etc/rsyslog.d/remote.conf

*.* @192.168.1.100:514

或者用更现代的方案——Filebeat + ELK:

# /etc/filebeat/filebeat.yml
filebeat.inputs:
- type: log
  paths:
    - /var/log/audit/audit.log
    - /var/log/secure
    - /var/log/messages
output.logstash:
  hosts: ["192.168.1.100:5044"]

这个不是强制的,但合规检查的时候问你"日志怎么集中管理的",能答得上来就稳了。

数据库加固才是重头戏

服务器层做好了,数据库才是真正值钱的地方。前面提到的那个被删数据的事故,就出在数据库权限上。

MySQL 账号体系,要分得足够细

MySQL 装好之后第一个动作就是改 root 密码、删匿名账号、删测试库。这三件事 90% 的人不会做。

-- 修改 root 密码(MySQL 5.7)
UPDATE mysql.user SET authentication_string=PASSWORD('YourStrong@Pass123') WHERE User='root';

-- MySQL 8.0
ALTER USER 'root'@'localhost' IDENTIFIED BY 'YourStrong@Pass123';

-- 删除匿名账号
DELETE FROM mysql.user WHERE User='';

-- 删除测试库
DROP DATABASE IF EXISTS test;
DELETE FROM mysql.db WHERE Db='test' OR Db='test\\_%';

-- 刷新权限
FLUSH PRIVILEGES;

然后是建业务账号,这是关键中的关键。不要所有应用都共用一个 root,也不要一个应用账号拥有所有库的所有权限。

原则是:账号-库-权限一一对应,最小权限。比如一个订单服务的账号:

-- 创建账号并限制来源 IP
CREATE USER 'order_app'@'10.0.%.%' IDENTIFIED BY 'Order@App#2024';
CREATE USER 'order_app'@'localhost' IDENTIFIED BY 'Order@App#2024';

-- 只给 order 库的全部权限(或者更细)
GRANT SELECT, INSERT, UPDATE, DELETE ON order_db.* TO 'order_app'@'10.0.%.%';
GRANT SELECT, INSERT, UPDATE, DELETE ON order_db.* TO 'order_app'@'localhost';

-- 单独建一个只读账号给数据分析师
CREATE USER 'report_user'@'10.0.%.%' IDENTIFIED BY 'Report@User#2024';
GRANT SELECT ON order_db.* TO 'report_user'@'10.0.%.%';

FLUSH PRIVILEGES;

注意 IDENTIFIED BY 后面这个密码,建议用密码管理工具生成,14 位以上,包含大小写数字特殊字符。不要用业务名+年份那种弱密码,见过太多 Order@2023Order@2024 这种。

还有一件容易忽略的事——MySQL 8.0 之后默认 caching_sha2_password 认证插件,有些老客户端(比如某些老版本的 Navicat)连不上,需要在 my.cnf 里改:

default_authentication_plugin=mysql_native_password

或者为特定账号指定认证插件:

ALTER USER 'order_app'@'10.0.%.%' IDENTIFIED WITH mysql_native_password BY 'Order@App#2024';

MySQL 网络访问,要绑死 IP

MySQL 默认监听 0.0.0.0,相当于对所有 IP 开放。生产环境必须改。

编辑 /etc/my.cnf(或 /etc/mysql/mysql.conf.d/mysqld.cnf):

[mysqld]
bind-address = 10.0.1.20   # 绑到内网 IP
skip-name-resolve           # 禁止 DNS 解析,加快连接速度

改完重启 MySQL,用 ss -tlnp | grep 3306 确认下是不是只监听 10.0.1.20 这个地址。

MySQL 开启审计日志,这才是能定责的关键

光控制权限还不够,你得知道每个账号到底执行了哪些 SQL。MySQL 社区版本身不带审计功能,这块要单独搞。

MySQL 5.7 可以用 MariaDB 的 server_audit 插件,我之前在 5.7 环境就是用这个[1]:

# 1. 找插件目录
mysql -e "show global variables like 'plugin_dir';"
# 假设输出是 /usr/lib64/mysql/plugin/

# 2. 从 MariaDB 安装包里提取插件(注意版本对应)
wget https://downloads.mariadb.com/MariaDB/mariadb-10.5.16/bintar-linux-x86_64/mariadb-10.5.16-linux-x86_64.tar.gz
tar -zxvf mariadb-10.5.16-linux-x86_64.tar.gz
cp mariadb-10.5.16-linux-x86_64/lib/plugin/server_audit.so /usr/lib64/mysql/plugin/
chmod +x /usr/lib64/mysql/plugin/server_audit.so

# 3. 创建审计日志目录
mkdir -p /opt/mysqldata/auditlogs
chown -R mysql:mysql /opt/mysqldata/auditlogs

# 4. 在 my.cnf 中配置

/etc/my.cnf 加上[1]:

[mysqld]
# 防止插件被卸载
server_audit=FORCE_PLUS_PERMANENT
# 记录连接、查询、表操作、DDL
server_audit_events='CONNECT,QUERY,TABLE,QUERY_DDL'
# 开启审计
server_audit_logging=on
# 日志路径
server_audit_file_path=/opt/mysqldata/auditlogs
# 单个日志 1GB
server_audit_file_rotate_size=1073741824
# 保留 15 个日志文件
server_audit_file_rotations=15
# 大 SQL 也要记全
max_allowed_packet=32M

重启 MySQL 后验证:

-- 查插件状态
SHOW PLUGINS WHERE NAME = 'server_audit';
-- 查配置
SHOW GLOBAL VARIABLES LIKE 'server_audit%';

如果用 MySQL 8.0 社区版,server_audit 插件兼容性有问题,建议改用 Percona Server 自带的 audit_log 插件2:

# 1. 下载 Percona Server 安装包,提取 audit_log.so
tar -xvf Percona-Server-8.0.32-24-Linux.x86_64.glibc2.17-minimal.tar.gz
cd Percona-Server-8.0.32-24-Linux.x86_64.glibc2.17-minimal/lib/plugin
cp audit_log.so /usr/local/mysql/lib/plugin/

# 2. my.cnf 配置
[mysqld]
plugin-load = audit_log.so
audit_log_file = /opt/mysqldata/auditlogs/audit.log
audit_log_format = CSV
audit_log_policy = QUERIES
audit_log_handler = FILE
audit_log_rotate_on_size = 1073741824
audit_log_rotations = 15

audit_log_policy 有 4 个值2:

  • ALL - 记录所有事件(最全但最耗性能)
  • LOGINS - 只记录登录
  • QUERIES - 只记录查询(推荐)
  • NONE - 关闭

生产环境建议先用 QUERIES,跑一周看看对性能影响,再决定要不要改成 ALL。如果只关心 DDL 操作(比如谁删了表、谁改了结构),可以用 audit_log_include_commands 限制。

审计日志的格式我截一段实际的[1]:

20250610 10:42:50,test_db01,root,192.168.83.36,7853786,438352985,QUERY,hxxxdb,'SELECT ... FROM user_bank_cards',0

从左到右分别是:时间、主机名、用户名、客户端 IP、线程 ID、查询 ID、事件类型、数据库名、SQL 内容、执行结果码。

这里有个性能问题要提醒——审计日志如果全量记录 SELECT,对 IO 压力很大。生产环境我一般用 Python 脚本做后处理,只把 DDL 和关键表的操作挑出来入库,原始日志保留定期归档。

那次删数据的事故,就是这样被查出来的

说回开头的故事——那个每天凌晨删数据的离职开发,最后是怎么找到的?

我当时也是刚给数据库开了审计,第二天用脚本解析审计日志,筛选出所有 DELETE 操作入库,结果发现每天凌晨 3 点都有一个叫 dev_zhang 的账号在执行 DELETE FROM user_info WHERE id < 10000

查了一圈,dev_zhang 是 3 个月前离职的一个开发,当时走流程的时候账号没清。找他谈的时候,他还一脸无辜说"我离职前写了个测试脚本调试用的,忘了删"。

最后虽然没追究,但这件事之后,公司的离职流程里多了一条——HR 提离职的同时,必须给运维发邮件,运维当天清账号。这套机制就是靠审计日志倒逼出来的流程改进。

避坑清单,这些事我全踩过

光给你步骤不说坑就是不负责任,下面这些坑我都亲身经历过。

1. 改了 SSH 把自己锁在外面

PermitRootLogin no 之前一定要先把自己的公钥配好,并且保留一个 root 的登录窗口测试。我见过最离谱的——有人改了 PermitRootLogin no 同时改了端口,然后重启 sshd,发现新端口连不上,原端口也不让 root 登,最后只能去机房接显示器。

2. 防火墙规则把业务端口也挡了

改默认策略为 DROP 之前,一定要先在规则里放行业务端口,不然业务一访问就报错。线上出过一次事故,有人改完 iptables 没保存直接 reboot,结果重启后规则没生效,业务全挂。

3. MySQL 改完 bind-address 启动失败

bind-address 配错格式(比如多了个空格)会导致 MySQL 启动失败,错误日志看不清楚。一定要在改之前备份配置文件,并且用 mysql --print-defaults 看下实际加载的配置。

4. 审计日志把磁盘写满

server_audit_file_rotations=0 表示不轮转,日志会无限增长,直到磁盘满。我见过一个 DBA 配错了参数,一周后审计日志把根分区写满,MySQL 直接挂了。

5. auditd 用 systemctl restart 起不来

这个前面说过,auditd 是特殊守护进程,要用 service auditd restart 或者 augenrules --load 重载规则。

6. 改了密码但忘了同步给应用

数据库改完密码,业务方没收到通知,结果业务全连不上。这种事在加班时特别容易发生,改密码之前一定要走变更流程,通知所有相关方。

收尾的 3 个建议

整套加固做下来,你会发现真正费时间的不是配置本身,而是流程和习惯。

第一个建议,加固做成自动化脚本。新机器上线自动跑一遍加固脚本,把 SSH 配置、防火墙规则、账号体系、审计日志全部配置好,机器一交付就是加固过的状态。我现在所有新机器都是用 Ansible playbook 做的,新机器 5 分钟交付,加固完直接可用。

第二个建议,日志集中存储+定期审计。审计日志本地存一份,远程日志服务器存一份,离线归档一份。至少每周过一次审计日志,重点看权限变更、敏感表操作、登录失败记录。

第三个建议,离职流程必须有账号清理环节。这个前面那个故事已经说明了,不细说了。

安全这件事说到底不是技术问题,是流程问题。你技术再牛,一个离职账号没清,三个月后就是定时炸弹。

好了,今天的实操就到这里。整套流程我整理了一份 checklist,包含 60 多条具体的配置项和命令,你想要的话评论区扣个"安全加固",我整理完发出来。

最后求个关注,公众号:耕云躬行录

个人博客:躬行笔记,每周更新运维实战、故障复盘、效率工具。觉得有用的话转发给身边做运维的朋友,少踩点坑少背点锅,我们下期见。


参考来源:

  • [1] 为MySQL社区版实现审计功能:从插件配置到日志监控全解析 - ownit.top
  • [2] mysql审计功能开启 - 腾讯云开发者社区
  • [4] MySQL 8 社区版安装Percona的审计插件 - 博客园

文章目录

博主介绍

热爱技术的云计算运维工程师,Python全栈工程师,分享开发经验与生活感悟。
欢迎关注我的微信公众号@运维躬行录,领取海量学习资料

微信二维码