Patet

运维安全

一台刚开机的 VPS 几分钟内就会被全网扫描——本篇用几条具体命令,帮你挡掉约 99% 的自动化攻击。

把一台全新的服务器接到公网上,你以为没人知道它的存在?现实是:几分钟之内,自动化扫描脚本就会找上门。它们日夜不停地遍历整个 IPv4 地址段,挨个试 SSH 弱口令、扫已知漏洞端口。你不用做什么惊天动地的加固,只要做对下面几件事,就能挡掉约 99% 的自动化攻击——剩下的针对性攻击,普通项目几乎遇不到。

下面每一节都给你可以直接复制粘贴的命令(以 Ubuntu / Debian 为例)。建议照着顺序一路做下来。

SSH 加固

SSH 是攻击者最爱的入口,也是你最该先锁好的门。核心思路只有两点:别用 root 直接登录别用密码登录(改用密钥)。

创建一个有 sudo 权限的非 root 用户

平时用 root 干活风险太大——任何一个手滑都可能毁掉整台机器。先建一个日常用的普通用户,需要管理员权限时用 sudo 临时提权。

# 以 root 登录后执行(把 deploy 换成你想要的用户名)
adduser deploy
usermod -aG sudo deploy

adduser 会让你设个密码并填几项信息(直接回车跳过即可)。usermod -aG sudo 把这个用户加进 sudo 组,让它能提权。

把你的 SSH 公钥拷到新用户

密钥登录比密码安全得多,也方便。在你自己的电脑上(不是服务器上)操作。

如果你本地还没有密钥,先生成一对:

# 在你自己的电脑上
ssh-keygen -t ed25519 -C "your_email@example.com"

然后把公钥拷到服务器。最省事的是 ssh-copy-id

# 在你自己的电脑上
ssh-copy-id deploy@你的服务器IP

如果没有 ssh-copy-id,就手动把公钥内容追加到服务器的 ~/.ssh/authorized_keys

# 在服务器上,以 deploy 用户执行
mkdir -p ~/.ssh && chmod 700 ~/.ssh
# 把你本地 ~/.ssh/id_ed25519.pub 的内容粘进下面这个文件
nano ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys

配好后,先在新终端里验证一下密钥能正常登录:ssh deploy@你的服务器IP,能进去再往下走。

关闭密码登录和 root 登录

密钥能登了,就把密码登录和 root 直接登录都关掉。编辑 /etc/ssh/sshd_config

sudo nano /etc/ssh/sshd_config

确保下面这几行存在且取值正确(有的被注释了,去掉前面的 # 并改对):

PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes

保存后重启 SSH 服务让配置生效:

sudo systemctl restart ssh

(部分系统服务名是 sshd,若上面命令报错,改用 sudo systemctl restart sshd。)

改完别急着关掉当前窗口

重启 SSH 后,保持当前这个已登录的会话开着不动,另开一个新终端测试 ssh deploy@你的服务器IP 能不能登进去。万一配置写错把自己锁在门外,你还能用原来的会话改回来。确认新终端能正常登录,再关旧窗口。

防火墙(ufw)

默认情况下服务器可能开着一堆你根本没在用的端口。用 ufw(Uncomplicated Firewall)只放行真正需要的:SSH、HTTP、HTTPS,其余一律拒绝。

# 放行 SSH(用 OpenSSH 这个预设,免得把自己锁外面)
sudo ufw allow OpenSSH

# 放行网站的 HTTP 和 HTTPS
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp

# 启用防火墙(会提示可能中断 SSH,输 y 确认;上面已放行 OpenSSH,安全)
sudo ufw enable

# 查看当前规则
sudo ufw status verbose

allow OpenSSH,再 ufw enable,顺序别搞反。如果你改过 SSH 端口(比如改成 2222),要放行的是那个新端口(sudo ufw allow 2222/tcp),否则一启用防火墙就把自己关在外面了。

自动安全更新

补丁要打得勤,但你不可能天天手动盯着。unattended-upgrades 能让系统自动安装安全更新。

sudo apt update
sudo apt install -y unattended-upgrades
sudo dpkg-reconfigure --priority=low unattended-upgrades

最后那条命令会弹出一个对话框,选 Yes 启用自动安全更新即可。装好后,安全补丁会在后台自动安装,你不用再操心大多数已知漏洞。

fail2ban

即使关了密码登录,攻击者还是会不停地试探 SSH。fail2ban 会盯着日志,发现某个 IP 反复登录失败,就自动把它封禁一段时间,让暴力破解彻底没戏。

sudo apt install -y fail2ban
sudo systemctl enable --now fail2ban

# 查看被封的 IP 和 SSH 防护状态
sudo fail2ban-client status sshd

它装好后对 SSH 默认就生效,几乎零配置。想调封禁时长、失败次数阈值,可以编辑 /etc/fail2ban/jail.local

最小权限

一个朴素但极其重要的原则:任何东西都只给它干活所需的最小权限

  • 别用 root 跑你的应用。 给应用建一个专用的非 root 用户,万一应用被攻破,攻击者拿到的也只是这个受限用户,而不是整台机器的最高权限。
# 建一个不能登录、专门用来跑应用的系统用户
sudo adduser --system --group --no-create-home appuser
  • 文件权限收紧。 应用代码和数据目录归这个专用用户所有,别给所有人可写:
# 把应用目录的归属改成专用用户
sudo chown -R appuser:appuser /opt/yourapp
# 目录 755、文件 644,避免 777 这种全开放权限
sudo find /opt/yourapp -type d -exec chmod 755 {} \;
sudo find /opt/yourapp -type f -exec chmod 644 {} \;
  • systemd 跑服务时,在 unit 文件里指定 User=appuser,让进程以专用用户身份运行。现成的 systemd 模板见 模板与清单

密钥与环境变量

泄露密钥是新手最常见、后果最严重的事故之一。一旦数据库密码或 API key 进了公开仓库,几分钟内就可能被人扫到利用。

  • 永远不要把 .env 文件或密钥提交进 Git。 在仓库里建一个 .gitignore
# .gitignore
.env
.env.*
*.pem
*.key
id_rsa
  • 收紧密钥文件权限,只让属主能读:
chmod 600 .env
chmod 600 ~/.ssh/id_ed25519
  • 一旦密钥泄露,立刻轮换(rotate)。 别以为「从 Git 里删掉就行」——它早已存在历史记录和别人的克隆里。正确做法是:去对应平台作废旧密钥、生成新密钥,数据库密码也立即改掉。

密钥进过 Git 历史,就当它已经公开了

即使你后来删了文件、改了提交,密钥仍然躺在 Git 历史中,任何能拉到这个仓库的人都能翻出来。唯一可靠的补救是轮换密钥,不是删文件。生产密钥用平台的环境变量或密钥管理服务来配,别落地成文件提交。

数据库不要对公网开放

这是被攻破最多的一类配置错误:数据库直接监听 0.0.0.0,全网都能连。Postgres、MySQL、Redis、MongoDB 都中过招(甚至被勒索删库)。

  • 绑定到本机回环地址,只允许同一台服务器上的应用连接。

Postgres(postgresql.conf):

listen_addresses = 'localhost'

MySQL(my.cnf):

bind-address = 127.0.0.1

Redis(redis.conf):

bind 127.0.0.1
  • 用防火墙兜底:确认数据库端口(Postgres 5432、MySQL 3306、Redis 6379)没有ufw 放行到公网。前面我们只 allow 了 22/80/443,数据库端口就被默认拒掉了,正合适。
  • 永远用强密码,别用默认账号密码。Redis 这类默认没密码的,务必设上 requirepass
  • 如果应用和数据库在不同机器上,别图省事开公网,用内网 / VPC 私网,或加 TLS。

备份

服务器会挂、磁盘会坏、命令会手滑。备份不是可选项

  • 自动化地备份数据库和文件,别指望自己记得手动跑。用 cron 定时执行:
# 每天凌晨 3 点备份 Postgres 数据库(写进 crontab -e)
0 3 * * * pg_dump yourdb | gzip > /backups/yourdb-$(date +\%F).sql.gz
  • 存到异地(offsite)。 备份和服务器放一起,机器一没就全没了。把备份同步到对象存储(如 S3、Cloudflare R2、Backblaze B2):
# 例:用 rclone 把备份同步到对象存储
rclone copy /backups remote:my-backups
  • 给备份设保留策略(比如保留近 30 天),别让磁盘被旧备份撑爆。

没恢复演练过的备份,不算备份

备份脚本天天在跑 ≠ 备份真的能用。文件可能损坏、内容可能是空的、恢复步骤可能根本走不通——这些只有真正试着恢复一次才会暴露。定期挑一份备份,在另一台机器(或本地)上完整恢复并验证数据。能恢复出来的,才叫备份。

监控与告警

服务器出问题时,你希望是自己先收到通知,而不是用户来骂你才知道。

  • 可用性监控(uptime monitor):用 UptimeRobothealthchecks.io 定时探测你的站点,挂了立刻发邮件 / 推送告警。免费档通常就够用。
  • 错误日志:把应用错误收集起来(Sentry 之类),别让异常默默地烂在日志里没人看。
  • 磁盘 / CPU 告警:磁盘写满会让数据库直接罢工。设个阈值告警,或至少定期 df -h 看一眼。
  • 进程自动重启:应用崩了要能自己拉起来。用 systemdRestart=always)或 Node 项目常用的 PM2。systemd 服务模板见 模板与清单
# systemd unit 片段:进程退出后自动重启
[Service]
User=appuser
Restart=always
RestartSec=5

现成配置直接抄

上面提到的 sshd_configsystemd service 文件、ufw 规则、备份脚本等,都有可以直接复制改用的版本,见 模板与清单。配套阅读:发布到公网购买服务器接入 Cloudflare(用 Cloudflare 还能再加一层 DDoS 与 WAF 防护)。

本步骤检查清单

  • 已创建非 root sudo 用户,配好 SSH 密钥登录,并关闭密码登录与 root 登录
  • 已启用 ufw 防火墙,只放行 SSH / 80 / 443
  • 已装好 unattended-upgradesfail2ban
  • 应用以专用非 root 用户运行,文件权限已收紧
  • .env 和密钥已加进 .gitignore,权限设为 600,泄露密钥立即轮换
  • 数据库只监听 localhost,没有对公网开放,且用强密码
  • 备份已自动化、存到异地,并且真正恢复演练过一次
  • 已配好可用性监控、告警和进程自动重启

完整清单见 上线自查清单

本页目录