云服务器安全基线:上线前必做的 9 项加固清单,从 SSH 密钥登录、安全组最小权限,到 MFA、补丁节奏与恢复演练,附可直接复制的命令。
一台云服务器被攻破的路径通常很朴素:扫到开放的 22 端口 → 爆破出弱口令或撞库成功 → 拿到 root → 植入计划任务、挖矿或横向移动。云服务器安全基线要做的,就是把这条链上的每一环都变贵:让扫到无意义、让爆破无入口、让 root 拿不到、让操作留得下痕迹。
判断标准不是「做没做」,而是「能不能被别人验证」。 每项加固都要配一条你自己能跑的验证命令——基线不是一份文档,而是每月能重新跑一遍的检查。
上线前 9 项加固总览(可直接当 checklist)
这 9 项拆开看,本质是三件事:减少入口(SSH、防火墙)、减少权限(非 root、MFA、最小授权)、保证出事能兜住(备份、日志、凭证轮换)。下表是最低标准,不是目标值——先做到,再谈更严格。需要托管侧能力(WAF、DDoS 高防、堡垒机)时,对照 云安全产品 补齐。
| # | 加固项 | 上线前最低标准 | 怎么验证 |
|---|---|---|---|
| 1 | SSH 登录 | 禁止 root 与密码登录,仅密钥 | sshd -T 中两项均为 no |
| 2 | 防火墙 / 安全组 | 管理端口只对来源 IP 开放 | 安全组无 0.0.0.0/0 的 22/3306/6379 |
| 3 | 账号与权限 | 一人一个 IAM 身份,不共享 root 密钥 | IAM 用户可逐人对应到人 |
| 4 | MFA | 主账号与高权限子账号强制开启 | 控制台显示已启用 |
| 5 | 补丁 | 系统每月,业务依赖每季度 | apt list --upgradable 无积压 |
| 6 | 备份与恢复 | 每日自动快照 + 季度恢复演练 | 从快照拉起新实例成功一次 |
| 7 | 加密 | 磁盘静态加密 + 全站 TLS 1.2+ | curl -I https:// 返回 200 |
| 8 | 日志与告警 | 留存 ≥ 90 天,新 SSH key 告警 | lastb -ai 有记录且可查 |
| 9 | 密钥管理 | 凭证不入库,托管并可轮换 | 仓库扫描无高熵串命中 |
顺序有讲究:先 SSH 密钥登录,再动防火墙。 反过来做(先关端口再推 key)会把自己锁在门外,只能走云厂商的救援模式,而那通常需要人工工单。
加固 1|SSH:关掉密码登录,只留密钥
暴力破解 SSH 至今仍是云主机被入侵的第一入口。第一项加固就是:让「密码」这条路径根本不存在。
- 给自己或
deploy用户生成新密钥:ssh-keygen -t ed25519 -C "you@laptop",再把公钥推上去:cat ~/.ssh/id_ed25519.pub | ssh deploy@SERVER "umask 077; mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys" - 在新开的终端里实测能登录,确认成功后再动配置。原来的会话先别关。
- 备份配置:
sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak。注意 sshd 多数配置项是首次出现生效,所以要改现有行,往文件末尾追加是无效的。 - 禁 root 登录:
sudo sed -i "s/^#*PermitRootLogin.*/PermitRootLogin no/" /etc/ssh/sshd_config - 禁密码登录:
sudo sed -i "s/^#*PasswordAuthentication.*/PasswordAuthentication no/" /etc/ssh/sshd_config。旧发行版里这一项叫ChallengeResponseAuthentication,同样要处理。 - 先验语法再重载:
sudo sshd -t && sudo systemctl reload ssh(RHEL 系服务名为sshd)。 - 确认生效值:
sudo sshd -T | grep -Ei "permitrootlogin|passwordauthentication",应输出PermitRootLogin no与PasswordAuthentication no。
再叠一层 fail2ban 限制尝试频率:sudo apt install -y fail2ban && sudo systemctl enable --now fail2ban,用 sudo fail2ban-client status sshd 看已封禁的 IP。
关于「把 22 改成 2222」:这不会让你更安全。公开扫描工具 1–65535 全扫,改端口只是让默认日志安静一点;真正的收益来自来源限制与密钥登录。
加固 2|防火墙与安全组:默认拒绝,按需开口
网络层的目标是默认拒绝。云厂商的安全组是第一道墙(流量到达主机前就被丢弃),主机内的 ufw / firewalld 是第二道墙。两道墙都留着,成本几乎为零。
- 安全组:只把 80/443 放给
0.0.0.0/0;22 只放给办公网出口 IP;3306 / 5432 / 6379 / 27017 / 9200 一律不给0.0.0.0/0 - Debian/Ubuntu 主机防火墙:
sudo ufw default deny incoming→sudo ufw allow 22/tcp→sudo ufw allow 80,443/tcp→sudo ufw enable - RHEL 系主机防火墙:
sudo firewall-cmd --permanent --add-service=http --add-service=https,再用--add-rich-rule只对指定来源放行 22,最后sudo firewall-cmd --reload - 数据库只监听回环或内网:MySQL 配
bind-address=127.0.0.1;Redis 配bind 127.0.0.1 -::1并保持protected-mode yes,远程访问走 VPC 私网 + 安全组
ufw enable 之前必须先 ufw allow 22/tcp(Ubuntu 上 ufw allow OpenSSH 也可以,它是同名别名规则)。顺序反了会立刻断开当前 SSH 会话。
自查两处:本机看监听端口 ss -tulpn | grep LISTEN,从另一台机器看对外暴露 nmap -Pn your.server.ip。两处结果都只应有你打算开放的端口。
加固 3|账号:一个人一个身份,不共享 root
把 root 当成「系统自己的账号」,人一律不直接用。目标状态是:每个人的操作都能追溯到一个具体身份,出事能单独吊销而不影响同事。
- 云控制台侧:新建 IAM 子账号(RAM 子用户同理),按最小权限给策略,只授予「只读监控」或「只能操作这台实例」这类范围;删掉主账号的长期 AccessKey,CI 与运维改用角色 + 临时凭证,有效期设 15–60 分钟
- 主机侧:建
deploy用户做日常运维,用sudo visudo只授权必要命令,而不是整条NOPASSWD: ALL - 一人一把 SSH 密钥,不要把同一个
id_rsa复制给全组。人员变动时删一行authorized_keys即可精确吊销 - 给私钥加口令并用 ssh-agent 缓存:
ssh-keygen -p -f ~/.ssh/id_ed25519+eval "$(ssh-agent -s)",兼顾安全与体验
精确授权的写法(追加到 /etc/sudoers.d/deploy):deploy ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart myapp.service。只放开真正要用的那一条命令,而不是整台机器的 root。
加固 4|MFA:给「删库」动作加一道真实校验
密钥和密码都可能被复制、被钓走,MFA 是唯一能推翻「凭证已泄露就直接可用」这个假设的机制。它不防入侵,防的是凭证已经泄露的情况。
- 主账号必须开 MFA,且不要只依赖短信:SIM 补卡、短信轰炸都是常见手法,优先 TOTP(Google Authenticator、1Password、Bitwarden 等)
- 按权限分级强制:能改安全组、能删资源的子账号必须开,纯只读账号可以暂不强制
- 恢复码离线保存:放密码管理器或打印密封,不放在同一台机器上,也不要发到工作群里
- 控制台登录与 SSH 登录是两件事:前者用控制台 MFA,后者需要在 PAM 里接
pam_google_authenticator,或用 sshd 的AuthenticationMethods组合策略
别把 MFA 做成「形同虚设」:团队里必须有人能走流程帮同事重置,而这个人自己也要开 MFA。否则一次误操作就会变成一场线上求助。
云侧可以用策略强制,而不是口头要求:在权限策略的 Condition 里加 "Bool": { "aws:MultiFactorAuthPresent": "true" },未开启 MFA 的调用直接被拒。
加固 5|补丁:把更新变成日程,而不是事故
加固不是一次性动作。原则很简单:能修的当天修,修不了的(要发版、要重启)排进固定窗口。
- 系统层:Debian/Ubuntu 用
sudo apt update && sudo apt list --upgradable查看,装unattended-upgrades并在/etc/apt/apt.conf.d/20auto-upgrades里设APT::Periodic::Update-Package-Lists "1";与APT::Periodic::Unattended-Upgrade "1"; - RHEL 系:手动用
dnf update -y,自动化交给dnf-automatic:sudo systemctl enable --now dnf-automatic.timer - 内核与应用依赖:内核升级要重启才生效(
uname -r确认版本,固定窗口执行sudo reboot);依赖单独扫:npm audit --production、pip-audit、govulncheck ./... - 容器镜像:定期
docker build --pull重建,基础镜像的漏洞不会因为你的业务代码没改就消失
发行版有生命周期上限:主流发行版的标准维护窗口通常约 5 年,之后不再有安全更新(CentOS 7 已于 2024 年 6 月结束,Ubuntu 20.04 LTS 标准维护已于 2025 年 5 月结束)。仍在跑的机器要么升版本,要么确认已接入商业延保。
想要一次性全量体检:sudo apt install -y lynis && sudo lynis audit system,把报告存档作为合规证据,也方便对比下一次的变化。
加固 6|备份:有快照不算数,恢复成功才算
备份的价值不在「有」,在「能恢复」。没演练过的备份只是一个假设。
- 自动快照:每日 1–2 次,保留 7–14 天;每次发版或改数据库结构前手动打一次快照并打标签注明变更
- 快照不等于应用一致性备份:进程写到一半被快照,恢复后 InnoDB 会自行回滚,但逻辑上可能丢事务。数据库额外做导出:
mysqldump --single-transaction --quick -u root -p dbname > /backup/db-$(date +%F).sql.gz(InnoDB 用--single-transaction代替--lock-tables) - 异地留一份:快照跨区域复制,或同步到对象存储,并且给备份本身加密——备份文件常含全量用户数据,是最容易被忽略的泄露源
- 恢复演练:每季度从快照拉一台新实例(隔离网络),核对数据条数与最新时间戳,记录真实 RTO,然后销毁测试实例
演练结果才写进 runbook。写「RTO 2 小时」不如写「2026 年 8 月实测:从快照到数据可读 38 分钟」——后者是数字,前者是承诺。
加固 7|加密:磁盘静态加密 + 全链路 TLS
加密的目标很具体:磁盘被人拿走、备份被下载、流量被中间人读取时,数据依然不可读。
- 静态加密:云硬盘与快照通常默认启用厂商侧静态加密(一般不额外计费);备份桶、对象存储、数据库快照建议启用客户管理密钥(CMK),这样人员离职时可以连密钥一起吊销。CMK 一般按密钥数量与调用次数计费,以云厂商官网实时报价为准
- 传输加密:全站 HTTPS,Nginx 只保留
ssl_protocols TLSv1.2 TLSv1.3;,80 全量跳 443 并配置 HSTS;证书用sudo certbot --nginx -d example.com自动续期 - 内部链路也加密:同 VPC 内多机互访走私网 IP,数据库连接强制 TLS 并校验证书,Redis / MySQL 端口不对外暴露
- SSH 细节:
ForwardAgent no,关闭不必要的端口转发;团队用 ssh-agent 统一管理私钥,而不是散落在多台机器上
传输加密不只影响前台 API。上传接口、webhook 回调、对象存储的预签名 URL、第三方 SDK 的证书校验开关都要确认没有被关掉——这类问题在测试环境常常是默认开启的。
证书过期引发的事故,根因通常不是加密机制有问题,而是没人管续期。把续期做成自动化,而不是靠人记得。
加固 8|日志与告警:留痕 90 天,新钥匙要报警
出事时的分水岭只有一个问题:三天前发生过什么,现在能不能查到。
- 系统日志留存:
/etc/logrotate.d/rsyslog里设rotate 14+daily,并用journalctl --disk-usage确认落盘量在可控范围内 - 开 auditd 记录敏感文件变更:
sudo systemctl enable --now auditd,规则写进/etc/audit/rules.d/audit.rules:
- 规则行:
-w /etc/passwd -p wa -k identity、-w /etc/sudoers -p wa -k sudo、-w /etc/ssh/sshd_config -p wa -k sshd_config - 加载生效:
sudo augenrules --load(RHEL 系还要sudo systemctl restart auditd) - 事后查询:
sudo ausearch -k identity -ts recent
- 云侧审计:开启对象存储访问日志与云厂商的操作审计服务(CloudTrail / 操作审计一类),日志桶用独立账号写入并开启保留策略
- 告警只留三条高信号:新增 SSH 公钥、失败登录突增(陌生 IP 段爆破)、非工作时间的管理操作
留存周期给个下限:在线 30–90 天,导出到对象存储再存一年。登录记录用 last -ai(成功)与 lastb -ai(失败)快速排查。
fail2ban 的默认阈值容易在网络抖动时误封整段办公室出口 IP。配置后先用 sudo fail2ban-client status sshd 观察一周,再收紧封禁时长。
加固 9|密钥管理:任何凭证都不进代码仓库
凭证泄露最常见的路径不是被黑客攻破,而是被提交进 git、被写进镜像层、被贴进工单和聊天记录。
- 代码里不写密钥:
.env进.gitignore;已经提交过就按已泄露处理——git rm --cached .env只能停止后续跟踪,历史里的必须用git filter-repo改写,并且同时轮换那把凭证 - 提交前自动扫描:
gitleaks detect或trufflehog git .,挂到 pre-commit 或 CI 上 - 运行时注入:由 CI/CD 注入环境变量,或放云厂商的密钥管理服务(Secrets Manager / KMS 一类),应用启动时拉取并缓存;不要写进 Dockerfile 的
ENV,那等于打进镜像 - 文件权限:
chmod 600 .env,用umask 077创建敏感文件,应用以独立系统用户运行,必要时chown root:appgroup后chmod 640
命令行参数会出现在 ps 输出里:mysql -uroot -pPASSWORD 这种写法,同机器任何用户 ps aux 就能看到密码。改用交互提示符,或 mysql --defaults-extra-file=/root/.my.cnf。同理检查 shell history 里有没有 export SECRET=...。
轮换要有节奏:数据库密码 ALTER USER 与应用同步更新,第三方 API key 按季度轮换,并给每把凭证登记用途、负责人、创建时间与失效日期——没有到期日的密钥,迟早会变成永久密钥。
密钥泄露了怎么办:15 分钟应急清单
假设一把 SSH 私钥或一个云 AccessKey 泄露了。第一个动作是吊销,不是改密码,也不是先观察两天。
- 吊销(0–5 分钟):在 IAM 控制台禁用或删除泄露的 AccessKey;SSH 侧从所有服务器的
authorized_keys移除对应公钥(先确认你还有别的入口,否则会把自己关在门外) - 留证(5–15 分钟):立刻给受影响实例打快照并导出证据——
sudo last -ai、lastb -ai、sudo journalctl -u ssh --since "-24h"、当前ss -tulpn结果、计划任务与自定义 systemd 单元(/etc/cron*、/etc/systemd/system/) - 排查(15–60 分钟):新增系统用户、
authorized_keys差异、crontab -l、异常监听端口、异常出站连接(ss -tunp)、可疑的公钥注释名 - 轮换(同步进行):从这台机器能触达的一切都要换——数据库密码、对象存储凭证、第三方 API key、CI/CD 里的 secrets、镜像仓库 token。泄露一把通常意味着整套凭证都该换
- 重建而不是清理:一旦确认已获得 root,在原机上清理是不够的,持久化可能藏在没查到的地方。从可信镜像或干净快照重建,只迁移经过检查的数据
- 复盘:写下时间线(何时发现、哪些凭证受影响、是否被使用过),按影响面通知相关方,并把这次发现变成一条告警规则
如果泄露的凭证在被吊销前被实际使用过,就要按数据泄露事件处理:评估哪些数据被访问、是否需要对外通报,而不是只当成一次配置错误关掉工单。
怎么验证:15 分钟自检清单
基线应该是可以在 15 分钟内验证的东西。换个终端,按顺序跑下面这些:
- SSH 生效配置:
sudo sshd -T | grep -Ei "permitrootlogin|passwordauthentication",期望看到两项均为no - 对外暴露面(从另一台机器跑):
nmap -Pn your.server.ip,只应出现 22/80/443 及你有意开放的端口 - 本机监听:
ss -tulpn | grep LISTEN,数据库与缓存的监听地址应为127.0.0.1或内网地址 - 防火墙规则:
sudo ufw status verbose(或sudo firewall-cmd --list-all),默认策略应为拒绝或仅白名单 - 待更新包:
sudo apt list --upgradable(Debian 系)/dnf check-update(RHEL 系) - 失败登录:
lastb -ai | head -20,出现陌生 IP 段就继续追 - SUID 文件与计划任务:
sudo find / -xdev -perm -4000 -type f 2>/dev/null、crontab -l、ls -la /etc/cron.d/ - 仓库密钥扫描:
git ls-files | xargs grep -nEi "akia|secret|password",有命中就按已泄露处理
把这 8 条写进每月一次的例行检查,或者做成 CI 里的基线任务。能被自动化的加固才叫基线,只做一次的只是改过一次配置。
下一步:挑一台已经上线的机器跑完这 8 条,把命中的项分成「5 分钟能改完」和「需要排期」两栏——前一栏今天清掉,后一栏进发版窗口,写清负责人和日期。账号侧的另一半风险(来源不明的账号、共用支付方式、被牵连封禁)不在这 9 项里,单独见 云账号被封的 5 类原因与预防清单。
本文给的是跨厂商通用的最小集。具体到你用的云厂商,安全组默认值说明与最佳实践文档可能更严格(例如强制 IMDSv2、默认拒绝入站),以官方文档实时内容为准。