☰
CentOS 7.9内网环境OpenSSH离线升级完整实战指南
2026/10/7 3:28:35 网站建设 项目流程

做运维的都知道,只要系统上了等保或者安全扫描,OpenSSH升级几乎是躲不过去的一道坎。尤其是RHEL / CentOS 7.9这种已经进入生命周期末期的系统,自带的OpenSSH版本普遍偏老,扫描器随便照一下就是CVE列表里躺了好几年的大洞。更麻烦的是生产环境往往在内网,隔离网段根本连不上外网yum源,想升级只能把源码包拷进去离线编译。最近我正好接手了一批7.9机器的OpenSSH离线升级任务,前前后后踩了不少坑,把完整流程和排查思路整理出来,给同样被这个问题困住的兄弟做个参考。

这篇文章覆盖从环境盘点、依赖编译、OpenSSH安装到服务切换的完整链路,所有操作都在纯内网环境验证过,适用于RHEL 7.9和CentOS 7.9(x86_64架构)。新手可以照着一步步操作,老手可以重点看第四部分的坑点总结,尤其是升级后登录不上这种翻车现场怎么救回来。

1. 为什么要离线升级OpenSSH:背景与坑点

1.1 等保合规与漏洞背景

先说最现实的驱动力。等保2.0和行业安全基线检查里,SSH服务版本是必查项。OpenSSH 7.4这个CentOS 7.9自带的版本,在漏洞库里挂着不少中高危CVE,包括但不限于用户名枚举、信息泄露、权限提升这类问题。扫描报告一旦出来,限期整改是跑不掉的。

内网机器还有个特点——系统盘和网络都是隔离的,安全团队只给一个整改窗口,比如周末凌晨两小时。这就意味着升级方案必须一次成功,而且不能影响现有业务连接。还有一个隐藏问题:内网机器很多是用了多年的老环境,glibc版本、编译工具链都可能不全,离线升级不是简单把rpm包装上就完事。

1.2 离线环境下的升级难点分析

离线升级OpenSSH,本质上是解决三个问题:依赖缺失、版本兼容、服务连续性。

依赖这块,OpenSSH编译需要OpenSSL、zlib、PAM开发库。OpenSSL尤其关键,OpenSSH的加密通道完全依赖它。CentOS 7.9自带的OpenSSL是1.0.2k,这个版本本身也有漏洞,所以升级OpenSSH时通常要一起把OpenSSL升掉。但OpenSSL又不是随便乱升的——yum、curl、wget这些系统组件都动态链接了系统OpenSSL,不能直接替换系统库,否则yum可能直接罢工。

版本兼容上,OpenSSH 8.8以上版本默认禁用了ssh-rsa算法(SHA-1),老客户端会连不上。很多内网环境里还有老设备用ssh-rsa登录,升级完新版本后这些设备就是"Connection closed by remote host",得提前想好对策。

服务连续性最让人头疼。升级sshd过程中一旦配置出错或者服务起不来,远程连接就断了,而人还在机房外面。所以保底通道是必须的——telnet虽然明文不安全,但关键时刻能救命。

1.3 方案选型:源码编译 vs 第三方RPM

升级OpenSSH有两条主流路线:一是拿源码编译,二是找第三方维护的RPM包(比如知名第三方仓库)。

我的建议是:纯内网环境优先源码编译。理由有三个:第一,第三方RPM依赖关系复杂,离线环境下搞依赖树本身就容易翻车;第二,源码编译可以把OpenSSL、OpenSSH都安装到自定义路径,不影响系统默认组件,风险可控;第三,等保整改往往有版本下限要求,源码编译可以精确选版本。

源码编译也有缺点——编译期间需要gcc、make等工具链,有些精简安装的机器连gcc都没有。这个问题在后面准备工作里会细说。

2. 动手前的准备工作:依赖盘点与保底通道

2.1 版本盘点与环境检查

在碰任何源码包之前,先把目标机器的家底摸清楚。需要确认四件事:系统版本、架构、当前OpenSSH版本、当前OpenSSL版本。

# 查看系统版本 cat /etc/redhat-release # 查看架构 uname -m # 查看当前OpenSSH版本 ssh -V 2>&1 # 查看当前OpenSSL版本 openssl version

我这次操作的机器都是CentOS 7.9 x86_64,自带的OpenSSH 7.4p1,OpenSSL 1.0.2k。升级目标是OpenSSH 9.6p1,同时把OpenSSL升到1.1.1w。

还要检查编译工具链:

# 检查gcc、make是否安装 gcc --version make --version # 检查PAM开发库 rpm -qa | grep pam-devel

如果没有gcc,这就麻烦了。内网环境没法yum install,只能找同版本系统的安装光盘或者yum仓库缓存来补。CentOS 7.9的安装ISO里自带gcc、make这些包,mount上去用rpm -ivh逐个安装就行。这一步虽然繁琐,但务必在升级前做好,否则编译到一半发现没编译器,进退两难。

2.2 依赖软件包的获取清单

离线环境所有东西都要提前准备好。我列一下我这次用的源码包清单和下载地址(在内网外的机器上下好,再拷进去):

软件包版本说明
zlib1.2.13OpenSSH编译依赖
openssl1.1.1w加密库,OpenSSH依赖
openssh9.6p1目标升级版本
openssl-devel1.0.2k(系统自带)如果系统里没有,需要从ISO补
pam-devel系统自带没有的话也要从ISO补

源码包下载后,用MD5/SHA256校验一下完整性,然后传到目标机器的/usr/local/src目录。传文件的方式,内网环境常用scp、ftp,或者直接U盘拷。U盘拷贝时注意挂载格式,FAT32单文件不能超过4GB,不过这几个源码包都没那么大,问题不大。

2.3 提前开通telnet保底通道

这是整个升级过程中最重要的保险措施,没有之一。OpenSSH升级过程中任何一步出错,sshd服务可能起不来,telnet就是唯一的救命通道。

CentOS 7.9默认没装telnet-server,需要提前装好并启动:

# 检查是否已安装 rpm -qa | grep telnet-server # 如果没有,从系统ISO或镜像站下载安装: # telnet-server-0.17-64.el7.x86_64.rpm # telnet-0.17-64.el7.x86_64.rpm # xinetd-2.3.15-14.el7.x86_64.rpm rpm -ivh telnet-server-*.rpm telnet-*.rpm xinetd-*.rpm # 配置xinetd管理telnet cat > /etc/xinetd.d/telnet << 'EOF' service telnet { disable = no flags = REUSE socket_type = stream wait = no user = root server = /usr/sbin/in.telnetd log_on_failure += USERID } EOF # 启动服务 systemctl restart xinetd systemctl enable xinetd

防火墙放行telnet端口(默认23):

firewall-cmd --permanent --add-port=23/tcp firewall-cmd --reload

然后从另一台机器测试telnet连接,能登录成功再继续后面的操作。

注意:telnet是明文传输,只在升级期间临时开放,升级完成后必须立即关闭。如果公司安全策略不允许开telnet,退而求其次是准备好物理console(机房KVM),或者确认带外管理卡(如iLO/iDRAC)可用。

2.4 下载源码包与传输

源码包版本选择上,我建议OpenSSH选9.x的当前最新稳定版,OpenSSL选1.1.1系列(1.1.1w是最终维护版)。OpenSSL 3.x在CentOS 7.9上编译会面临glibc版本过老的问题,尽量别碰。

下载完成后,我习惯把所有源码包放在/usr/local/src,统一解压:

cd /usr/local/src tar -zxvf zlib-1.2.13.tar.gz tar -zxvf openssl-1.1.1w.tar.gz tar -zxvf openssh-9.6p1.tar.gz

解压后建议先看下编译说明(README、INSTALL文件),确认有没有特别要求。OpenSSH 9.6的INSTALL文件里会写明依赖的最低版本,我之前见过有人不看说明直接编译,因为PAM版本不够导致configure直接失败。

3. 离线升级OpenSSH完整实操

3.1 编译安装zlib

zlib是基础库,OpenSSL和OpenSSH都依赖它。虽然CentOS 7.9系统里已经有zlib,但版本比较老(1.2.7),为了兼容性我们把它安装到独立目录。

cd /usr/local/src/zlib-1.2.13 ./configure --prefix=/usr/local/zlib make -j4 make install

-j4是启用4个并行编译任务,机器核多的话能明显加快速度。如果机器的CPU核数少,可以改成-j2或直接不写这个参数。

编译完成后,zlib会被安装到/usr/local/zlib,不影响系统原有的zlib库。注意编译OpenSSL时要用--with-zlib-lib=/usr/local/zlib/lib这样的参数指定它的位置。

3.2 编译安装OpenSSL到自定义路径

OpenSSL是OpenSSH的核心依赖,也是最容易出问题的环节。直接覆盖系统OpenSSL会引发连锁反应(yum、wget都会坏),所以这里必须安装到自定义路径。

cd /usr/local/src/openssl-1.1.1w ./config --prefix=/usr/local/openssl --openssldir=/usr/local/openssl shared zlib \ --with-zlib-lib=/usr/local/zlib/lib --with-zlib-include=/usr/local/zlib/include

注意几个关键配置项:

  • --prefix:指定安装路径,OpenSSL会装到/usr/local/openssl
  • shared:生成动态共享库,OpenSSH编译需要链接动态库
  • zlib:启用zlib压缩支持
  • -fPIC:如果configure报错说需要PIC,可以在config后面追加这个参数,尤其当系统里其它库是以PIC方式编译时
make -j4 make install

编译完成后,验证一下安装结果:

/usr/local/openssl/bin/openssl version

注意:这里不要设置LD_LIBRARY_PATH指向/usr/local/openssl/lib,否则会影响系统全局的动态库加载顺序。OpenSSH编译时会通过--with-ssl-dir明确指定使用哪个OpenSSL,运行时会通过rpath找到对应的库。

如果OpenSSL编译过程中报错No such file or directory: perl或者Can't locate IPC/Cmd.pm,这是系统Perl版本太老导致的。解决办法是用./config no-tests跳过测试模块构建,或者手动升级Perl(这个成本更高,建议先试no-tests)。

3.3 编译安装OpenSSH

这是整个升级的核心步骤。OpenSSH的configure参数决定了它的依赖位置、认证方式、运行路径,一个参数不对就可能导致编译失败或登录异常。

cd /usr/local/src/openssh-9.6p1 ./configure --prefix=/usr/local/openssh \ --sysconfdir=/etc/ssh \ --with-ssl-dir=/usr/local/openssl \ --with-zlib=/usr/local/zlib \ --with-pam \ --with-md5-passwords \ --mandir=/usr/share/man

参数含义如下:

  • --prefix:安装到/usr/local/openssh
  • --sysconfdir:配置文件目录设置为/etc/ssh,这样sshd_config、ssh_host_*_key等文件沿用系统原有位置,避免迁移配置的麻烦
  • --with-ssl-dir:指定OpenSSL目录
  • --with-zlib:指定zlib目录
  • --with-pam:启用PAM认证支持,否则会绕开系统的密码认证策略、账户锁定策略,这是很多运维踩的坑
  • --with-md5-passwords:兼容旧系统的MD5密码哈希
  • --mandir:man手册页安装到系统默认位置

--sysconfdir=/etc/ssh这一点我特别强调一下。如果保留默认的--prefix下的目录,新版本sshd会使用新的配置目录,而系统原来的sshd配置(比如PermitRootLogin、PasswordAuthentication这些)不会被加载。我把配置目录直接指到/etc/ssh,新版本sshd会读取原有配置,同时用新的配置模板覆盖部分缺失项。

configure完成后,编译安装:

make -j4 make install

编译过程可能会报openssl/xxx.h: No such file or directory,这是OpenSSL头文件路径没被找到。检查configure时--with-ssl-dir参数是否正确,或者确认OpenSSL是否成功安装到指定目录。

3.4 配置与启动sshd服务

新版OpenSSH安装完成后,需要处理几个关键配置和服务管理文件。

用/usr/local/openssh下的新版服务程序替换系统默认的sshd程序:

# 停止系统自带sshd服务 systemctl stop sshd # 替换二进制文件 cp /usr/sbin/sshd /usr/sbin/sshd.bak.$(date +%Y%m%d) cp /usr/local/openssh/sbin/sshd /usr/sbin/sshd # 替换客户端工具 cp /usr/bin/ssh /usr/bin/ssh.bak.$(date +%Y%m%d) cp /usr/local/openssh/bin/ssh /usr/bin/ssh cp /usr/local/openssh/bin/scp /usr/bin/scp cp /usr/local/openssh/bin/sftp /usr/bin/sftp

这样做的目的是让systemctl restart sshd时依然通过原有的systemd服务单元启动新版程序,后续管理方式不变。

然后校验配置并重启服务:

# 校验sshd配置语法 /usr/sbin/sshd -t # 如果输出类似 /etc/ssh/sshd_config line 25: Bad configuration option 的报错 # 说明旧配置里有新版已移除的选项,需要注释掉 # 重启sshd systemctl restart sshd # 查看服务状态 systemctl status sshd

关键配置项检查:

# /etc/ssh/sshd_config 核心配置确认 Port 22 PermitRootLogin yes # 如果运维需要root远程登录,保持yes,但建议配合密钥认证 PasswordAuthentication yes # 升级期间先保持yes,避免密码登录被卡 PubkeyAuthentication yes # 允许密钥登录 UsePAM yes # 必须为yes,否则PAM认证不生效

配置/usr/local/openssh/etc下的PAM文件:

# OpenSSH 9.6 安装时会生成/etc/pam.d/sshd的模板 # 确认系统原有的PAM配置还在,没有被覆盖 cp /etc/pam.d/sshd /etc/pam.d/sshd.bak.$(date +%Y%m%d) cp /usr/local/openssh/etc/sshd.pam /etc/pam.d/sshd

不过这个替换不建议直接做,因为系统原版的PAM配置包含了pam_localuser、pam_succeed_if等规则,直接替换可能会影响本地账号登录。我建议保留系统原版的/etc/pam.d/sshd,只用它来做PAM认证,OpenSSH自带的模板仅做参考。

设置SELinux(如果系统启用了):

# 查看SELinux状态 getenforce # 如果是Enforcing,需要放行sshd相关端口和连接 # 新版本sshd二进制文件的SELinux上下文可能不对,恢复一下 restorecon -Rv /usr/sbin/sshd /usr/bin/ssh

重启后如果SELinux拦截了sshd,日志里能看到SELinux is preventing /usr/sbin/sshd from name_connect access on the tcp_socket port 22。处理方法是用audit2why或直接搜索/var/log/audit/audit.log里的相关记录,然后设置布尔值:

# 允许sshd访问网络 setsebool -P ssh_sysadm_login on

接着处理systemd服务配置,让sshd服务可以管理新版二进制:

# 查看原有的sshd服务定义 systemctl cat sshd # ExecStart=/usr/sbin/sshd -D $OPTIONS # 我们已经替换了/usr/sbin/sshd,所以服务定义不用改

最后确认sshd监听正常:

netstat -tlnp | grep :22 ss -tlnp | grep :22 # 应该看到sshd监听在0.0.0.0:22

3.5 升级后的验证步骤

升级完成后不要急着收工,验证环节必须做足。先查版本号:

ssh -V 2>&1 # 输出:OpenSSH_9.6p1, OpenSSL 1.1.1w 11 Sep 2023

然后从客户端测试连接。这里要特别注意:升级后新版本默认禁用了ssh-rsa算法,如果你的客户端工具比较老(比如Windows自带的ssh还是老版本),连接时可能报Unable to negotiate with ...: no matching host key type found。

解决办法是临时在服务端/etc/ssh/sshd_config里加一行,允许老算法:

HostKeyAlgorithms +ssh-rsa PubkeyAcceptedAlgorithms +ssh-rsa

重启sshd后老客户端就能连了。但这个属于妥协方案,安全巡检时这个配置会拉低评分,建议在用密钥认证逐步替换老客户端后移除。

还要验证密钥登录是否正常:

ssh -i ~/.ssh/id_rsa user@server

以及配置文件关键项是否生效:

sshd -T | grep -E 'permitrootlogin|passwordauthentication|usepam'

如果之前开通了telnet保底通道,认证ssh连接一切正常后,关闭telnet:

systemctl stop xinetd systemctl disable xinetd firewall-cmd --permanent --remove-port=23/tcp firewall-cmd --reload

至此,OpenSSH离线升级主流程完成。

4. 常见问题与排查技巧实录

实际升级过程中,我遇到的问题比预设的要多得多。下面这些是我在多次操作中积累的经验,按问题类型分类整理,每个都有背景和解决过程。

4.1 configure阶段报错与处理

报错:checking for openSSL... no
这是最经典的报错。原因多半是configure没找到OpenSSL头文件或库文件。检查--with-ssl-dir的路径是否正确,确认 OpenSSL 是否真的装到了那个目录:

ls /usr/local/openssl/include/openssl/ssl.h ls /usr/local/openssl/lib/libssl.so

如果文件不存在,说明OpenSSL编译安装失败,回头重新做OpenSSL那一步。还有个小技巧:configure时加上CFLAGS=-I/usr/local/openssl/include LDFLAGS=-L/usr/local/openssl/lib能提高成功率。

报错:configure: error: *** zlib.h missing
原因是zlib开发库没找到。用--with-zlib=/usr/local/zlib指定路径,或者加上CPPFLAGS和LDFLAGS环境变量。

报错:configure: error: *** Cannot find PAM headers
缺PAM开发库。CentOS 7.9系统里通常有pam-devel,如果确认没有,需要从ISO里找pam-devel包补装。

4.2 升级后登录不了的排查思路

升级后ssh端口连不上,是最吓人的情况。我在自己测试机器上就翻过一次车,好在有telnet保底。排查步骤记好,按顺序来:

  1. 确认服务状态:systemctl status sshd,看是否active (running)
  2. 看监听端口:ss -tlnp | grep 22,确认sshd确实在监听
  3. 看防火墙:firewall-cmd --list-all,检查22端口是否被放行;还要注意有没有装iptables,两个防火墙可能同时存在
  4. 看SELinux:getenforce,如果是Enforcing,参考上面restorecon和布尔值处理
  5. 看日志:journalctl -u sshd -n 50,tail -50 /var/log/secure

其中/var/log/secure里的记录最有价值。比如常见的:

Jul 11 02:34:17 server sshd[12345]: fatal: Cannot bind any address

这是端口被占用或者监听地址无效。可能是原来还有别的sshd实例在跑,先pkill -9 sshd清理再重启。

如果登录时提示Connection closed by remote host,多半是算法协商失败,按上文的方法打开HostKeyAlgorithms +ssh-rsa或PubkeyAcceptedAlgorithms +ssh-rsa。

还有个容易忽略的点——sshd_config里的AllowUsers或AllowGroups。如果配置了白名单,而白名单里没有当前登录用户,就会直接拒绝连接。升级前备份的配置文件里如果有这类限制,要检查是否要保留。

4.3 服务启动失败与日志分析

编译安装后第一次启动sshd失败,多数原因集中在四个方面:配置语法错误、二进制库依赖缺失、权限上下文错误、PID文件冲突。

配置语法错误:/usr/sbin/sshd -t会直接告诉你哪行配置写错了。常见的是旧版配置选项在新版已废弃,比如UsePrivilegeSeparation在9.6里被移除了,Protocol 2也默认不需要写了。处理方式就是注释掉。

库依赖缺失:新编译的sshd二进制可能找不到libcrypto.so之类的库。

/usr/sbin/sshd -t # 如果输出:error while loading shared libraries: libcrypto.so.1.1: cannot open shared object file

解决办法是让系统动态库加载器找到新库的位置:

echo "/usr/local/openssl/lib" > /etc/ld.so.conf.d/openssl-1.1.1.conf ldconfig /usr/sbin/sshd -t

权限上下文错误:如果升级后的sshd被SELinux拦截,日志里会出现SELinux is preventing /usr/sbin/sshd from write access on the file /var/run/sshd。恢复上下文:

restorecon -Rv /var/run /usr/sbin/sshd

PID文件冲突:如果原来sshd的PID文件还在,新进程可能无法创建,报sshd: fatal: Cannot open PID file /var/run/sshd.pid as mode 0644。删掉旧PID文件重启即可:

rm -f /var/run/sshd.pid systemctl restart sshd

4.4 独家避坑经验总结

最后整理几条我在多次实操中总结出的经验,这些都是踩过坑才记住的:

第一,永远保留一份旧版sshd二进制副本。我在替换/usr/sbin/sshd前,一定会备份到/usr/sbin/sshd.bak.yyyymmdd。万一新版程序有问题,可以秒级回滚。同时也要备份/etc/ssh/sshd_config、/etc/pam.d/sshd这些关键配置文件。

第二,编译参数里的--with-pam一定不能省。我第一次升级时觉得系统密码认证够用了,没加PAM,结果系统账号的密码策略、fail2ban、账户锁定全部失效。运维登录审计发现密码错误次数限制不生效,这个坑相当深。

第三,升级完了别急着关telnet,先重启一次系统验证自动启动。我之前在一台机器上验证了ssh连接正常就关了telnet,结果后面机器重启发现sshd服务没起来(因为pid文件和xxx机制的锅),人又不在现场,只能让机房的人帮忙做console操作。

第四,/etc/ssh目录下sshd_config的文件权限要正确。OpenSSH对配置目录权限敏感,sshd_config不能对group和其他用户可写,否则启动时会报Bad permissions。如果从别的机器拷贝了配置文件过来,记得chmod 600 /etc/ssh/sshd_config,chmod 700 /etc/ssh。

第五,升级完成后检查一下sshd的依赖库引用。用ldd /usr/sbin/sshd检查是否有 "not found" 的依赖。我之前就因为OpenSSL的libssl.so版本不对,导致新sshd运行时使用的是系统旧库,虽然能启动,但部分加密算法不可用。正确的依赖应该是:

libcrypto.so.1.1 => /usr/local/openssl/lib/libcrypto.so.1.1 libssl.so.1.1 => /usr/local/openssl/lib/libssl.so.1.1 libz.so.1 => /usr/local/zlib/lib/libz.so.1

如果ldd显示的是/lib64/libcrypto.so.10,说明rpath没生效,需要重新编译,或者在编译时用LDFLAGS显式指定rpath。

第六,关于升级时机的选择。我一般会把操作拆成两个窗口:第一个窗口做环境准备(装编译工具、开通telnet、传包),这个可以在工作日的低峰期完成;第二个窗口才做正式的编译和替换,这个必须放在停机窗口内。为什么?因为编译期间CPU占用高,可能影响线上业务,而且configure阶段不停止服务,不会影响现有连接,完全可以在低峰期把这些前期工作做掉。

个人经验是,OpenSSH升级本身不算复杂,真正考验人的是在极端情况下的应变能力。把保底通道准备好,把备份做足,把每个步骤的验证做细,基本不会出大问题。如果后续想降低手工操作的风险,可以把这个流程写成脚本,把版本检查、依赖清理、编译安装、配置替换、服务重启、自检这几个环节串起来,脚本化之后运维效率会高很多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询