⚠️ 提示:本站 AI 助手使用博主个人 API Token,没事别特么瞎用。
运维 · · 约 47 分钟阅读

Linux SSH 服务配置完全指南:sshd_config 逐项解读与生产最佳实践

SSH 是每台 Linux 服务器的"后门",配置错一次就可能把自己锁在门外或被暴力打穿。本文按 OpenSSH 8.x/9.x 官方默认顺序,逐项解释 sshd_config 中最常见 80+ 指令(端口、监听、协议、密钥、认证、PAM、登录限制、SCP/SFTP、端口转发、安全加固、日志审计、匹配块 Match),配套 4 套面向生产的配置模板(通用最小化 / 跳板机堡垒机 / Git/CI 密钥登录 / 禁用密码但保留公网入口),并给出 15 个高频排障场景(超时自动断开、跳板机端口转发被拒、sftp subsystem 错误、密钥正确仍要输密码、密钥类型不被接受、banner 未显示、PAM 拒绝等),以及 systemd 起停重载、改配置后如何"不把自己锁在门外"双会话验证流程。

一、SSH 与 sshd 是什么

OpenSSH 全家桶的角色

flowchart LR
  C["客户端<br/>ssh / scp / sftp / ssh-keygen<br/>(~/.ssh 配置)"] -- "22/tcp" --> S["服务器<br/>sshd (守护进程)<br/>/etc/ssh/sshd_config"]
  S --> P["PAM 栈<br/>/etc/pam.d/sshd"]
  S --> A["授权文件<br/>~/.ssh/authorized_keys<br/>(公钥校验)"]
  S --> L["wtmp/btmp/Journal<br/>登录审计"]
  • OpenSSH:绝大多数发行版自带的 SSH 实现(客户端 + 服务端 + 工具集)。

  • ssh:客户端命令,用来连别人。

  • sshd:服务端守护进程(sshd 末尾多一个 d = daemon),监听 22 端口处理进入的连接。

  • sshd_config:本文主角——sshd 的主配置文件,路径固定 /etc/ssh/sshd_config(部分发行版还有 /etc/ssh/sshd_config.d/*.conf 覆盖优先级更高)。

  • 配置覆盖顺序(后出现的覆盖前者):sshd_config → sshd_config.d/*.conf(字典序)→ Match 块按匹配命中顺序。

改配置的铁律:永不把自己锁在门外

⚠️ 黄金法则:改完 sshd_config 永远不要直接 systemctl restart sshd。

正确流程:

  1. 开 2 个 SSH 窗口:窗口 A 已登录保持连接(= 保险带),窗口 B 用新配置测试。
  2. 改完配置先语法检查:sshd -t → 0 退出码才允许重载。
  3. sshd -T(打印合并后的实际生效配置)——用来排查「我明明改了怎么没生效」。
  4. systemctl reload sshd(软重载,不中断窗口 A)。
  5. 窗口 B 新开一个连接确认登录成功;成功再关闭窗口 A,失败立即在 A 里回滚并再次 reload。

二、配置的读入顺序与检查方法

# 1) 语法检查(最常用)
sshd -t && echo OK

# 2) 打印实际生效的完整配置(合并 include + match 后的最终值)
#    非常适合查:我改的 Port 是不是真的生效了?
sshd -T | grep -Ei 'port|listenaddress|permitroot|passwordauth|pubkey'

# 3) 模拟某用户/某源 IP,看 Match 块命中后的效果
#    例:用 root 从 10.0.0.5 登录会得到什么最终配置
sshd -T -C user=root,host=10.0.0.5,addr=10.0.0.5

# 4) 查某发行版默认的 sshd_config 模板(防止被你改烂后想对比)
#    Debian/Ubuntu 把默认值放到 /usr/share/openssh,RHEL 在 /etc/ssh/sshd_config.rpmnew
ls /usr/share/openssh 2>/dev/null; ls /etc/ssh/*.rpmnew 2>/dev/null

三、sshd_config 常见指令逐项解读

说明:下面默认按 OpenSSH 8.x/9.x 组织;个别指令(如 PubkeyAcceptedAlgorithms)在 8.5+ 改名,老版本用 PubkeyAcceptedKeyTypes。一切以你本机 man sshd_config 为准。

2.1 基本网络监听

指令默认值作用生产建议
Port 2222监听 TCP 端口,可写多行监听多个端口跳板机建议多开一个备份端口(如 Port 22 + Port 22022),防止防火墙误配
ListenAddress 0.0.0.0全部 IPv4 地址绑定网卡地址;IPv6 用 ListenAddress ::内网机改成只绑内网 IP,减少暴露面
AddressFamily anyany地址族:inet / inet6 / any纯 IPv6 主机可写 inet6;双栈留 any

2.2 主机密钥(Host Key)——服务器的身份证

指令默认值作用说明
HostKey /etc/ssh/ssh_host_rsa_keyRHEL/Debian 都自带RSA 主机密钥文件新发行版一般不写死这几条,sshd -T 会列出实际路径
HostKey /etc/ssh/ssh_host_ecdsa_key—ECDSA 主机密钥256/384/521 三种曲线任选
HostKey /etc/ssh/ssh_host_ed25519_key—Ed25519 主机密钥推荐优先算法,签名更快更小
HostKeyAlgorithms自动选择允许客户端协商的主机密钥算法列表老旧客户端(如 JSch 老版本)连不上时,把 ssh-rsa 加回来
RekeyLimit default nonedefault会话多长数据量/多久后重协商一次密钥保持默认即可;高安全环境可 1G 30m

💡 客户端首次连接时提示的 The authenticity of host can't be established 就是在让你校验服务器 Host Key 指纹。ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub 能拿到服务器指纹,和客户端显示的核对后再输 yes。

2.3 协议与加密套件(Ciphers / MACs / KexAlgorithms)

指令说明
Ciphers允许的对称加密算法。推荐保守写法:Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr
MACs完整性校验算法:MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com,hmac-sha2-512,hmac-sha2-256
KexAlgorithms密钥交换算法:OpenSSH 9 默认就挺安全,要锁死可以:curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512

🔐 安全基线(PCI-DSS / 等保)审核常查:禁用 CBC 模式、禁用 SHA-1、禁用小于 3072 位的 DH 组。ssh-audit(开源工具)会直接给出合规审计报告。

2.4 日志与审计

指令默认作用建议
SyslogFacility AUTHAUTH写到 syslog 哪个设施保持 AUTH;某些系统需要 AUTHPRIV 分开日志
LogLevel INFOINFO日志级别:QUIET/FATAL/ERROR/INFO/VERBOSE/DEBUG生产用 VERBOSE(会记录公钥指纹,方便排障);不要 DEBUG(会记录大量敏感协商与会话元数据,仅用于临时排障,禁止生产环境使用)
PrintMotd yesyes登录后打印 /etc/motd 内容想自定义 Banner 时用后面的 Banner 指令
PrintLastLog yesyes显示上次登录时间和来源 IP建议 yes,用户自己能第一时间发现异常登录
Banner /etc/ssh/banner无认证前显示的法律/警告文本合规必备,放一句「未授权访问将被记录」
Compression delayeddelayed是否压缩会话传输delayed = 认证后才开压缩(防止 pre-auth 攻击);建议保留
TCPKeepAlive yesyesTCP 层发送 keepalive 探测配合下一节的 ClientAlive 组合使用

2.5 连接存活(超时与保活)

flowchart LR
  S[sshd] -- "每 ClientAliveInterval 秒<br/>发一次空探测" --> C[客户端]
  C -- 回复 --> S
  S -- "连续 ClientAliveCountMax 次无回复<br/>主动断开" --> X[TCP 断开]
指令作用生产跳板机示例
ClientAliveInterval 60服务端多久给客户端发一次保活探测(秒)30 或 60
ClientAliveCountMax 3连续多少次探测无响应才断开3(即 3 分钟无响应后断)
LoginGraceTime 60客户端连上后多少秒内必须完成认证,否则踢掉45,缩短暴力破解窗口
MaxStartups 10:30:100未完成认证的并发连接数阈值:超过 10 后开始按 30% 概率拒绝新连接,到 100 全拒公网机设 20:30:60
MaxSessions 10同一条 TCP 会话可开的多路复用通道数上限保持默认
MaxAuthTries 6单次连接最多输错几次密码公网机建议 3

💡 很多人误以为”我配置了 keepalive 怎么还是断”——中间设备(NAT、防火墙、LB)强制 10 分钟清会话,此时客户端侧也得开:~/.ssh/config 里写 ServerAliveInterval 30,让客户端也主动发探测。

2.6 用户/组准入:谁能登录?谁被禁止?

指令作用优先级(由高到低:Deny > Allow)
DenyUsers alice bob黑名单用户最优先(不管 Allow 怎么写,Deny 直接拒)
DenyGroups badguys黑名单用户组
AllowUsers ops@10.0.0.0/24 jenkins白名单用户,支持 user@host 限制来源推荐:生产只用 Allow 不用 Deny
AllowGroups ops deploy白名单用户组只允许 ops/deploy 组登录

执行顺序:DenyUsers → DenyGroups → AllowUsers → AllowGroups。全部不写默认允许所有人(只要认证通过)。

⚠️ 坑:用了 AllowUsers 但漏写了 root(或自己的账号),重载后直接把自己拒在外。一定要配合上面”双会话 + reload”流程。

2.7 根用户登录与密码登录

指令默认/选项说明
PermitRootLogin prohibit-passwordprohibit-passwordyes(允许,危险)/ no(完全禁止,推荐)/ prohibit-password(公钥能登但密码不行)/ forced-commands-only
PasswordAuthentication yesyes是否允许密码登录公网必须 no;内网机也建议 no
PermitEmptyPasswords nono允许空密码登录吗?永远 no
ChallengeResponseAuthentication no与 PAM 联动OpenSSH 9 里这个字段常显示 deprecated,等价于 KbdInteractiveAuthentication
KbdInteractiveAuthentication yesyes键盘交互式认证(PAM、OTP 二次验证走这里)开了 2FA 才 yes,纯密钥登录可 no
PubkeyAuthentication yesyes公钥认证总开关必然 yes
AuthorizedKeysFile .ssh/authorized_keys .ssh/authorized_keys2默认两个位置从用户 home 下读哪些文件作为公钥列表不要改默认;目录权限必须 700 文件 600,属主必须是登录用户本人,否则 sshd 会默默忽略
PubkeyAcceptedAlgorithms8.5+ 用这个,之前叫 PubkeyAcceptedKeyTypes允许的公钥算法列表老客户端连不上时把 ssh-rsa 加回来
AuthorizedKeysCommand / AuthorizedKeysCommandUser无公钥不从本地文件读,改为调脚本从 LDAP/AD/Vault 拉中大型公司用这个统一管理公钥,脚本必须以非 root 身份运行(第二个参数指定运行用户)
UsePAM yesRHEL/Debian 都 yes启用 PAM 栈(含密码校验、密码过期提醒、2FA、限制、审计)几乎必 yes;改成 no 后所有 PAM 模块都失效
AuthenticationMethods publickey,keyboard-interactive单个任意方法通过即可多因子组合写法:逗号=AND 逗号分隔,空格=OR 逗号分隔2FA 强制要求:publickey,keyboard-interactive(先公钥再 OTP,两者都要)
                 ┌──────────────────────────────────────────────┐
                 │ AuthenticationMethods 的 AND/OR 语义         │
                 ├────────────────────────────────────────────┤
                 │  publickey password                          │
                 │    → OR 语义:密码 OR 公钥 任一通过即可     │
                 │                                              │
                 │  publickey,keyboard-interactive              │
                 │    → AND 语义:必须先公钥成功,再OTP成功    │
                 └──────────────────────────────────────────────┘

2.8 登录后 shell 与 TTY

指令作用建议
PermitTTY yes是否分配 pty(交互式终端)流水线/CI 只跑命令可设 no,防止用户搞出交互 shell
ForceCommand /opt/gate-shell.sh忽略客户端指定的命令,强制执行固定命令跳板机、Git 服务器(git-shell)、sftp-only 用户常用
X11Forwarding noX11 图形转发生产一律 no,没有用到图形界面
AllowTcpForwarding no / yes / remote / local端口转发总开关(详见 2.9)普通服务器 no,跳板机按需局部开
GatewayPorts no远程转发时绑定在远端 0.0.0.0 还是 127.0.0.1默认 no 更安全,只有明确需要外部访问才 yes
AllowAgentForwarding no允许把 ssh-agent 转发到远端吗?公网/跳板机要 no;agent 转发=把本地私钥签名能力借给远端,被入侵就悲剧
AllowStreamLocalForwarding noUnix socket 转发几乎不用,默认 no 更安全
PermitTunnel no建 tun 设备(把 SSH 当 IPsec/VPN 用)不用就关
DisableForwarding yesOpenSSH 9.0+ 新增:一键关闭所有转发(= 上面 4 条全关)生产通用服务器直接写这条,简单安全

2.9 端口转发四兄弟(容易混乱的一组)

flowchart LR
  L["本地转发 -L<br/>ssh -L 8080:内网Web:80 跳板机<br/>用户机:8080 → 跳板机 → 内网Web:80"]
  R["远程转发 -R<br/>ssh -R 2222:本地:22 云服务器<br/>外网访问 云:2222 → 家里主机:22"]
  D["动态转发 -D (SOCKS)<br/>ssh -D 1080 跳板机<br/>浏览器设 socks5://127.0.0.1:1080"]
  W["反向隧道 / 多跳板<br/>ProxyJump / ProxyCommand<br/>先 hop 到跳板再到目标机"]

对应 sshd_config 控制项:

指令控制的功能跳板机常用值
AllowTcpForwarding local-L 和 -R 的总开关;local 只允许本地转发local(内网穿透禁止 -R 时)
GatewayPorts clientspecified-R 的绑定地址;clientspecified 允许客户端显式写 *:2222内网穿透才开,默认 no
PermitOpen host:port host2:port2允许 -L 只转发到哪些目标白名单模式,严格控制穿透范围
DisableForwarding yes一键关所有转发普通业务服务器推荐

2.10 SFTP / SCP 子系统

指令作用说明
Subsystem sftp /usr/libexec/openssh/sftp-server注册 SFTP 子系统路径Debian 是 /usr/lib/openssh/sftp-server;写错了 sftp 直接连不上
Subsystem sftp internal-sftp用 sshd 内置的 sftp 实现(不是外部程序)推荐 + ChrootDirectory 的唯一正确组合,性能更好、权限检查更严
ChrootDirectory /home/sftp/%u把用户锁在指定目录(chroot jail)① 目录属主必须是 root:root;② 目录权限不能高于 755(不能有任何写权限给非 root);③ 用户要上传文件必须在里面再建一个”用户可写子目录”
ForceCommand internal-sftp和 ChrootDirectory 配合:该用户只能用 sftp,不能起 shell

🔥 最常见 Chroot 报错:fatal: safely_chroot: stat("/home/sftp/alice"): Permission denied。十有八九是目录属主不是 root,或目录权限写了 775/777。严格检查 stat /home/sftp/alice:Uid 必须是 0( root ),Mode 不能有 w 给 group/other。

2.11 其他高频指令


指令作用
UseDNS no对客户端 IP 反解 DNS 做 host 匹配公网机建议 no;慢连接头号杀手,DNS 一挂每次登录慢 10 秒
GSSAPIAuthentication noKerberos 认证没搭 AD/FreeIPA 就关,否则每次登录尝试 GSS 失败耗时间
AcceptEnv LANG LC_*允许客户端把哪些环境变量带进服务端只放行 LANG/LC_,不要随便加 PATH,小心 PATH 注入
SendEnv(客户端)—和 AcceptEnv 配对;要传变量两边都得开
StrictModes yes检查 .ssh 目录与 authorized_keys 的权限/属主必须 yes,关了会忽略权限错误,安全风险
IgnoreRhosts yes忽略 .rhosts / .shosts 信任文件必须 yes,老式 rlogin 遗留
HostbasedAuthentication no基于主机的信任(类似 rsh)必须 no
PermitUserEnvironment no是否读 ~/.ssh/environment 文件必须 no,防止 PATH/LD_PRELOAD 注入
PidFile /var/run/sshd.pidsshd 主进程 PID 路径不用改
Ciphers / MACs / KexAlgorithms / HostKeyAlgorithms已在 2.3 节讲过配合 ssh-audit 扫描建议值
CASignatureAlgorithmsCA 签名允许的算法(证书登录)用 SSH 证书才关心
TrustedUserCAKeys /etc/ssh/ca.pub受信任的用户 CA 公钥(证书登录)SSH 证书体系必开

四、Match 块:按用户/组/来源 IP 精细化差异化配置

当你想”所有用户都禁密码,唯独监控机那个专门拉指标的用户只能来自 10.0.0.0/24 且能密码登录”,全局开关做不到,用 Match 块:

# ========== 全局 ==========
PasswordAuthentication no
PermitRootLogin no
DisableForwarding yes
X11Forwarding no

# ========== 只对 ops 组成员生效:允许端口转发做跳板 ==========
Match Group ops
    AllowTcpForwarding yes
    AllowAgentForwarding no
    PermitTTY yes

# ========== 只对监控账号 zabbix:只能从监控机来 + 强制执行脚本 ==========
Match User zabbix Address 10.0.1.15,10.0.1.16
    PasswordAuthentication yes
    PermitRootLogin no
    PermitTTY no
    ForceCommand /usr/local/bin/zabbix-agent-check.sh
    DisableForwarding yes

# ========== 只对 sftpusers 组:chroot + 只能 sftp ==========
Match Group sftpusers
    ChrootDirectory /sftp/%u
    ForceCommand internal-sftp
    X11Forwarding no
    AllowTcpForwarding no
    PermitTTY no

Match 块可使用的判断条件:

Match 条件含义示例
User用户名(通配 *)User alice, deploy_*
Group用户组Group ops,!temp_ops
Host客户端输入的 Host 名(注意是客户端指定、不是反解 DNS)Host *.internal.corp
Address客户端源 IP / CIDRAddress 10.0.0.0/8,!10.0.0.99
LocalAddress服务器端连接到的 IP(多网卡服务器)LocalAddress 192.168.122.1
All以上都匹配All

验证 Match 是否命中:sshd -T -C user=zabbix,addr=10.0.1.15,host=gateway 观察 PasswordAuthentication 是否变成 yes。

五、四套面向生产的配置模板

4.1 模板 A:通用最小化(内网业务服务器)

适用:普通 Web/DB/K8s worker,运维走跳板机,公网不暴露 22。

# /etc/ssh/sshd_config
Port 22
ListenAddress 0.0.0.0
ListenAddress ::

HostKey /etc/ssh/ssh_host_ed25519_key
HostKey /etc/ssh/ssh_host_rsa_key
HostKey /etc/ssh/ssh_host_ecdsa_key

SyslogFacility AUTHPRIV
LogLevel VERBOSE

LoginGraceTime 45
MaxStartups 20:30:60
MaxAuthTries 3
ClientAliveInterval 60
ClientAliveCountMax 3

PermitRootLogin no
AllowGroups ops deploy
DenyUsers root test

PubkeyAuthentication yes
AuthorizedKeysFile .ssh/authorized_keys
PasswordAuthentication no
PermitEmptyPasswords no
KbdInteractiveAuthentication no
UsePAM yes
StrictModes yes
IgnoreRhosts yes
PermitUserEnvironment no

X11Forwarding no
PrintMotd no
PrintLastLog yes
UseDNS no
GSSAPIAuthentication no

# 9.0+ 一行关掉所有转发
DisableForwarding yes
# 如果 OpenSSH < 9.0,用等价写法:
# AllowTcpForwarding no
# AllowAgentForwarding no
# AllowStreamLocalForwarding no
# PermitTunnel no

AcceptEnv LANG LC_*
Subsystem sftp  internal-sftp

4.2 模板 B:堡垒机 / 跳板机(公网暴露、支持局部转发)

适用:公网可直达,用户需从本机跳板到内网服务器。

Port 22
Port 22022                      # 双端口,防 22 被防火墙误封
ListenAddress 0.0.0.0
TCPKeepAlive yes

LoginGraceTime 30
MaxAuthTries 3
MaxStartups 30:50:80
ClientAliveInterval 30
ClientAliveCountMax 3
Banner /etc/ssh/banner

PermitRootLogin no
AllowGroups ops
AllowTcpForwarding local        # 只允许 -L 本地转发(跳板→内网),禁止 -R 把内网暴露出去
AllowAgentForwarding no         # 严禁借 agent,跳板机被攻破也不能借私钥
X11Forwarding no
PermitTunnel no
GatewayPorts no

PubkeyAuthentication yes
PasswordAuthentication no
KbdInteractiveAuthentication yes
# 强 2FA:必须先公钥,再 OTP
AuthenticationMethods publickey,keyboard-interactive
UsePAM yes

LogLevel VERBOSE
UseDNS no
GSSAPIAuthentication no

# 只允许 ops 组的端口转发白名单
Match Group ops
  PermitOpen 10.0.0.100:6443 10.0.0.101:6443 10.0.0.10:* 10.0.0.20:3306

二次验证的 Google Authenticator PAM 安装:

# RHEL
dnf install -y google-authenticator qrencode
# Debian
apt install -y libpam-google-authenticator
# 每个要启 2FA 的用户自己执行:
google-authenticator -t -d -f -r 3 -R 30 -W
# /etc/pam.d/sshd 首行加:
#   auth required pam_google_authenticator.so nullok
# nullok = 没配过的用户先跳过;全部用户配置完了去掉 nullok。

4.3 模板 C:Git / CI 服务器(纯密钥 + 多用户 + 禁用交互 TTY)

PermitRootLogin no
AllowGroups gitlab-runner deployer jenkins

PubkeyAuthentication yes
AuthorizedKeysCommand /opt/bin/fetch-authkeys.sh
AuthorizedKeysCommandUser nobody
PasswordAuthentication no
KbdInteractiveAuthentication no

X11Forwarding no
DisableForwarding yes
PermitTTY no

# 个别 CI 账号需要 ssh 进目标机部署,局部开转发
Match Group deployer
    AllowTcpForwarding local
    PermitTTY yes

4.4 模板 D:SFTP-only 用户组(chroot 隔离上传下载)

# /etc/ssh/sshd_config
Subsystem sftp internal-sftp

Match Group sftpusers
    ChrootDirectory /sftp/%u
    ForceCommand internal-sftp
    X11Forwarding no
    DisableForwarding yes
    PermitTTY no
    PasswordAuthentication no
    PubkeyAuthentication yes

配套初始化脚本:

# 执行一次即可
groupadd sftpusers
useradd -m -G sftpusers -s /usr/sbin/nologin alice
install -d -o root -g root -m 755 /sftp/alice        # 755 属主 root,Chroot 要求
install -d -o alice -g sftpusers -m 700 /sftp/alice/uploads
# 给 alice 放公钥(chroot 后 home 还是 /home/alice,sshd 仍能读到 authorized_keys)
install -d -o alice -g alice -m 700 /home/alice/.ssh
cat >> /home/alice/.ssh/authorized_keys  # <-- 粘贴公钥
chown alice:alice /home/alice/.ssh/authorized_keys && chmod 600 /home/alice/.ssh/authorized_keys

六、systemd 与日常运维命令速查

# 重载配置(不改端口、证书、监听地址时推荐,零中断)
systemctl reload sshd
# 重启(改了端口、HostKey 等才需要)
systemctl restart sshd
# 开机自启
systemctl enable sshd
# 看状态 + 最近日志
systemctl status sshd
journalctl -u sshd -n 50 --no-pager
# 持续追踪登录失败(暴力破解实时监控)
journalctl -u sshd -f | grep -Ei 'fail|invalid|error'
# 谁登过(最近)
last -n 30
lastlog -u ops
# 谁试图登但失败(btmp 日志)
lastb -n 30
# 把所有正在连接的会话列出来(含来源 IP、登录用户、pty)
ss -tnp | grep sshd
# 临时 ban IP(fail2ban 自动做)
fail2ban-client status sshd
fail2ban-client set sshd banip 1.2.3.4

七、15 个高频排障场景

6.1 改完配置自己登不上了 → 双会话+reload 流程(见 0.2 节)

6.2 客户端:Connection closed by 1.2.3.4 port 22

服务器侧原因常见:MaxStartups 被打满、SELinux 在强制模式下把 sshd 限制了、AllowUsers 没把你放进去。 排障:服务器跑 journalctl -u sshd -e,同时 sshd -d 前台调试模式看实时拒绝原因。

6.3 Permission denied (publickey).——明明公钥放对了

用 -vvv 抓协商细节:

ssh -vvv ops@目标机 2>&1 | grep -Ei 'offer|publickey|sign|accept|fail|pubkey|auth'

服务器侧常见根因列表:

┌───────────────────────────────────────────────────────────────────┐
│  公钥认证失败 80% 的根因(按概率排序)                            │
├──────────────────────────────────────────────────────────────────┤
│ 1) StrictModes 校验未通过,sshd 默默忽略 authorized_keys          │
│    $ ls -ld ~ ~/.ssh ~/.ssh/authorized_keys                       │
│    ~ = 700, .ssh = 700, authorized_keys = 600                     │
│    全部属主 = 登录用户本人,不能是 root                           │
│ 2) authorized_keys 里粘贴公钥时多了换行/空格/隐藏字符             │
│ 3) 服务端 PubkeyAuthentication = no(少见,但可能被 Match 改)    │
│ 4) 用户 home 目录是 NFS,且 no_root_squash 下 sshd root 不可读    │
│ 5) SELinux:restorecon -Rv ~/.ssh/                                │
│ 6) 客户端私钥算法不在服务端 PubkeyAcceptedAlgorithms 内           │
│ 7) 私钥设了密码但 ssh-agent 没载入,导致一直在输入私钥密码        │
└───────────────────────────────────────────────────────────────────┘

6.4 Server refused our key 但日志里啥都不写

LogLevel INFO 调 VERBOSE,reload 之后再连一次:

sed -i 's/^LogLevel INFO/LogLevel VERBOSE/' /etc/ssh/sshd_config
systemctl reload sshd
journalctl -u sshd -f

VERBOSE 会打印 userauth_pubkey: key type ssh-rsa not in PubkeyAcceptedAlgorithms [preauth] 这种致命线索。

6.5 用 ssh-rsa 老密钥无法登录(OpenSSH 8.8+ 默认禁用 SHA-1 签名)

OpenSSH 8.8 起把 ssh-rsa(注意:是 RSA 密钥的 SHA-1 签名算法,不是 RSA 密钥本身)从默认列表剔除了。 正确修复不是改 sshd_config 全局放开,而是升级客户端算法:把客户端密钥换成 ed25519:

ssh-keygen -t ed25519 -C "work email"   # 推荐
ssh-keygen -t rsa -b 4096                # 旧客户端兼容也得 4096 位

如果必须短期兼容(客户端是老设备),局部开一个用户:

Match User olddevice Address 10.1.2.3
    PubkeyAcceptedAlgorithms +ssh-rsa

6.6 每次登录都要等 10 秒才出密码提示

90% 是 UseDNS yes 导致服务端反解超时。改 UseDNS no + GSSAPIAuthentication no reload 即可。

6.7 idle 一段时间就断:ClientAliveInterval 配置正确但还是断

客户端也得开 ServerAliveInterval,两边配合;中间 NAT/LB 也会主动清连接,此时只能缩短客户端探测(30s 一次):

# ~/.ssh/config 客户端
Host *
  ServerAliveInterval 30
  ServerAliveCountMax 3
  TCPKeepAlive yes

6.8 sftp 连上就断,报 Received message too long

99% 原因是用户 shell 的 rc 脚本(.bashrc .profile)里有 echo 输出,被 sftp 客户端当成协议数据了。改 rc 文件,让非交互 shell 直接 return:

# 在 .bashrc 第一行加:
[[ $- != *i* ]] && return

6.9 ChrootDirectory 报错 Permission denied

  1. Chroot 路径的每一层属主必须是 root;2) 每一层不能有 group/other 的写权限。正确权限样例:
drwxr-xr-x root root /sftp
drwxr-xr-x root root /sftp/alice
drwx------ alice sftpusers /sftp/alice/uploads  ← 只有这个子目录是 alice 可写

6.10 跳板机上 -L 端口转发被拒绝

检查:AllowTcpForwarding 是否允许;PermitOpen 是否把目标加入白名单;DisableForwarding 是否不小心开在全局。sshd -T -C user=你的账号 核对最终值。

6.11 -R 远程转发绑定到 0.0.0.0 了还是不能从外部访问

GatewayPorts 默认 no,此时 -R 只绑 127.0.0.1。改成 GatewayPorts clientspecified,并在客户端用:

ssh -R '*:2222:127.0.0.1:22' 云端IP    # 注意 *:2222 才绑全网,直接写 2222 默认绑 127

同时服务器防火墙要放行 2222 入站。

6.12 bad ownership or modes for chroot directory

见 6.9;另外注意 internal-sftp 要配对,不要写外部 /usr/libexec/sftp-server 再配 Chroot,会有权限问题。

6.13 PAM: Authentication failure for root from 1.2.3.4,但密码是对的

PAM 限制:/etc/security/access.conf(Deny root 从非控制台登录)、/etc/security/limits.conf(maxlogins)、faillock(连续输错被临时锁)。看 PAM 日志:journalctl -u sshd -e 里会写具体哪个 PAM 模块拒绝了。 解锁 faillock:

faillock --user root --reset
# 老发行版
pam_tally2 --user root --reset

6.14 Banner 设置了但不显示

Banner 是密码提示前显示;用 sftp/非交互会话、或者用了 GSSAPIAuthentication 先尝试快速认证都会跳过 banner 文本;此外文件要所有用户可读:chmod 644 /etc/ssh/banner。

6.15 生产怎么验证配置?安全基线扫描工具

# ssh-audit:一键看算法、证书、版本、已知漏洞
git clone https://github.com/jtesta/ssh-audit.git && cd ssh-audit
./ssh-audit.py note.robinzen.cn:22

# 另一个:Lynis 系统审计(含 SSH 章节)
lynis audit system --quick --tests-from-group ssh

八、附:OpenSSH 关键版本特性速记

版本关键变化
OpenSSH 7.0禁用 ssh-dss DSA 密钥;引入 ssh-ed25519 主机密钥
OpenSSH 7.4移除 SSHv1 协议支持;StreamLocalBindUnlink;新增 DisableForwarding 指令;修复 UseRoaming(CVE-2016-0777)
OpenSSH 8.2U2F/FIDO 支持(ssh-keygen -t ecdsa-sk);弃用 ssh-rsa(SHA-1)
OpenSSH 8.5PubkeyAcceptedKeyTypes 改名 PubkeyAcceptedAlgorithms
OpenSSH 8.8默认禁用 ssh-rsa(SHA-1) 签名算法(老客户端连不上的根因)
OpenSSH 9.0新增 DisableForwarding yes;scp 默认改用 sftp 协议(兼容 SFTP-only 服务器)
OpenSSH 9.2+sshd_config.d/*.conf 普遍支持;修正 PermitOpen 匹配 CIDR

💡 本机查看版本:sshd -V(部分发行版用 ssh -V,显示同一个版本号)。


一句话总结:PermitRootLogin no + PasswordAuthentication no + AllowGroups/AllowUsers + DisableForwarding + 双会话 reload = sshd 生产安全的 5 个必选项;剩下的配置项就按本文对照表按需打开。

问问 AI