sshd —— SSH 服务端守护进程与硬化
一句话定义
sshd 是 OpenSSH 的服务端守护进程,监听端口(默认 22)、接受客户端连接、做认证、建立加密会话。本地用 ssh 是客户端,对端机器上必须有 sshd 在跑才能接你。
典型场景
- Day0 装机:新机器有默认 sshd 但默认禁了公钥认证(IDC 模板"特色"),要改 sshd_config 并 reload
- Day0 §2.3:key 验证可用后,关掉密码登录、关掉 root 密码、关掉 keyboard-interactive
- 每次改完 sshd 配置:
sshd -t必跑,否则把自己锁外面 - 排错"为什么 key 都对了还是连不上":90% 是远端 sshd 配置问题
守护进程的基本管理
启停 / 状态
systemctl status sshd # CentOS / RHEL / Ubuntu 22+ 推荐
systemctl status ssh # Debian / 老版 Ubuntu 可能叫 ssh
systemctl restart sshd # 重启(断开所有现有连接 —— 危险)
systemctl reload sshd # 重读配置不断连接(推荐用这个)
systemctl enable sshd # 开机自启
服务名是
sshd还是ssh:Ubuntu 22.04+ 是sshd,老 Debian 是ssh。不确定就systemctl list-unit-files | grep -i ssh看一下。
看监听状态
ss -lntp | grep sshd
# LISTEN 0 128 0.0.0.0:22 0.0.0.0:* users:(("sshd",pid=1234,fd=3))
# LISTEN 0 128 [::]:22 [::]:* users:(("sshd",pid=1234,fd=4))
注意:sshd 同时监听 IPv4 (0.0.0.0:22) 和 IPv6 ([::]:22),这是默认行为。
配置文件结构
OpenSSH 服务端配置在 两个位置:
/etc/ssh/sshd_config ← 主配置(包级别)
/etc/ssh/sshd_config.d/*.conf ← 分片 override(Ubuntu 22+ 引入)
sshd_config.d/*.conf 的 override 机制(重点)
主文件 sshd_config 末尾通常有:
Include /etc/ssh/sshd_config.d/*.conf
OpenSSH 读完主文件后,把 sshd_config.d/ 下所有 .conf 文件按字母序追加到配置流里。然后按"先到先得"原则生效。
陷阱:
# 你改了主配置
vim /etc/ssh/sshd_config
# 把 PasswordAuthentication 改成 no
systemctl reload sshd
# 但密码登录依然能用,什么鬼?
sshd -T | grep -i password
# passwordauthentication yes ← 还是 yes!
原因:/etc/ssh/sshd_config.d/50-cloud-init.conf 这种文件里写了 PasswordAuthentication yes,override 了你的修改。
很多 IDC 的 Ubuntu 模板都会自动生成这种 cloud-init / vendor 的分片配置,你看主文件以为改成功了,实际不生效。
正确改法
两边都要改:
# 1. 改主配置
sed -i 's/^#*PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config
# 2. 同时改所有分片
for f in /etc/ssh/sshd_config.d/*.conf; do
[ -f "$f" ] && grep -q PasswordAuthentication "$f" && \
sed -i 's/^PasswordAuthentication.*/PasswordAuthentication no/' "$f"
done
# 3. 验语法
sshd -t
# 4. reload
systemctl reload sshd
用 sshd -T 看实际生效的配置
这是排查 sshd 问题的第一个该跑的命令。
sshd -T | grep -i pubkey
# pubkeyauthentication yes
sshd -T | grep -i password
# passwordauthentication no
sshd -T | grep -i permitrootlogin
# permitrootlogin prohibit-password
sshd -T 模拟启动一次、把所有最终生效的配置(包括 Include 文件、Match 块、默认值)合并打印出来。不需要重启就能看实际生效情况。
别去
grep配置文件!文件里可能写了#PubkeyAuthentication yes(被注释)、可能在sshd_config.d里被 override、可能 OpenSSH 默认值就是你想要的(根本不用配)。sshd -T是唯一可靠的事实来源。
用 sshd -t 验证配置语法
sshd -t
echo $? # 0 表示语法正确
改完 sshd_config 一定先跑 sshd -t,再 reload。否则配错一个字,reload 之后 sshd 启动失败、SSH 服务彻底没了、现有连接断了你也连不回来 —— 物理 console 救援都未必有。
良好习惯:
sshd -t && systemctl reload sshd
&& 让 reload 只在 sshd -t 通过时才执行。
关键字段速查
| 字段 | 默认 | 训练营推荐 | 说明 |
|---|---|---|---|
Port | 22 | 22 / 改非默认 | 改非默认端口能减少 90% 的暴力扫描,但不是安全的根本对策 |
ListenAddress | 0.0.0.0 :: | 默认 | 只想监听内网网卡时指定 IP |
Protocol | 2 | 2 | OpenSSH 7 已废 Protocol 1,不用管 |
PermitRootLogin | prohibit-password | prohibit-password | 允许 root 用 key 登录,但禁止密码 |
PasswordAuthentication | yes | no(key 验通后) | 防止暴力破解 |
KbdInteractiveAuthentication | yes | no(key 验通后) | 同上,挡 PAM 那条路 |
PubkeyAuthentication | yes | yes | 允许公钥登录 |
AuthorizedKeysFile | .ssh/authorized_keys | 默认 | 公钥存放路径 |
MaxAuthTries | 6 | 3 | 失败次数上限,调小挡爆破 |
MaxSessions | 10 | 10 | 单个 ssh 连接里能开几个 session |
ClientAliveInterval | 0 | 60 | 多久没消息就发探测包(防 idle 断流,也防长挂连接耗资源) |
ClientAliveCountMax | 3 | 3 | 探测失败几次后断 |
AllowUsers / AllowGroups | 不限 | 视情况 | 白名单 |
DenyUsers / DenyGroups | 不限 | 视情况 | 黑名单 |
X11Forwarding | yes (Ubuntu) | no | 不用就关 |
AllowTcpForwarding | yes | yes | K8s port-forward / 跳板必需 |
GatewayPorts | no | no | 是否允许 -R 转发监听 0.0.0.0 |
训练营硬化套餐(Day0 §2.3)
ssh root@$ip "
sed -i \
-e 's/^#*PasswordAuthentication.*/PasswordAuthentication no/' \
-e 's/^#*PermitRootLogin.*/PermitRootLogin prohibit-password/' \
-e 's/^#*KbdInteractiveAuthentication.*/KbdInteractiveAuthentication no/' \
-e 's/^#*PubkeyAuthentication.*/PubkeyAuthentication yes/' \
/etc/ssh/sshd_config
for f in /etc/ssh/sshd_config.d/*.conf; do
[ -f \"\$f\" ] && sed -i \
-e 's/^PasswordAuthentication.*/PasswordAuthentication no/' \
-e 's/^PubkeyAuthentication.*/PubkeyAuthentication yes/' \"\$f\"
done
sshd -t && systemctl reload sshd
"
为什么这套组合:
| 改动 | 防住什么 |
|---|---|
PasswordAuthentication no | 全网爆破密码(每天几千次自动扫描) |
KbdInteractiveAuthentication no | PAM 的"另一条密码路"(不少教程只关了 PasswordAuthentication,结果 KbdInteractive 是个后门) |
PermitRootLogin prohibit-password | root 用密码登录(用 key 登可以 —— 适合自动化) |
PubkeyAuthentication yes | 反过来确保 key 认证是开的(很多 IDC 默认关) |
更严格的话 PermitRootLogin no —— 彻底禁 root 直接登,强制走普通用户 + sudo。但训练营场景多是单人玩、用 root 直接干活更省事,prohibit-password 已经够。
「PubkeyAuthentication no」这个真实踩坑
某些 IDC(特别是国内小厂)的 Ubuntu 模板,默认禁了 PubkeyAuthentication,只让密码登录(方便他们 console 重置密码)。
症状:
- 公钥已经写进
~/.ssh/authorized_keys - 权限都对(700 / 600)
ssh -v显示 key 正常 offer- 远端依然
Permission denied (publickey)
排查(先连上去 —— 还能用密码登):
ssh root@$ip 'sshd -T | grep -i pubkey'
# pubkeyauthentication no ← 根因
修复:
sed -i 's/^#*PubkeyAuthentication.*/PubkeyAuthentication yes/' /etc/ssh/sshd_config
for f in /etc/ssh/sshd_config.d/*.conf; do
[ -f "$f" ] && grep -q PubkeyAuthentication "$f" && \
sed -i 's/^PubkeyAuthentication.*/PubkeyAuthentication yes/' "$f"
done
sshd -t && systemctl reload sshd
教训:任何时候 sshd 表现不符合预期,第一时间 sshd -T 看实际生效,不要去 cat 配置文件猜。
reload vs restart 的区别
| 操作 | 影响 | 用途 |
|---|---|---|
reload sshd | 重读配置,不断现有连接 | 改配置后用这个(90% 场景) |
restart sshd | 重启进程,所有现有连接断开 | sshd 进程本身卡死、或者需要清空状态 |
注意:reload 不是万能的 —— 有些选项(监听端口、密码学算法选择)改了之后只有 restart 才生效。
但 restart 之前要确认:
- 新配置
sshd -t通过 - 你还有备用入口(物理 console、其它登录方式),万一 restart 之后启不起来能救
临时改端口、不影响现有登录
改了 Port 22 → Port 2222 之后:
sshd -t && systemctl restart sshd # 必须 restart,reload 不会切端口
但是!restart 会断现有连接。安全做法:
# 在 sshd_config 里同时监听两个端口
Port 22
Port 2222
# restart 之后两个端口都通;新连接走 2222;老连接(22)继续;测试 2222 没问题之后再去掉 22 行
看实时连接 / 看日志
# 当前活跃的 ssh 会话
who # 谁登着
ss -tnp | grep :22 # 端口 22 上的 established 连接
# sshd 日志(systemd-journald)
journalctl -u sshd -n 50 # 最近 50 行
journalctl -u sshd -f # 跟随
journalctl -u sshd --since "10 min ago"
# 老式系统的话
tail -f /var/log/auth.log # Debian/Ubuntu
tail -f /var/log/secure # CentOS/RHEL
排查爆破:
journalctl -u sshd --since today | grep -i 'failed\|invalid'
进阶:Match 块(按条件应用不同配置)
# /etc/ssh/sshd_config
PasswordAuthentication no # 全局禁密码
Match User backup
PasswordAuthentication yes # 但 backup 这个用户可以用密码(备份脚本用)
ForceCommand /usr/local/bin/backup-only # 而且强制只能跑这个命令
Match 让你按 user / group / host / address 等条件应用不同的配置。生产环境常用,训练营场景用不太到,知道有就行。
常见踩坑
坑 1:reload 之后配置没生效
90% 是 sshd_config.d/*.conf 里有 override。永远用 sshd -T 看实际生效,不要 grep 配置文件。
坑 2:把自己锁外面
改完配置 systemctl restart sshd 启动失败 → 现有 ssh 连接断 → 新连接也建不起来。
防御:
- 改配置前先开第二个 ssh 会话(windowed terminal 第二个 tab),保持登录,作为"救命通道"
- 必跑
sshd -t,语法对了再 reload/restart - 用
reload而不是restart(reload 不断连接) - 改完之后保留当前连接,开第三个 ssh 测试 —— 测通了再 logout 当前连接
坑 3:私钥/公钥都对,还是 Permission denied
可能性(从高到低):
- 远端 sshd 禁了 pubkey 认证(
sshd -T | grep -i pubkey) - 远端
~/.ssh/authorized_keys权限不对(必须 600 或 644) - 远端
~/.ssh目录权限不对(必须 700) - 远端用户的 home 目录权限不对(必须 755 或更严,不能是 777)—— OpenSSH 出于安全会拒
- SELinux 拒(
getenforce看;临时setenforce 0测试) - AllowUsers / DenyUsers 规则把你过滤了
authorized_keys里的公钥是错的(不是当前 IdentityFile 对应的公钥)
排查命令组合:
ssh -v m1 2>&1 | grep -i 'offering\|accepted\|refused'
ssh m1 'sshd -T | grep -iE "pubkey|allowusers|denyusers"' # 当然得先能登上去
坑 4:端口已经被占用,启动失败
Address already in use
通常是上一个 sshd 进程没退干净,或者有别的服务占了 22。
ss -lntp | grep :22 # 看谁占着
systemctl stop sshd
ss -lntp | grep :22 # 确认彻底停了
systemctl start sshd
坑 5:以为改了 sshd_config 立即生效
vim /etc/ssh/sshd_config # 改完保存
# 然后什么也不做,等着新登录用新配置 —— 不会生效
必须 systemctl reload sshd(或 restart)才生效。OpenSSH 不会 watch 配置文件变化。
坑 6:用 SCP/SFTP 传文件出错
sftp> ls
Received message too long ...
通常是远端 /etc/profile 或 ~/.bashrc 里有 echo/printf 之类的输出,污染了 sftp 通道。
修复:把 ~/.bashrc 里 "只对交互 shell 有意义" 的输出包在条件里:
if [[ $- == *i* ]]; then
echo "Welcome back"
fi
或者用 [[ -n "$PS1" ]] 判断。
关联命令
- ssh —— 客户端
- ssh-keygen —— 远端
host key也是这个生成(ssh-keygen -A重生成) - ssh-config —— 客户端配置
systemctl—— 启停 sshd 服务journalctl—— 看 sshd 日志