适用:有公网 IP 的服务器 耗时:约 40 分钟 前置:T3(SSH)、E4(网络排查)
公网服务器上线后,多久会被人扫?答案是:几分钟。
这一篇讲最有效的六件事。做完这六件,你的服务器就比 95% 的机器安全。
一、先看清现状:SSH 暴露在公网上
新装的服务器,root + 密码登录,22 端口对全世界开放。
这意味着:有人可以用自动化工具,每秒尝试几百个密码。
看看有多少人在试
sudo grep "Failed password" /var/log/auth.log | wc -l
sudo grep "Failed password" /var/log/auth.log | tail -5
sudo lastb | head -20
一台新服务器放公网一天,失败记录通常会有几千到几万条。
这不是"可能被攻击",是"正在被攻击"。
二、加固清单:六件事
| # | 做什么 | 挡住什么 |
|---|---|---|
| 1 | 禁用密码登录,只用密钥 | 暴力破解(最有效的一条) |
| 2 | 禁止 root 直接登录 | root 爆破 |
| 3 | 装 fail2ban | 反复尝试的 IP |
| 4 | 配防火墙,只开必要端口 | 意外暴露的服务 |
| 5 | 及时更新系统 | 已知漏洞 |
| 6 | 服务只绑必要地址 | 数据库等被直接访问 |
按顺序做,每做一条验证一条。
三、第 1+2 条:SSH 加固(最重要)
⚠️ 动手前必读
改 SSH 配置最常见的灾难:把自己锁在外面。
三条保命措施:
- 保持当前 SSH 会话不要关
- 另开一个终端测试新连接
- 新连接能进,再关旧会话
如果是云服务器,先确认你知道怎么用云控制台的 VNC 登录(那是最后的救命通道)。
步骤
① 先确认密钥登录已经能用了
# 在本地(不是服务器)
ssh-copy-id 用户@服务器IP
# 测试:应该不问密码就进去了
ssh 用户@服务器IP
这一步没成功,千万别往下做。
② 备份配置
sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak-$(date +%Y%m%d)
③ 修改配置
sudo nano /etc/ssh/sshd_config
改这三项(找现有行改,没有就加):
# 禁止 root 直接登录
PermitRootLogin no
# 禁用密码认证(只用密钥)
PasswordAuthentication no
# 允许公钥认证
PubkeyAuthentication yes
很多配置里
PasswordAuthentication前面有#(注释掉 = 用默认值,通常是 yes)。 必须去掉#并改成no。
Debian/Ubuntu 新版有个坑:可能会有 Include /etc/ssh/sshd_config.d/*.conf,那里的配置会覆盖主文件。
ls /etc/ssh/sshd_config.d/
如果有文件里面写了 PasswordAuthentication yes,要一起改。
④ 检查语法
sudo sshd -t
没输出 = 语法正确。
⑤ 平滑重载
sudo systemctl reload ssh # Debian 系
sudo systemctl reload sshd # RPM 系
用
reload不用restart—— 现有连接不断,你现在的会话还活着。
⑥ 验证(关键步骤)
另开一个终端(不是当前这个):
# 用密钥登录 —— 应该成功
ssh 用户@服务器IP
# 试着用密码登录 —— 应该被拒绝
ssh -o PreferredAuthentications=password -o PubkeyAuthentication=no 用户@服务器IP
# 应该看到 Permission denied (publickey)
第二个测试最重要:如果还能用密码登录,说明配置没生效。
两个测试都符合预期,再关掉旧会话。
四、第 3 条:fail2ban
它监控日志,发现某个 IP 反复失败,就自动封掉。
安装
sudo apt install fail2ban # Debian 系
sudo dnf install fail2ban # RPM 系
基本配置
不要直接改 jail.conf(升级会被覆盖)。建一个 jail.local:
sudo nano /etc/fail2ban/jail.local
[DEFAULT]
bantime = 1h
findtime = 10m
maxretry = 5
banaction = iptables-multiport
[sshd]
enabled = true
port = ssh
logpath = %(sshd_log)s
参数含义:
| 参数 | 含义 |
|---|---|
bantime | 封多久 |
findtime | 在多长时间窗口内计数 |
maxretry | 失败几次触发 |
banaction | 用什么方式封(关键,见下面) |
⚠️ banaction 是最容易失效的地方
默认配置在某些系统上会"假封禁"——日志显示"已封禁",但实际 iptables 里一条规则都没有。
我踩过这个坑:配置是 banaction = firewallcmd-rich-rules,但系统根本没跑 firewalld,每次封禁都报错失败,IP 一个没封,而 fail2ban-client status 还显示 “Total banned: 14”。
正确的做法:
# 看你系统有什么防火墙
which iptables nft firewall-cmd
# 纯 iptables 系统
banaction = iptables-multiport
# 用 nftables 的新系统
banaction = nftables-multiport
# 确实在用 firewalld(并且服务在跑)
banaction = firewallcmd-rich-rules
启用并验证
sudo systemctl enable --now fail2ban
sudo fail2ban-client status
sudo fail2ban-client status sshd
⚠️ 真的封住了吗?必须实测
这是最关键的一步——不要相信 status 的输出。
# 1. 看 iptables 里有没有 f2b 链
sudo iptables -L -n | grep f2b
如果什么都没有,说明封禁没生效。
实测封一个 IP:
# 手动封一个测试 IP
sudo fail2ban-client set sshd banip 203.0.113.99
# 立刻检查 iptables
sudo iptables -L -n | grep 203.0.113.99
应该看到类似:
REJECT all -- 203.0.113.99 0.0.0.0/0 reject-with icmp-port-unreachable
看到了才算真生效。 如果看不到,banaction 配错了。
解封测试 IP:
sudo fail2ban-client set sshd unbanip 203.0.113.99
五、第 4 条:防火墙,只开必要端口
先想清楚:这台机器需要开哪些端口
| 端口 | 用途 | 要不要开 |
|---|---|---|
| 22 | SSH | ✅ 要(或改成别的) |
| 80 | HTTP | ✅ 要(网站 + 证书验证) |
| 443 | HTTPS | ✅ 要 |
| 3306 | MySQL | ❌ 绝对不要 |
| 6379 | Redis | ❌ 绝对不要 |
| 8080 | 应用 | ❌ 一般不要(用 nginx 反代) |
原则:只开"必须从外部访问"的端口,其他一概不开。
ufw(Debian/Ubuntu)
sudo apt install ufw
# ⚠️ 先放行 SSH,再启用!(否则锁死)
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status verbose
⚠️ 顺序很重要:先
allow 22再enable。反过来会让你当场失去 SSH 连接。
firewalld(RPM 系)
sudo systemctl enable --now firewalld
sudo firewall-cmd --permanent --add-service=ssh
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload
sudo firewall-cmd --list-all
云服务器:还有一层安全组
云控制台网页上的"安全组/防火墙",优先级高于本机防火墙。
本机 ufw 全开、ss 显示在监听、curl 127.0.0.1 也通,但外网就是连不上 → 99% 是安全组没放行。
这是云上最常见的"为什么访问不了"。
六、第 5 条:及时更新
只装安全更新(推荐)
# Debian 系
sudo apt update
sudo apt list --upgradable
sudo apt upgrade
# 只装安全更新(unattended-upgrades)
sudo apt install unattended-upgrades
sudo dpkg-reconfigure --priority=low unattended-upgrades
自动安全更新是好事还是坏事?
| 优点 | 缺点 |
|---|---|
| 及时补漏洞 | 可能在凌晨重启服务 |
| 不用记着手动更新 | 极小概率引入新问题 |
建议:个人服务器开自动安全更新;生产关键业务手动更新并测试。
检查重启需求
# Debian 系
ls /var/run/reboot-required 2>/dev/null && echo "需要重启"
# RPM 系
sudo needs-restarting -r
内核更新后必须重启才生效。
七、第 6 条:服务只绑必要地址
很多服务默认监听 0.0.0.0(所有网络接口),等于对公网开放。
检查哪些服务在监听
sudo ss -ltnp
看"Local Address"那一列:
0.0.0.0:3306 ← ⚠️ 对全世界开放
127.0.0.1:3306 ← ✅ 只有本机能连
[::]:80 ← 所有 IPv6
发现不该暴露的,改它的配置绑到 127.0.0.1。
| 服务 | 配置项 |
|---|---|
| MySQL/MariaDB | bind-address = 127.0.0.1 |
| Redis | bind 127.0.0.1 |
| PostgreSQL | listen_addresses = 'localhost' |
| Docker 端口 | -p 127.0.0.1:3306:3306 |
Docker 特别注意:
-p 3306:3306等于-p 0.0.0.0:3306:3306,而且 Docker 会绕过 ufw 规则(它直接写 iptables)。 解决:一律写-p 127.0.0.1:3306:3306。
八、再补三件小事
1. 关掉不用的服务
systemctl list-unit-files --state=enabled
看着不认识的服务,查一下是什么,不用就停掉。
2. 检查有没有可疑的定时任务
crontab -l
sudo crontab -l
sudo ls -la /etc/cron.*/
入侵者常在这里留后门。
3. 检查有没有可疑账号
# UID 0 的账号(应该只有 root)
awk -F: '$3==0 {print $1}' /etc/passwd
# 有 shell 的账号(能登录的)
awk -F: '$7 !~ /nologin|false/ {print $1, $7}' /etc/passwd
# 最近登录
last -20
lastb -20
九、加固后的自查清单
做完上面这些,跑一遍这个:
#!/bin/bash
echo "=== 1. SSH 配置 ==="
sudo sshd -T 2>/dev/null | grep -E "^(passwordauthentication|permitrootlogin|pubkeyauthentication)"
echo "=== 2. 监听端口 ==="
sudo ss -ltnp | grep -v "127.0.0.1" | grep -v "::1"
echo "=== 3. 防火墙 ==="
sudo ufw status 2>/dev/null | head -8 || sudo firewall-cmd --list-all 2>/dev/null | head -12
echo "=== 4. fail2ban ==="
sudo fail2ban-client status 2>/dev/null || echo "未安装"
echo "=== 5. 失败登录统计 ==="
sudo grep -c "Failed password" /var/log/auth.log 2>/dev/null || echo "无记录"
echo "=== 6. UID 0 账号 ==="
awk -F: '$3==0 {print $1}' /etc/passwd
echo "=== 7. 重启需求 ==="
[ -f /var/run/reboot-required ] && echo "需要重启" || echo "不需要"
期望结果:
passwordauthentication nopermitrootlogin no- 监听端口里没有
0.0.0.0:3306这种 - 防火墙只开了 22/80/443
- fail2ban 在跑
- UID 0 只有
root
十、动手:五个练习
练习 1:看有多少人在尝试爆破
sudo grep -c "Failed password" /var/log/auth.log 2>/dev/null
sudo lastb | wc -l
练习 2:确认密钥能登录(改配置前)
ssh-copy-id 你的用户@服务器
ssh 你的用户@服务器 # 应该不问密码
练习 3:检查当前 SSH 配置
sudo sshd -T | grep -E "^(passwordauthentication|permitrootlogin)"
练习 4:验证 fail2ban 真的能封
sudo fail2ban-client set sshd banip 203.0.113.99
sudo iptables -L -n | grep 203.0.113.99
sudo fail2ban-client set sshd unbanip 203.0.113.99
看不到 iptables 里的规则,就是没真封。
练习 5:看哪些服务对公网开了
sudo ss -ltnp | grep "0.0.0.0"
逐个判断:这些真的需要对全世界开放吗?
十一、验证:你过关了吗
- 密钥登录已经能用了
-
PasswordAuthentication no生效 -
PermitRootLogin no生效 - fail2ban 在跑,且实测能真封 IP
- 防火墙只开了必要端口
- 知道云服务器还有"安全组"这一层
- 数据库/Redis 等只绑
127.0.0.1 - 知道怎么检查 UID 0 账号和可疑 cron
十二、不行怎么办
Q1:改完 SSH,连不上了! 别关旧会话。
如果旧会话还在:
sudo cp /etc/ssh/sshd_config.bak-* /etc/ssh/sshd_config
sudo systemctl reload ssh
如果已经全断了 → 用云控制台的 VNC / 物理控制台登录,改回来。
Q2:sshd -t 报错
配置语法错。不要 reload,先改对。
Q3:密钥登录不工作 按顺序查:
# ① 本地私钥权限
ls -l ~/.ssh/id_ed25519 # 必须 600
# ② 服务器上公钥
cat ~/.ssh/authorized_keys # 有没有你的公钥
ls -ld ~/.ssh # 必须 700
ls -l ~/.ssh/authorized_keys # 必须 600
# ③ 详细调试
ssh -v 用户@服务器 2>&1 | grep -i "offering\|denied\|authentications"
Q4:fail2ban 装了但没封任何 IP
九成是 banaction 配错(见第四节)。实测:
sudo fail2ban-client set sshd banip 203.0.113.99
sudo iptables -L -n | grep 203.0.113.99
没规则 = 没生效。
Q5:ufw enable 后 SSH 断了
因为没先 allow 22。 用云控制台 VNC 进去:
sudo ufw allow 22/tcp
sudo ufw reload
Q6:锁死在里面了,怎么办
- 云服务器 → 控制台 → VNC / 远程连接
- 物理机 → 接键盘显示器
- 虚拟机 → 宿主机控制台
这就是为什么动手前要确认"第二条路"。
Q7:ss 显示某个端口绑在 0.0.0.0,但不知道是什么服务
sudo ss -ltnp | grep :端口号
会显示进程名和 PID。 再 ps -p PID -o cmd 看完整命令。
Q8:Docker 的端口不受 ufw 管
这是 Docker 的设计(直接写 iptables)。
解决:-p 127.0.0.1:端口:端口,或在 /etc/docker/daemon.json 里设 "iptables": false(不推荐,会影响其他功能)。
Q9:担心自己把自己锁死,有没有安全做法 改 SSH 端口也是一种思路(减少扫描噪音),但不要以为改了端口就安全——扫描器会扫全端口。 真正的安全来自:密钥 + 禁 root + fail2ban + 及时更新。
十三、一句话总结
六件事:密钥登录、禁 root、fail2ban、防火墙、更新、只绑必要地址。
改 SSH 前:保持旧会话 + 另开终端测试 + 知道 VNC 怎么进。
fail2ban 必须实测封 IP,别信
status的输出。
0.0.0.0就是对全世界开放——包括 Docker 的-p。
← 上一篇 S6|监控 返回 第四周:架起来 总课表 28 天完整计划
28 天课程到此完成。
接下来往哪走? → 走完 28 天,往哪走:八个方向的选择指南