SSH 改个端口就算加固?一台 Linux 服务器上线前我会检查这些东西
新买一台云服务器,装好 Docker,项目一跑,域名一绑,看起来已经可以用了。
然后你打开 SSH 日志,通常很快就能看到各种陌生 IP 在试 root、admin、ubuntu、test。很多人的第一反应是把 22 改成 22222,再装个 Fail2ban,觉得差不多了。
改端口当然有用,至少日志能安静不少。但它解决的主要是扫描噪声。只要服务还在公网监听,端口最终都能被扫出来。服务器真正需要处理的是认证、权限、暴露面、更新和恢复能力。
这篇按我自己给一台新 Linux 服务器做上线检查的顺序来写。示例以 Ubuntu Server 为主,Debian 基本一样,其他发行版只需要替换包管理器和防火墙工具。
先说一个最重要的原则:所有 SSH 和防火墙改动,都不要在只剩一条远程连接的情况下直接提交。
保留当前 SSH 会话,另开一个终端验证新配置能登录,再关闭旧会话。云服务器最好同时确认厂商控制台、VNC 或救援模式可用。安全加固把自己锁在门外,这种事一点也不少见。
先看清服务器到底暴露了什么
我不会一上来先改配置,第一步通常是看现状。
sudo ss -lntup
重点看监听地址:
127.0.0.1:5432
0.0.0.0:22
0.0.0.0:80
0.0.0.0:443
[::]:22
127.0.0.1 只监听本机,0.0.0.0 表示监听所有 IPv4 地址,[::] 通常意味着 IPv6 也在监听。
很多“我明明没开放这个端口”的问题,到这里就已经能看出一半。
如果装了 Docker,再补一条:
docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.Ports}}'
服务器的网络边界大概可以画成这样:
Internet
|
v
云厂商安全组 / 防火墙
|
v
Linux 主机防火墙(UFW / nftables)
|
+------ sshd
+------ Caddy / Nginx
+------ Docker 发布到宿主机的端口
|
v
Docker 内部网络
这几层任何一层配错,最后看到的结果都可能和你的预期不同。
先准备一个正常的管理账号
日常管理没有必要一直用 root。
sudo adduser ops
sudo usermod -aG sudo ops
然后切到新账号确认 sudo 正常:
su - ops
sudo whoami
正常应输出:
root
这里的意思是“普通账号需要时可以提权”,并不是让这个账号一直以 root 身份跑。
Ubuntu 官方的安全建议也一直强调最小权限:平时使用普通用户,只在管理任务中使用 sudo。
SSH 密钥先配好,再关密码登录
在自己的电脑上生成一把 Ed25519 密钥:
ssh-keygen -t ed25519 -a 64 -C "server-admin"
Linux、macOS 或 WSL 可以直接:
ssh-copy-id ops@SERVER_IP
然后试一次:
ssh ops@SERVER_IP
确认不用输入服务器密码也能正常登录以后,再开始改 sshd。
我比较喜欢把自己的配置放到 drop-in 文件里,而不是直接把 /etc/ssh/sshd_config 改得面目全非:
sudo nano /etc/ssh/sshd_config.d/99-hardening.conf
基础配置可以从下面这份开始:
PermitRootLogin no
PubkeyAuthentication yes
PasswordAuthentication no
MaxAuthTries 3
LoginGraceTime 30
X11Forwarding no
AllowUsers ops
如果确定没有使用 PAM 一次性验证码、SSH MFA 之类的认证方式,还可以加:
KbdInteractiveAuthentication no
但这条不要无脑复制。你以后如果要做多因素认证,它很可能需要重新打开。
改完以后先检查语法:
sudo sshd -t
没有任何输出才继续。
然后重新加载:
sudo systemctl reload ssh
此时不要退出当前终端,另开窗口重新登录一次。
ssh ops@SERVER_IP
确认没问题,再继续后面的操作。
那 SSH 要不要改端口?
可以改,但我不会把它当成核心防线。
例如:
Port 22222
它确实能过滤掉一批只盯着 22 端口乱撞的脚本,日志会清爽很多。但端口号不是秘密,公网扫描器迟早会找到它。
真正重要的是:
- root 不直接远程登录;
- 密码认证关闭;
- 私钥保管好;
- SSH 入口受到防火墙限制;
- 登录行为有日志和封禁策略。
端口只是锦上添花。
防火墙别等到最后才开
Ubuntu 自带 UFW,但默认通常没有启用。
先设置默认策略:
sudo ufw default deny incoming
sudo ufw default allow outgoing
在启用之前先放行 SSH。
如果还是 22:
sudo ufw allow 22/tcp
如果已经改到 22222:
sudo ufw allow 22222/tcp
Web 服务再放 80 和 443:
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
确认规则:
sudo ufw status numbered
最后才启用:
sudo ufw enable
如果你有固定公网 IP,SSH 还可以继续收紧:
sudo ufw allow proto tcp from 203.0.113.10 to any port 22
这样公网其他地址连 SSH 都到不了认证阶段。
当然,移动网络、校园网、家庭宽带经常换 IP 的人不要照抄,否则下次换个网络就会发现自己进不去了。
别忘了云厂商安全组
云服务器一般还有一层平台侧安全组。
我习惯让它和主机规则都保持最小开放:
22/22222 只允许管理来源(能固定的话)
80 0.0.0.0/0
443 0.0.0.0/0
其他端口 默认关闭
安全组和 UFW 是两套东西,改一边不会自动同步另一边。
IPv6 也别忘。很多人只检查 IPv4,结果服务在 [::]:端口 上照样对外监听。
Fail2ban 可以装,但别把参数配成报复社会
Fail2ban 的作用很简单:持续读取日志,发现某个来源反复认证失败后,临时封掉它。
安装:
sudo apt update
sudo apt install -y fail2ban
新建:
sudo nano /etc/fail2ban/jail.d/sshd.local
一份比较正常的起点:
[sshd]
enabled = true
port = ssh
backend = systemd
maxretry = 5
findtime = 10m
bantime = 1h
如果 SSH 已经换端口,把 port 改成对应数字。
启动并检查:
sudo systemctl enable --now fail2ban
sudo fail2ban-client status sshd
我不建议把配置写成:
maxretry = 1
bantime = 几十年
一次失败就永久封禁,带来的安全收益很有限,误伤自己、同事、跳板机、NAT 出口的概率却会明显上升。
攻击者真正需要面对的是“根本没有密码可以猜”,这比把封禁时间写得特别夸张有效得多。
Docker 是最容易漏掉的一层
这里非常值得单独说。
假设你的 Compose 里写了:
services:
postgres:
image: postgres:17
ports:
- "5432:5432"
这通常意味着 PostgreSQL 被发布到了宿主机网络接口。
如果数据库只给同一台机器里的应用访问,这个 ports 很可能根本没必要。
更合理的写法通常是让两个容器进入同一个 Docker 网络:
services:
app:
image: example/app:latest
networks:
- backend
postgres:
image: postgres:17
networks:
- backend
networks:
backend:
应用直接访问:
postgres:5432
数据库完全不需要发布到公网。
如果确实需要宿主机访问,但不需要外网访问,可以绑定回环地址:
ports:
- "127.0.0.1:5432:5432"
Docker 官方文档明确提醒:Linux 上 Docker 会创建自己的防火墙规则来实现端口发布和网络隔离。已发布到宿主机外部地址的端口,默认可能对远程主机可访问。因此不要只看 ufw status 就断言“这个端口肯定被挡住了”。
至少同时检查:
sudo ss -lntup
docker ps --format 'table {{.Names}}\t{{.Ports}}'
如果你前面已经用了 Caddy 做统一入口,通常最舒服的结构就是:
公网
|
80 / 443
|
Caddy
|
Docker 内部网络
|--- Web
|--- API
|--- 其他服务
PostgreSQL / Redis / NATS 等内部组件不发布公网端口
之前写过一篇完整部署过程,可以直接接着看:
用 Caddy + Docker Compose 给自托管服务自动上 HTTPS
系统更新不能靠“有空再说”
很多服务器刚装好时会认真更新两次,半年以后就再也没人碰。
至少定期执行:
sudo apt update
apt list --upgradable
然后安排升级:
sudo apt upgrade
Ubuntu Server 支持 unattended-upgrades 自动安装安全更新,很多镜像默认已经开启,但不同云厂商镜像的实际配置可能不一样,最好自己确认:
systemctl status unattended-upgrades
没有安装的话:
sudo apt install -y unattended-upgrades
sudo dpkg-reconfigure --priority=low unattended-upgrades
自动更新也不是设置完就再也不管。内核、OpenSSL、OpenSSH、容器运行时这些东西升级以后,有时需要重启服务甚至重启机器才能真正使用修复后的版本。
密钥、Token 和 .env 也属于服务器安全
SSH 锁得再严,结果管理员把数据库密码写进 Git 仓库,还是白忙。
至少做到:
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys
chmod 600 .env
但 chmod 600 .env 只是最基础的一层。真正重要的是:
.env不提交 Git;- CI/CD Token 不写进仓库;
- 数据库、对象存储、云 API 使用独立凭据;
- 能给只读权限就别给管理员权限;
- 凭据疑似泄露时直接轮换,不要只删聊天记录或 Git 提交;
- 生产环境不要和开发环境共用同一套密钥。
如果项目已经开始认真做发布流程,可以再看之前的供应链安全文章:
你扫描的是代码,攻击者盯的是发布链路:软件供应链安全到底在防什么
日志要看得见,出事以后才有东西查
SSH 登录先看:
sudo journalctl -u ssh --since today
查看近期登录:
last -a
Fail2ban:
sudo fail2ban-client status sshd
UFW:
sudo ufw status verbose
Docker:
docker ps
服务运行状态:
systemctl --type=service --state=running
对一两台个人服务器,不一定非要先上完整 SIEM。但最起码你得知道日志在哪、保留多久、服务器重启后还有没有,以及磁盘满了会发生什么。
如果网站经过反向代理,还要确认访问日志里没有把 Authorization、Cookie、密码、Token 等敏感信息完整记录下来。
这部分和上一篇 HTTPS 文章是连着的:
备份不是运维附加项,它决定你被打以后还能不能回来
很多安全文章只讲“怎么不被打”,很少讲“打穿以后怎么办”。
现实里没有哪台公网服务器能承诺永远不出问题。
所以至少要有:
- 数据库定期备份;
- 关键配置备份;
- 备份放到另一台机器或对象存储;
- 备份文件加密;
- 保留多个历史版本;
- 真正做过一次恢复测试。
最后一条尤其重要。
backup completed successfully 只能证明备份程序没报错,不能证明那份东西真的能恢复。
另外,SSH 管理密钥最好至少有第二种恢复路径:备用管理员密钥、云厂商控制台或者救援系统。主电脑硬盘损坏的时候,你不应该顺便失去服务器管理权。
我现在给服务器上线前会跑的一组检查
下面这些命令不修改系统,适合做最后一次人工检查:
printf '\n=== Listening ports ===\n'
sudo ss -lntup
printf '\n=== SSH effective config ===\n'
sudo sshd -T | grep -E \
'^(port|permitrootlogin|passwordauthentication|pubkeyauthentication|maxauthtries|kbdinteractiveauthentication) '
printf '\n=== Firewall ===\n'
sudo ufw status verbose
printf '\n=== Fail2ban ===\n'
sudo fail2ban-client status sshd 2>/dev/null || true
printf '\n=== Docker published ports ===\n'
docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.Ports}}' 2>/dev/null || true
printf '\n=== Running services ===\n'
systemctl --type=service --state=running --no-pager
printf '\n=== Pending package updates ===\n'
apt list --upgradable 2>/dev/null
printf '\n=== Recent SSH log ===\n'
sudo journalctl -u ssh -n 30 --no-pager
跑完以后问自己几个很简单的问题:
- 现在公网到底有哪些端口?
- SSH 还能不能用密码和 root 直接登录?
- Docker 有没有把数据库、中间件、管理后台顺手暴露出去?
- 系统最近一次安全更新是什么时候?
- 如果服务器今天被删掉,我能不能在另一台机器上恢复?
这几个问题比“SSH 到底是 22 还是 35271”重要得多。
最后
Linux 服务器安全没有一个神奇配置,复制进去就能结束。
真正麻烦的是那些很普通的小地方:一个多余的 ports:,一把几年没轮换的 Token,一个从来没测试恢复的备份,一台半年没升级的服务器,或者一个为了方便一直开着的 root 密码登录。
这些东西单独看都不刺激,也不会让终端里出现绿色的 SECURE 字样。但服务器长期跑在公网,最后出问题的往往就是这些地方。
所以我的习惯一直很简单:先把暴露面缩小,再把身份和权限管住,然后保证补丁、日志和恢复链路真的能工作。
改 SSH 端口可以做。
但别做到这里就收工。
参考资料
SSH 改个端口就算加固?一台 Linux 服务器上线前我会检查这些东西
https://wangling.hauchet.cn/archives/linux-server-security-baseline-before-production
评论