SSH 基础配置(密钥登录、禁用密码)见上一篇 SSH 密钥登录配置详解。这一篇处理三件事:换掉 22 端口、把 root 登录和认证方式收紧、用一台中转机连内网机器。张力问题只有一个:改了端口之后,安全到底提升了多少,剩下的攻击面靠什么堵。
先给结论:改端口只挡扫描器,挡不住已经盯上你的攻击者;端口改完之后真正守门的是强制密钥认证、PermitRootLogin 和 fail2ban 这条纵深链。下面按顺序展开。
改端口:sshd 改的是 ssh,不是 sshd
威胁:22 端口上的爆破量能刷爆 auth.log,掩盖真正的异常登录。换一个高位端口,日志噪音直接掉一个数量级。
操作:Ubuntu 24.04 的 sshd_config 默认通过 drop-in 目录加载片段,直接在 /etc/ssh/sshd_config.d/ 新建文件,不要去主文件里找注释掉的 Port 行(很多镜像默认写的是 Port 22,直接改它也行,但 drop-in 优先级更高,两处都看一眼最稳)。
sudo tee /etc/ssh/sshd_config.d/20-port.conf <<'EOF'
Port 2222
EOF
改完先跑语法检查,再重启服务:
sudo sshd -t && sudo systemctl reload ssh
sshd -t 静默返回说明配置没写错;有语法问题它会直接报行号。reload 不断开已有连接,比 restart 更安全。注意 Ubuntu 的服务名叫 ssh,systemctl restart sshd 会报 Unit not found。
验证:另开一个终端,别关当前这个,用新端口连接,通了再处理旧端口。
ssh -p 2222 username@your_server_ip
一条运维习惯:改端口期间在 drop-in 里同时写两行 Port(2222 和 22),确认新端口连通后再删掉 22 那行。云厂商的安全组/防火墙要放行新端口,UFW 用户执行 sudo ufw allow 2222/tcp,忘了这一步,新端口永远连不通。
改端口的边界要说清楚:它过滤的是全网无差别扫描器。针对性攻击者用一个端口扫描就能找到新端口,所以它只解决日志噪音问题,安全收益到此为止。
PermitRootLogin 与 AllowUsers:把登录对象收窄
威胁:root 是唯一保证存在的用户名,爆破工具默认就用它。只要 root 能从 SSH 进来,攻击者省掉猜用户名这一步。
操作:Ubuntu 24.04 镜像出厂就是 PermitRootLogin no,ssh-key-auth 那篇的密钥流程里已经覆盖过。如果你是在旧系统上维护,确认一下这行还在。真正值得加的是按用户名和用户组做白名单:
sudo tee /etc/ssh/sshd_config.d/20-restrict-users.conf <<'EOF'
PermitRootLogin no
AllowUsers deploy
EOF
AllowUsers 生效后,白名单外的账号即使密码正确也进不来。多人共用服务器时用 AllowGroups sshusers 配合 addgroup 管理,不用每次改配置。
验证:sshd -t 通过后 reload,用一个不在白名单里的账号试连,应直接被拒,连密码提示都没有。
AuthenticationMethods:多重认证语法上有坑
威胁:只配 PasswordAuthentication no 时,认证策略是「pubkey 或 password 任选其一」,去掉 password 就只剩 pubkey,这没问题。但当你要叠加第二重因素(比如密钥+PAM 动态码)时,单靠禁用某一项表达不了「都要」的语义,得用 AuthenticationMethods 显式声明。
操作:AuthenticationMethods 的分隔符决定语义:空格分隔=满足任意一组即可,逗号分隔=一组内的多个方法必须全部通过。组合「密钥 + PAM 键盘交互」写法:
sudo tee /etc/ssh/sshd_config.d/20-authmethods.conf <<'EOF'
KbdInteractiveAuthentication yes
AuthenticationMethods publickey,keyboard-interactive:pam
EOF
如果只是强制密钥、不叠加额外因素,不需要 AuthenticationMethods,保留 PasswordAuthentication no 就够了。这是这篇文章里最容易过度配置的一处:叠加 PAM 前先确认 TOTP(Time-based One-Time Password)侧已部署好,否则直接把自己锁在门外。
验证:改完 reload,用带密钥的会话连一次,应能看到 PAM 交互提示;密钥不存在时连接直接失败。
ProxyJump 跳板机:一条命令打通内网机器
场景:数据库、后台面板这类服务只绑在内网,公网摸不到。跳板机(jump host)是标准解法:先 SSH 到一台双网卡机器,再从它连内网目标。老的写法要敲两条 ssh,OpenSSH 7.3 起有了 -J 参数,一条命令直达。
操作:最直接的用法:
ssh -J [email protected]:2222 [email protected]
冒号后面是跳板机的 SSH 端口,默认 22 可省略。写进 ~/.ssh/config 一劳永逸:
Host jump
HostName 203.0.113.1
User jumpuser
Port 2222
Host internal
HostName 10.0.0.5
User target
ProxyJump jump
之后 ssh internal 就等于自动先连跳板再跳转。ProxyJump 的认证链是独立的:跳板机用自己的密钥,内网机器用它自己的密钥,两段各自走各自的 IdentityFile。SFTP 和 scp 同样支持 -J,传文件也能走跳板。
验证:ssh internal 直达内网机器命令行即成功。连接失败时先在跳板机上手动 ssh [email protected] 确认内网段通不通,把链路问题拆成两段排查。
纵深防御:端口改完之后还剩什么
改端口只解决日志噪音。下面这张表把剩余攻击面和对应手段放在一起:
| 攻击面 | 手段 | 配置位置 |
|---|---|---|
| 密码爆破 | PasswordAuthentication no | sshd_config |
| root 用户名被猜 | PermitRootLogin no | sshd_config |
| 失败重试无上限 | fail2ban 自动封禁 | /etc/fail2ban/jail.local |
| 扫描器绕过端口 | 密钥认证(爆破无解) | authorized_keys |
| 单密钥泄露 | passphrase + agent 托管 | ssh-keygen |
fail2ban 的完整部署已经写在 fail2ban 防爆破指南,这里只提一句:改端口后 fail2ban 监控的端口也要同步改,否则它盯着的还是 22,等于白装。顺带一提,OpenSSH 9.8 起服务端自带 PerSourcePenalties,对反复失败的源地址自动降权——新系统可以少装一层工具,老系统还是 fail2ban 更可控。
ssh-audit 可以对服务器配置做一次自动体检,装上跑一遍能发现还在放行的弱算法:
sudo apt install ssh-audit
ssh-audit 127.0.0.1
常见问题(FAQ)
1. 改端口后连接被拒,配置文件明明写对了?
三个地方按顺序查:UFW 有没有放行新端口、云厂商安全组有没有放行、sshd -t 有没有报错。前两个占绝大多数——改配置容易,忘记开防火墙是这类操作最常见的翻车点。
2. Ubuntu 上服务名到底是 ssh 还是 sshd?
ssh。Debian 系的 unit 文件叫 ssh.service,systemctl restart sshd 会直接报 Unit not found。RHEL 系才是 sshd。
3. drop-in 目录和主配置文件冲突,谁说了算?
sshd_config 里 Include 那行的位置决定优先级——更靠前的配置先生效。Ubuntu 24.04 默认把 Include /etc/ssh/sshd_config.d/*.conf 放在文件开头,所以 drop-in 里的配置会覆盖主文件后面的同名项。排查配置不生效时,用 sudo sshd -T | grep port 看实际生效值,别猜。
4. 改了 sshd 配置,为什么旧会话还能用?
reload 只对新连接生效,已建立的会话不受影响。这是特性,让你能在断开当前会话之前先验证新配置——改 SSH 配置永远保留至少一个已连接的终端窗口,直到确认新配置能登录。
5. ProxyJump 和 ProxyCommand 有什么区别?
ProxyJump 是 OpenSSH 7.3+ 的简写,底层自动展开成 ProxyCommand ssh -W ...。新配置一律用 ProxyJump,写法短、不易错;ProxyCommand 只在需要自定义转接逻辑(比如走 nc、走特定代理协议)时才用。
改端口、收 root、强制密钥、跳板进内网,这套组合拳下来,SSH 的暴露面就只剩「你的私钥文件本身」。维护成本最低的三件事:改端口清理日志、PermitRootLogin no、PasswordAuthentication no,三行配置换一个干净到接近无爆破记录的 auth.log。