我先把结论放在前面:这篇文章分享的是在 CentOS 环境下把 OpenSSH 升级到 10.2p1 的完整过程,包括为什么必须升、编译前要准备什么、编译参数怎么理解、遇到问题怎么排查。如果你手头有跑了好几年没动过的内网服务器,或者最近在做等保整改被扫出 SSH 漏洞,那么这篇内容可以直接拿来当操作手册用,同时我也会把我踩过的坑一并写出来,免得你重复交学费。
OpenSSH 属于那种“平时想不起,出事就慌神”的基础组件。多数服务器装完系统之后就再没管过它,直到安全扫描报告里出现几个高危 CVE,才意识到版本老得离谱。但直接升级又有风险:万一编译失败、配置错误、服务起不来,远程登录一断,机房又没人帮忙按电源键,这个锅就得自己背。所以我的处理思路一直是:先分析现状,再补齐依赖,然后编译安装,最后反复验证。每一步都留好后路,哪怕出了问题也能快速回滚。
1. 升级前必须要搞清楚的几个问题
1.1 为什么老版本 OpenSSH 越来越危险
很多 CentOS 服务器默认的 OpenSSH 还停留在 7.4、7.8 这类老版本上。不是说这些版本不能用,而是它们太老了。这几年公开披露过好几起跟 SSH 相关的严重漏洞,有的是认证绕过,有的是提权,有的是远程代码执行。一旦公网开放了 22 端口,又没有及时上线补丁,风险就摆在明面上。更麻烦的是,不少 CentOS 长期停留在旧版本,官方源里并不会主动提供新版 OpenSSH 的 RPM 包,所以源码编译安装反而成了最可控的升级手段。
OpenSSH 10.2p1 这个版本属于比较新的稳定分支,支持更强的加密算法、默认关闭了一批不安全的旧算法,同时也修掉了大量历史遗留问题。安全扫描工具对它的评定结果通常比较干净,这也是很多内网系统整改时要求升级到这个版本的原因。只要编译时依赖选对、配置项不搞乱,整体收益非常明显。
1.2 升级之前想清楚这三个问题
第一,你的应急登录通道是否可靠。升级过程中一旦 sshd 重启失败,当前连接被断,又没其他入口,你就只能去物理控制台操作。这个风险最容易被忽视,也最容易出事故。第二,备份是否完整。二进制文件、配置文件、服务管理脚本都要留底,别只备份一个 sshd_config 就以为万事大吉。第三,依赖环境是否满足。OpenSSH 编译依赖 OpenSSL、zlib、PAM 等组件,版本太低会直接导致 configure 报错,或者编译出来运行不稳定。这三件事没想清楚之前,不要轻易动生产机器。
1.3 升级方案选型:覆盖安装还是自定义目录
实际操作中我主要会考虑两种方案。第一种是直接编译安装到 /usr 目录,也就是把系统原来的 ssh、sshd 等命令覆盖掉,这种方式干净利落,重启 sshd 之后所有依赖默认路径的工具都能正常工作。第二种是装到 /usr/local/openssh 这种自定义目录,再通过软链接或修改 PATH 的方式去使用,好处是升级失败可以快速移除,坏处是路径配置比较琐碎,服务管理文件也要额外调整。
我个人的习惯是先在自定义目录编译一遍,确认版本、依赖、配置都没问题之后,再备份原文件并执行覆盖安装。这样既能验证新版包的可用性,又能避免一上来就把系统原有配置搞乱。如果你对这套流程非常熟,也可以一步到位直接 --prefix=/usr 编译,但前提是你对回滚方案有充分的把握。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 升级前的环境准备与备份
2.1 确认当前系统版本和 OpenSSH 版本
这一步没啥技术含量,但特别重要。先执行下面几个命令,把环境信息记录下来,后续检查和回滚都会用到。
bash复制cat /etc/os-release
ssh -V
sshd -V
对 CentOS 来说,我遇到过最常见的组合是 CentOS 7 + OpenSSH 7.4。这个版本在编译新版 OpenSSH 时会存在一个比较棘手的问题:系统自带 OpenSSL 是 1.0.2,而 OpenSSH 10.2p1 编译时一般要求 OpenSSL 1.1.1 或更高版本。如果看到 configure 阶段直接报错,别慌,这就是核心冲突点。有条件的话,我建议先把系统升级到 CentOS 8/9 这种自带 OpenSSL 1.1.1 或 3.0 的版本;如果系统版本没法动,那就要在编译时单独处理 OpenSSL 路径,比如先编译一个新版 OpenSSL 到 /usr/local/openssl,再用 --with-ssl-dir 指定过去。
2.2 安装编译依赖
在 CentOS 上编译 OpenSSH,最常用的依赖包就是这些:
bash复制yum -y install gcc make zlib-devel openssl-devel pam-devel rpm-build libtool perl-core krb5-devel ncurses-devel
如果你是在最小化安装的 CentOS 上操作,建议把 gcc 和 make 放在最前面,因为后续所有编译动作都依赖它们。zlib-devel 和 openssl-devel 负责提供头文件和链接库,不装的话 configure 阶段会直接退出。pam-devel 是编译 PAM 支持时必需的,没有它,编译出来的 sshd 虽然也能用,但密码认证这块很容易出问题,表现为怎么输密码都登录失败,日志里又看不到明确原因。krb5-devel 是为了 GSSAPI 认证支持,如果你不需要 Kerberos,也可以不装,但装上一劳永逸,避免以后想用的时候又找不到头文件。
一个常见的坑是系统里有旧版本的 openssl-devel,与新版 OpenSSH 期望的版本不匹配,这时候不要强行卸载系统包,而是通过编译一个新版 OpenSSL 到独立目录来解决。具体做法后面会展开。
2.3 先开一条应急通道
这是我强烈建议不要省略的一步。因为升级 OpenSSH 的过程中,难免要重启 sshd、测试新配置,万一一句话写错,ssh 服务起不来,远程连接就彻底断了。我的做法是二选一:要么提前开启 telnet 服务作为备用登录通道,要么用 screen 或 tmux 保持一个常驻会话,避免编译安装过程中因为网络抖动或服务重启导致连接断开。
如果你选择 telnet,在 CentOS 上可以这么快速起一个:
bash复制yum -y install telnet-server
systemctl enable telnet.socket --now
注意,telnet 的密码是明文传输的,这个通道只在内网、只在升级期间临时用,升级完成、确认 sshd 正常之后要立刻关闭并卸载,别给生产环境留后门。如果你有带外管理卡或者虚拟机控制台权限,那就不需要开 telnet,直接用控制台登录就行。总之一句话:在你确认 sshd 已经稳定跑起来之前,永远不要把最后一个登录窗口关掉。
2.4 备份现有 SSH 配置与二进制
升级前备份这一步,除了常规的配置文件,还要把二进制也留下来。我一般会执行这样一串命令:
bash复制cp -rp /etc/ssh /etc/ssh.bak.$(date +%F)
cp -p /usr/sbin/sshd /usr/sbin/sshd.bak.$(date +%F)
cp -p /usr/bin/ssh /usr/bin/ssh.bak.$(date +%F)
cp -p /usr/bin/scp /usr/bin/scp.bak.$(date +%F)
配置文件里包含主机密钥,也就是 /etc/ssh/ssh_host_*_key 和对应的 .pub 文件,这些是服务器身份凭证,最好不要重新生成。因为如果主机密钥变了,客户端连接时会提示 host key 不匹配,尤其是老运维用 ssh 脚本批量登录的场景,容易引发连锁问题。所以我在实际升级时,默认都会保留原有密钥,不删除、不覆盖。
3. 获取源码并完成编译安装
3.1 下载 OpenSSH 10.2p1 源码包
OpenSSH 的 portable 版本源码通常发布在 OpenBSD 官网的镜像目录里,下载命令如下:
bash复制cd /usr/local/src
wget https://cdn.openbsd.org/pub/OpenBSD/OpenSSH/portable/openssh-10.2p1.tar.gz
tar -zxvf openssh-10.2p1.tar.gz
cd openssh-10.2p1
如果你的服务器是纯内网环境,没法直接从外网拉取源码,那就需要在一台能上网的机器上提前下载好源码包,再通过内部软件仓库、U盘或其他文件分发方式上传到目标机器上。这里顺带提醒一句:下载完之后最好校验一下文件哈希,避免源码包被篡改或者下载不完整,解压出来编译到一半才报错,很浪费时间。
3.2 编译参数到底该怎么理解
configure 这一行是整个升级过程中最核心的环节,参数选错了后面全是麻烦。我给一套常用的参数,然后逐个解释:
bash复制./configure \
--prefix=/usr \
--sysconfdir=/etc/ssh \
--with-pam \
--with-zlib \
--with-ssl-dir=/usr/local/openssl \
--with-privsep-path=/var/empty/sshd \
--with-md5-passwords
--prefix=/usr 表示安装到 /usr 目录,这样 ssh 和 sshd 会分别安装到 /usr/bin/ssh、/usr/sbin/sshd,与系统默认路径保持一致,之后启动和调用都不用做额外的 PATH 调整。--sysconfdir=/etc/ssh 是指定配置文件目录,这个必须和系统原有的 SSH 配置目录一致,否则 sshd 会去别的地方找配置,结果服务起来了但行为跟以前完全不一样。--with-pam 开启 PAM 认证,Linux 下用户密码认证、sudo、账户锁定都依赖它,不能省。--with-zlib 是为了压缩支持和完整性校验。--with-ssl-dir 指定 OpenSSL 安装路径,如果系统自带的 OpenSSL 版本满足要求,可以不写这个参数直接让系统去查;但如果你按照我的方案在 /usr/local/openssl 编译了新版本 OpenSSL,那么这里一定要写。
--with-privsep-path=/var/empty/sshd 是 OpenSSH 的特权分离目录。sshd 收到连接后会降权到普通用户运行,这个目录必须存在且权限正确,否则 sshd 启动失败。很多人在升级后遇到 Privilege separation user sshd does not exist 或者 directory not found 的报错,就是因为没处理这一步。CentOS 原本已经有了这个目录,但为了保险起见,我也会手动再创建并设置权限:
bash复制mkdir -p /var/empty/sshd
chmod 755 /var/empty/sshd
chown root:root /var/empty/sshd
--with-md5-passwords 是为了兼容一些老系统上 MD5 格式的账户密码哈希,在纯 Linux 环境里用不用差别不大,但加上之后兼容性更好。
3.3 一行一行看编译过程
configure 通过之后,执行编译和安装:
bash复制make -j$(nproc)
make install
-j$(nproc) 表示用 CPU 的全部核心并行编译,可以显著缩短编译时间。在云主机上如果分配了多核 CPU,这一条能帮你知道什么叫“坐火箭”。编译过程中如果出现类似 openssl/evp.h: No such file or directory 的报错,基本就是 OpenSSL 头文件路径没找对,你需要回头检查 --with-ssl-dir 设定的目录下是否真的有 include/openssl/evp.h。如果之前没有单独编译 OpenSSL,也可以尝试安装系统的 openssl-devel 包,再重新 configure。
make install 执行完之后,建议先检查一下二进制版本和动态库依赖:
bash复制ssh -V
which ssh
which sshd
ldd /usr/sbin/sshd | grep ssl
这一步能帮你确认安装是否真正生效,也能提前暴露动态库链接问题。比较典型的情况是 sshd 已经装到 /usr/sbin/sshd,但运行时报找不到 libcrypto.so.3,这种情况多半是新版 OpenSSL 装到了自定义目录而动态链接库路径没配置。
3.4 配置 sshd_config 并启动服务
升级后 sshd 并不会自动读取你原来的所有旧配置,虽然 --sysconfdir=/etc/ssh 会沿用已有配置目录,但新版默认配置和旧版还是有不少差异。我这次升级时习惯先备份原 sshd_config,再基于新版安装生成的 sshd_config 样例去修改,把关键项一一确认。
建议重点关注以下几项:
bash复制PermitRootLogin no
PasswordAuthentication yes
PubkeyAuthentication yes
PermitEmptyPasswords no
UsePAM yes
UsePAM yes 这里一定要确认是 yes。如果 PAM 没开,很多系统会碰到密码认证失败。PasswordAuthentication yes 要视你的运维习惯而定,如果都是密钥登录,可以保持 no,减少暴力破解面。但是升级切换期间,建议先临时开启密码认证,确认新版本能正常登录后,再改回比较严格的安全策略。
改完配置后,务必先执行配置自检:
bash复制sshd -t
这条命令不会启动服务,只会验证配置文件语法。如果输出为空,说明配置没问题。如果像我经常见到的那样报类似 Missing privilege separation directory: /var/empty/sshd,就说明特权分离目录权限不对,先处理目录再继续。
确认无误后重启服务:
bash复制systemctl restart sshd
systemctl status sshd
如果不出意外,sshd 已经跑在新版本上了。用 ss -tlnp | grep sshd 看一下端口监听是否正常,再另外开一个终端窗口测试连接,确认一切正常后再把之前开的 telnet 通道关闭。
4. 升级后的验证与常见问题排查
4.1 用实际连接验证升级结果
服务起来的下一步,就是做一次真实的远程连接测试。不要光坐在被升级的机器上敲 ssh -V 就觉得完事了,要从另一台机器上主动连接一次,把登录成功、认证方式、隧道建立这些环节都跑通。我通常的做法是:
bash复制ssh -vvv user@target_ip
-vvv 会输出非常详细的调试信息,包括客户端支持哪些算法、服务端返回哪些算法、最终选择哪一种、认证走到哪一步。如果连接不出意外,完全能兼容。有问题时这种调试信息也比只看服务端日志要直观得多。
如果客户端版本很旧,比如 Windows 7 自带的 OpenSSH 客户端,或者早期的 PuTTY,那连接新版 OpenSSH 服务时可能出现类似 Unable to negotiate a key exchange method 这样的错误。这是因为新版本默认关闭了旧版算法的协商能力,旧客户端没有共同算法可用。这里我的建议是优先升级客户端,而不是在服务端重新开启旧算法。毕竟安全升级的目标就是把不安全的旧算法废掉,如果为了兼容老客户端又把它们打开,整体收益就打了折扣。
4.2 最常见的几个报错与解决思路
我把这些年升级 OpenSSH 遇到过的典型问题整理成一个速查表,供你排查时对照:
| 现象 | 可能原因 | 常见处理办法 |
|---|---|---|
| configure 报 OpenSSL 版本过低 | 系统 OpenSSL 低于 1.1.1 | 编译新版 OpenSSL 后使用 --with-ssl-dir 指定路径 |
| 启动 sshd 报目录不存在 | 特权分离目录缺失或权限错误 | 创建 /var/empty/sshd,设置 755 权限属主 root |
| 密码登录总是失败 | PAM 未编译进去或配置文件不匹配 | 重新编译时加 --with-pam,确认 UsePAM yes |
| 客户端连接提示算法不匹配 | 客户端版本太旧 | 升级客户端,不建议在服务端重新开启旧算法 |
| SELinux 阻止了非标准端口 | 改了 Port 但没有更新 SELinux 策略 | 用 semanage port -a -t ssh_port_t -p tcp 新端口 |
| sshd 服务起不来但配置语法正确 | 旧版二进制文件被占用或动态库缺失 | 重启机器或检查 ldd /usr/sbin/sshd |
| 主机密钥指纹变化 | 原有 /etc/ssh/ssh_host_* 被覆盖 |
恢复备份或重新生成密钥后通知客户端更新 |
第一个问题我已经在前文讲过了,这里重点说下 SELinux。CentOS 默认会开启 SELinux,如果你只是把 SSH 端口从 22 改到 2222,又没有配置 SELinux 策略,那么 sshd 启动后端口可能根本没在监听,或者连接直接被拒。这时候用 journalctl -u sshd 能看到 SELinux is preventing 字样的日志。解决办法是安装 policycoreutils-python-utils 包,然后执行:
bash复制semanage port -a -t ssh_port_t -p tcp 2222
再用 semanage port -l | grep ssh 确认端口已经加入策略,再重启 sshd 就能正常监听了。这个问题在测试环境里经常被忽略,部署到真实生产环境时会突然冒出来。
4.3 升级失败后的回滚与应急恢复
最坏的情况:ssh 服务起不来,远程连接已经断开,telnet 也没来得及开。这时候只能靠控制台或者带外管理卡登录服务器。登录进去后先把旧版本二进制和配置文件恢复回来:
bash复制cp -p /usr/sbin/sshd.bak.$(date +%F) /usr/sbin/sshd
cp -p /usr/bin/ssh.bak.$(date +%F) /usr/bin/ssh
cp -rp /etc/ssh.bak.$(date +%F)/* /etc/ssh/
systemctl restart sshd
如果没有备份二进制,但安装了原版本的 RPM 包,也可以通过 yum reinstall openssh-server openssh-clients 来恢复原状。不过这种方式依赖 yum 源里的版本,如果你已经修改过 yum 源里的 openssh 包,可能恢复的也不是原本版本,所以备份永远是最可靠的后路。
我升级了大几十台服务器后最深的体会是:OpenSSH 升级本身不难,难的是整个过程中的链路安全和意外处置。每一步都要想到“如果这一步挂了,我怎么退回去”。只要把应急通道和备份准备好,哪怕出了小问题也只是花几分钟恢复的事。
4.4 升级完别忘了做这些收尾检查
确认 sshd 正常之后,我会习惯性做一轮收尾检查,把安全隐患一并处理掉。第一,确认系统里的 sshd、ssh、scp、sftp 都指向新版二进制,有的服务器上因为历史原因存在多个 OpenSSH 实例,很容易漏下一个。第二,检查 sshd_config 里是否还开着 PermitRootLogin yes,如果只是内网测试环境还能接受,生产建议改成 prohibit-password 或 no。第三,关闭之前临时开启的 telnet 通道,并确认端口没有残留监听。第四,运行一次完整的安全扫描或者至少用 ssh-audit 这样的工具查看加密算法结果,确认服务端没有启用已知弱算法。
另外,升级之后很多 CentOS 系统会在某个时间点被 yum update 再次覆盖回系统自带 OpenSSH 版本。如果不想被覆盖,建议在 yum 配置中禁用 OpenSSH 相关包的更新,或者在升级完成后使用 chattr +i 加锁关键文件。这一步很容易被忽略,我亲眼见过有人在升级完毕后业务正常,隔了几天跑了一次系统更新,SSH 又变回旧版本,导致安全扫描又拉警报。
写在最后的几个实操建议
这篇文章基本把我这次 CentOS 升级 OpenSSH 10.2p1 的完整过程写清楚了,从环境检查、依赖准备、编译安装,到配置细节、问题排查、回滚方案,都有涉及。如果你打算照着做,我最后再提几点个人经验。
一是不要为了省时间跳过应急通道,哪怕是一次升级。因为 sshd 重启失败的概率远比你想的要高,真正出问题时如果连控制台都没有,才是真正的灾难。二是选择编译参数时要理解参数含义,不要从网上随手抄一段就执行,因为不同系统、不同依赖环境下合适的参数组合完全不同。三是升级完成后一定要用另一个终端或另一台机器实测连接,只有真实连接验证过,才能算升级完成。四是做好二进制和配置文件的备份,哪怕升级非常顺利,备份文件和命令留一段时间再删,比事后后悔强得多。
如果你在升级过程中遇到了这篇里没提到的问题,比如某个旧客户端兼容不过来,或者新版本 SSH 与某款堡垒机选项冲突,建议先用 sshd -T 查看服务端生效配置,再打开 ssh -vvv 抓客户端协商过程,基本都能定位到问题出在哪一层。这套排查思路无论版本怎么变,都比在网上盲目搜一条命令更管用。
