拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Ubuntu 22.04 OpenSSH/OpenSSL 源码编译升级与回滚实战

Ubuntu 22.04 OpenSSH/OpenSSL 源码编译升级与回滚实战

1. 先把 Ubuntu 22.04 自带的 OpenSSH 与 OpenSSL 摸清楚底细

1.1 出厂版本到底是多少,别凭印象判断

每次有人问我"Ubuntu 22.04 的 SSH 是不是太旧了",我都先让他把命令敲一遍再说。22.04 LTS 的默认组合大致是这样的:openssh-server在 8.9p1 这一代,openssl与libssl3在 3.0.2 这一代,后面跟着一小串ubuntu0.x的修订号,那个修订号才是关键——Ubuntu 会把上游的安全补丁往这个修订号里搬,所以修订号大、上游版本号小,是完全正常的。

ssh -V openssl version -a dpkg -l | grep -E 'openssh|libssl|libcrypto' ldd /usr/sbin/sshd | grep -E 'libssl|libcrypto'

最后那条ldd比前面几条都重要。它告诉你sshd二进制到底链的是哪个路径下的libcrypto.so.3。你要是准备自己编译一套新库,这个输出就是"改之前"的基准线,回头升级完再跑一遍对比,能立刻看出是否生效。

1.2 "版本号看起来低"和"有漏洞"根本不是一回事

这是我最想先掰扯清楚的一点。安全扫描报告经常把 OpenSSH 8.9 标成"存在 SWEET32、Terrapin、regreSSHion 等风险",然后甲方或者内部基线要求你升级到某个高版本。但事实是,Ubuntu 这类发行版的安全团队会把补丁回移到旧版本号上,你查8.9p1-3ubuntu0.10的 changelog 会发现这些 CVE 早就修掉了。版本号只是上游的"发布标记",不是安全状态的准确度量。

所以判断要不要动手,先做两件事:一是查apt changelog openssh-server或者/usr/share/doc/openssh-server/changelog.Debian.gz,把修订记录读一遍;二是问清楚需求方的真实意图——是扫描工具只会比对字符串,还是真的缺失某个算法或特性。前者是沟通问题,后者才是技术问题。这两种情况的处理方式完全不同,用错了方向就是白干一通还可能把机器搞挂。

1.3 真正必须自己动手编译的四种场景

我把这些年遇到的情况归了归类,基本上逃不出下面四种:

  • 合规硬指标:扫描器不接受"已回移补丁"的解释,只认版本号字符串,必须显示成指定版本。
  • 新算法/新特性需求:比如需要更现代的密钥交换组合、需要ssh-sk相关的硬件密钥支持、需要新版本才有的ProxyJump行为修正。
  • 第三方软件强制要求:某个中间件、某个 SDK 在编译或运行时要openssl >= 3.2,系统自带的 3.0.2 满足不了它的pkg-config检查。
  • 新旧交互兼容问题:老设备/老网元只支持被新版默认禁掉的算法,或者新版客户端连老服务端失败,需要在服务端或客户端侧调整策略。

对应地,解决路径也不一样。合规硬指标,走"官方源补丁能解决就解决,不行再编译,编译只编单个组件";软件依赖,优先考虑把那个软件本身编到独立目录去,而不是动系统库;兼容问题,八成改配置就够了,压根不用编译。我在实际项目里统计过,真正需要源码编译 OpenSSH 的比例大概只有三成,剩下七成都是配置或者沟通能解决的。

触发场景首选方案风险等级是否动系统库
扫描器只认版本号先查 changelog,再决定编译单组件中一般不动
需要新算法特性编译 OpenSSH,链接独立目录的新 OpenSSL中高不动
软件要求高版本 OpenSSL独立目录编译新 OpenSSL,走PKG_CONFIG_PATH低不动
兼容性协商失败只改sshd_config算法列表低不动

2. 高空作业前必须系好的安全绳:备份、影子实例与兜底定时任务

2.1 SSH 升级最要命的不是编译报错,是断连

编译出错顶多是浪费时间,但 SSH 服务起不来意味着你直接失去这台机器的入口。云主机如果有 VNC 控制台还好,物理机有串口或者 IPMI 也行,最怕的是那种"远程跳板进去、没有任何带外管理"的环境。我见过同事在一台生产机上make install完直接systemctl restart ssh,然后会话断掉,再也没连上,最后靠云厂商工单重启进救援模式才救回来,前后折腾了四个小时。

所以第一条规矩:升级动作必须在带外通道可用、或者有备用 sshd 实例在跑的前提下进行。

2.2 备份清单:这五样东西一个都不能少

我习惯在动手前建一个带时间戳的目录,把该留的全留一份,注意用cp -a保留权限和属主,SSH 相关文件对权限极其敏感:

BK=/root/ssh_upgrade_bak_$(date +%Y%m%d_%H%M) mkdir -p "$BK" cp -a /usr/sbin/sshd "$BK"/sshd.bin cp -a /usr/bin/ssh /usr/bin/scp /usr/bin/sftp /usr/bin/ssh-keygen "$BK"/ cp -a /etc/ssh "$BK"/etc_ssh cp -a /lib/systemd/system/ssh.service "$BK"/ 2>/dev/null cp -a /lib/systemd/system/ssh.socket "$BK"/ 2>/dev/null dpkg -l | grep -E 'openssh|libssl|libcrypto' > "$BK"/pkglist.txt ldd /usr/sbin/sshd > "$BK"/ldd_before.txt 2>&1

其中pkglist.txt很多人会忽略,但它才是"最干净的回滚方式"的依据。真出事了,apt-get install --reinstall openssh-server=8.9p1-3ubuntu0.x加上你记下的版本号,比手动把二进制拷回去可靠得多——因为包管理器还会帮你恢复 systemd 单元、man 手册、PAM 相关文件。

还有一个容易漏的点:如果你打算替换系统的libssl.so.3,那还要把/usr/lib/x86_64-linux-gnu/下的libssl.so.3、libcrypto.so.3以及对应的符号链接一起备份。这一步不做,出事就只能重装系统。

2.3 影子 sshd:全文性价比最高的一个技巧

与其纠结"新二进制能不能跑起来",不如直接让它在一个不冲突的端口上跑起来看看。新建一份独立配置:

cp /etc/ssh/sshd_config /etc/ssh/sshd_test_config sed -i 's/^#\?Port .*/Port 2222/' /etc/ssh/sshd_test_config sed -i 's|^#\?PidFile .*|PidFile /run/sshd_test.pid|' /etc/ssh/sshd_test_config /usr/sbin/sshd -f /etc/ssh/sshd_test_config -t /usr/sbin/sshd -f /etc/ssh/sshd_test_config -D -ddd

前台加-ddd跑着,另开一个窗口ssh -p 2222 user@host去登。能登进去、sftp -P 2222能传文件、PAM 认证流程正常,这才算"新二进制可用"。整个过程对现有的 22 端口零影响,失败了大不了 Ctrl+C 退出。

我特别建议在影子实例里同时测三件事:密码认证、密钥认证、SFTP 子系统。因为源码编译最容易出问题的就是这三块——密码认证挂 PAM,密钥认证挂AuthorizedKeysFile路径,SFTP 挂子系统路径,每一个都是独立的坑。

2.4 兜底定时任务:给自己留一个"后悔药"

这是一个有点土但非常好用的方法。假设你的 SSH 无论如何 15 分钟后都会被重启一次,即使会话断了,15 分钟后服务会自动恢复,你只需要等就行。

apt-get install -y at echo "/bin/systemctl start ssh.service" | at now + 15 minutes atq

前提是atd服务在跑。如果机器上没有at,用systemd-run也能达到同样效果:

systemd-run --on-active=15min --unit=ssh-rescue /bin/systemctl restart ssh.service

这个逻辑的妙处在于,它把"不可逆的失联风险"降级成了"等十五分钟"。我每次做这类操作之前都会先挂上这个任务,成功之后atrm掉或者systemctl stop ssh-rescue.timer清理掉即可。

注意:兜底任务只能保证服务进程起来,不能保证配置正确。如果sshd_config里写错了导致启动失败,定时任务照样救不了你。所以sshd -t语法检查永远是重启前的最后一道关。


3. OpenSSL 的编译与放置策略:系统库还是独立目录,先想清楚再动手

3.1 为什么顺序上应该先搞 OpenSSL

OpenSSH 编译时要链接 OpenSSL 的库和头文件。如果先编 OpenSSH、再升级 OpenSSL,很可能出现"编译时链的是旧库、运行时要的是新库"的错配,或者要重编一遍。所以合理顺序是:先把 OpenSSL 准备好并让pkg-config或--with-ssl-dir能找到它,然后再编译 OpenSSH。

3.2 两条路线的取舍:替换系统库 vs 独立目录

这是整个升级过程中最需要权衡的决策,我把两条路线的差别列清楚:

维度替换系统 libssl.so.3独立目录(如 /usr/local/openssl)
生效范围全系统所有依赖 OpenSSL 的程序只有显式指定路径的程序
扫描器识别容易识别到高版本可能识别不到,需要额外说明
风险apt、curl、python3-ssl 等都可能受影响风险被隔离,出问题只影响 sshd
回滚难度需要恢复二进制库文件删目录 + 改配置即可
适用场景合规硬压版本号且无法沟通绝大多数实际需求

我的默认建议是走独立目录这条路。OpenSSL 3.x 系列内部虽然保持了 ABI 兼容(soname 一直是libssl.so.3),但发行版打了大量自己的补丁,你从上游拉一个干净的 3.0.x 或 3.5.x 覆盖上去,理论上能跑,实际上偶尔会出现某些程序对特定行为差异的依赖而报错。把风险隔离在一个目录里,性价比高得多。

只有在一种情况下我会选择替换系统库:扫描器明确要求"系统级 OpenSSL 版本必须达到 X",且沟通无效。即便这样,我也会先跑一遍全系统的ldd扫描,看看到底有多少程序链了这个库,心里有数再动手。

3.3 编译参数的逐条解释

版本号这块我不写死了,OpenSSL 官网的下载页一直在更新,你按实际情况选长期支持分支的最新版即可。命令结构是不变的:

cd /usr/local/src curl -LO https://www.openssl.org/source/openssl-3.x.y.tar.gz tar -zxf openssl-3.x.y.tar.gz cd openssl-3.x.y ./config --prefix=/usr/local/openssl \ --openssldir=/usr/local/openssl \ shared zlib -fPIC make -j"$(nproc)" make install_sw

几个参数值得说明一下,因为配错了后期很麻烦:

  • --prefix是安装位置,决定了库文件、可执行文件放哪儿。
  • --openssldir是配置文件和证书目录。这个一定要和 prefix 分开或者明确指定,因为默认是/usr/local/ssl,如果和 prefix 不一致,openssl命令会去别的地方找openssl.cnf,导致一些默认行为诡异。
  • shared生成动态库.so。不加这个只有静态库,OpenSSH 就链不上。
  • zlib启用压缩支持,某些协议栈场景会用到。
  • -fPIC保证位置无关代码,动态库基本都要。

最关键的是最后一步用make install_sw而不是make install。install_sw只安装软件部分(库、头文件、二进制),不会去动--openssldir下的配置文件和证书目录。用make install有可能把你系统的默认openssl.cnf覆盖掉,而 Ubuntu 的openssl.cnf里有一堆发行版特有的配置,覆盖之后curl、apt都可能行为异常。

3.4 让动态加载器找到新库,但别急着全局生效

编译完之后,库在/usr/local/openssl/lib64(x86_64 上大概率是lib64,aarch64可能是lib,先ls确认,别写死)。

如果你选择了独立目录路线,不要急着往/etc/ld.so.conf.d/里加配置。那条路径是全局的,加了之后系统所有程序都会优先加载新库,等于变相替换了系统库。更稳妥的做法是让sshd自己带路径:编译 OpenSSH 时指定--with-ssl-dir,它会通过-Wl,-rpath或者运行时路径把库位置写进二进制。做完之后用ldd验证:

ldd /usr/sbin/sshd | grep -E 'libssl|libcrypto' readelf -d /usr/sbin/sshd | grep -i rpath

如果ldd显示的是/usr/local/openssl/lib64/libcrypto.so.3,那就说明链路绑好了,不依赖全局配置。如果还是指向/lib/x86_64-linux-gnu/,说明configure没认到,检查--with-ssl-dir传的路径里是否有lib64/libssl.so。

真要加全局配置的话,也建议加一个明确的前缀目录并且在ssh.service里用Environment=LD_LIBRARY_PATH=限定范围,而不是直接改/etc/ld.so.conf.d/。

3.5 头文件与 pkg-config 的小坑

如果你编完 OpenSSL 之后还要编别的软件,pkg-config可能还在找系统的.pc文件。这时候需要:

export PKG_CONFIG_PATH=/usr/local/openssl/lib64/pkgconfig:$PKG_CONFIG_PATH export LD_LIBRARY_PATH=/usr/local/openssl/lib64:$LD_LIBRARY_PATH pkg-config --modversion openssl

这三个环境变量在编译第三方软件时几乎必用,但不要写进/etc/profile全局生效,那会让整个系统的程序都优先用新库,又绕回了替换系统库的老问题上。写在当前会话里就够了。


4. 编译 OpenSSH:依赖清单、configure 参数与三个隐藏得最深的配置改动

4.1 依赖安装:build-dep卡住是常态

最省事的方式是用apt-get build-dep,但 Ubuntu 22.04 默认的sources.list里没有deb-src行,很多人在这就卡住了,报"you must put some 'deb-src' URIs in your sources.list"。要么把源码源打开,要么直接手动装依赖:

apt-get install -y build-essential zlib1g-dev libssl-dev \ libpam0g-dev libselinux1-dev libkrb5-dev libedit-dev \ libaudit-dev libsystemd-dev pkg-config

这几个包分别对应什么能力,值得记一下:libpam0g-dev决定 SSHD 能不能用 PAM 做认证(Ubuntu 上必须开,否则密码认证、pam_limits、pam_env全废);libselinux1-dev决定能不能做 SELinux 上下文的标签操作;libkrb5-dev是 Kerberos 认证;libedit-dev是新版sftp/ssh交互式输入的行编辑支持。少了哪个,configure会静默跳过对应功能,编出来的东西看起来能用,实际某些认证方式就是不通。

4.2 configure 参数:每一条都有理由

./configure --prefix=/usr \ --sysconfdir=/etc/ssh \ --with-ssl-dir=/usr/local/openssl \ --with-pam \ --with-selinux \ --with-zlib \ --with-md5-passwords \ --with-privsep-path=/var/lib/sshd \ --with-ssh1=no
  • --prefix=/usr而不是默认的/usr/local:这是为了和原系统的路径布局保持一致。如果按默认装到/usr/local,二进制会跑到/usr/local/sbin/sshd,而 systemd 单元里写的是/usr/sbin/sshd,服务会启动失败,或者启动了却是旧的。很多"我明明装了新版但ssh -V还是旧版"的问题,根因就在这。
  • --sysconfdir=/etc/ssh让配置文件路径和原系统对齐,不用改 systemd 单元的-f参数。
  • --with-ssl-dir指向刚编好的 OpenSSL,这是链接新库的关键。
  • --with-pam必开,理由上面说了。
  • --with-privsep-path=/var/lib/sshd:这个目录是权限分离进程的工作目录,必须存在、属主 root、权限 0755。Ubuntu 上一般已经有了(dpkg装的包会创建),自己编之前手工检查一遍:ls -ld /var/lib/sshd。目录权限不对,sshd直接拒绝启动,报错信息还比较隐晦。
  • --with-ssh1=no:SSHv1 协议早就该淘汰了,明确关掉,也让扫描器少一条告警。

4.3 打包装:用 checkinstall 换来回滚的余地

直接用make install会把文件散到各个目录,卸载的时候得靠make uninstall(如果 Makefile 支持),很不优雅。我更推荐用checkinstall打成.deb:

apt-get install -y checkinstall make -j"$(nproc)" checkinstall --pkgname=openssh-custom --pkgversion=9.x \ --backup=yes --install=no

它会在当前目录生成一个 deb 包。装的时候dpkg -i,卸载apt-get remove openssh-custom,回滚的时候把 Ubuntu 原版包重新装上就行。这个习惯养成了,后面运维会省心很多。

如果不想引入checkinstall,最起码也先把make install的输出重定向到一个文件里,记录下来它到底装了哪些文件:

make install 2>&1 | tee /root/ssh_install_files.txt

4.4 子系统路径:SFTP 挂掉的头号原因

这是源码编译最容易踩的坑,没有之一。Ubuntu 官方包里的sshd_config通常写着:

Subsystem sftp /usr/lib/openssh/sftp-server

你自己编译的新版本,sftp-server很可能装到了/usr/libexec/sftp-server或者/usr/local/libexec/sftp-server,取决于--libexecdir。路径对不上,客户端一连 SFTP 就报"subsystem request for sftp failed on channel 0"。

升级后务必确认:

find / -name "sftp-server" -type f 2>/dev/null grep -i "^Subsystem" /etc/ssh/sshd_config

对应的还可以配置内部子系统(不依赖外部二进制):

Subsystem sftp internal-sftp

用internal-sftp的好处是不依赖外部文件路径,缺点是不支持Match块里针对 SFTP 做更细的控制。我一般先用internal-sftp快速跑通,确认整体没问题后再切回外部路径。顺带一提,OpenSSH 9.0 之后scp默认改走 SFTP 协议了,所以子系统配错不只是 SFTP 用不了,scp也会一起挂,这点比老版本影响面更大。

4.5 主机密钥与 systemd 单元的连带影响

如果/etc/ssh/ssh_host_*这几个密钥文件因为某种原因丢了(比如你换了机器或者清过目录),sshd会拒绝启动。补生成一条命令就够:

ssh-keygen -A

另外 Ubuntu 从某个版本开始同时存在ssh.service和ssh.socket,默认可能是 socket 激活模式。自己编译的sshd和 socket 激活配合不好时会出现"服务看着是 active 的但 22 端口没监听"。检查一下:

systemctl status ssh.socket systemctl is-enabled ssh.socket

如果ssh.socket是启用的,而你又想用传统的ssh.service(sshd -D常驻),就把 socket 停掉并 mask:

systemctl stop ssh.socket systemctl disable ssh.socket systemctl mask ssh.socket systemctl enable --now ssh.service

这套操作我在好几台机器上都用过,问题现象就是"端口不通但服务正常",非常容易误判成防火墙问题。


5. 上线验证:把"看起来好了"变成"确实好了"的完整清单

5.1 静态检查:五行命令筛掉八成低级错误

重启服务之前,这五条命令我一条都不会省:

/usr/sbin/sshd -t # 配置语法检查 /usr/sbin/sshd -T | grep -E 'port|permitrootlogin' # 展开后的有效配置 ldd /usr/sbin/sshd | grep -E 'ssl|crypto' # 确认链接到新库 systemctl cat ssh.service # 确认 ExecStart 指向的二进制路径 ss -tlnp | grep ':22' # 确认端口监听(重启后)

sshd -T比-t更有价值,它会把Include、Match块全部展开,输出最终生效的配置。有一次我明明在sshd_config.d/里改了算法,但主配置里后面还有一行覆盖,-T一跑就露馅了。

5.2 协议层实测:算法协商到底行不行

版本号是给扫描器看的,算法协商才是给业务用的。查询当前二进制支持的算法列表:

ssh -Q kex ssh -Q cipher ssh -Q mac ssh -Q key

从外部探测服务端实际通告的算法:

nmap -p 22 --script ssh2-enum-algos 127.0.0.1

有了这两个输出,就能判断新旧两端能不能谈成。真正要做的是四象限测试:新客户端连新服务端、新客户端连老服务端、老客户端连新服务端、老客户端连新服务端。第二和第三种才是最容易出问题的,因为新版默认会禁掉一批老算法。

5.3 OpenSSL 侧的验证:证书链路与命令行为

如果你动了 OpenSSL,证书相关的操作一定要回归一遍,因为不同大版本在默认策略、默认摘要算法、配置项名称上都有细微差别:

openssl version -a openssl s_client -connect example.com:443 -servername example.com -showcerts openssl x509 -in server.crt -noout -text | head -n 20 openssl verify -CAfile ca.crt server.crt

最后一条特别值得跑,openssl verify是最常用的证书链自检命令。它的报错信息很直白,比如"unable to get local issuer certificate"就是中间证书或根证书不在信任链里,跟协议本身无关,纯粹是路径和文件的问题。写这篇文章的时候顺手在测试机上跑了一遍,从系统库切到独立目录之后,CApath的默认位置会变,如果你之前依赖/etc/ssl/certs的哈希目录,得确认新版本的默认查找路径是否一致。

验证项命令期望结果
服务版本ssh -V新版本号
客户端二进制which ssh; ldd $(which ssh)指向新库
配置有效sshd -T无语法错误,算法符合预期
算法协商nmap --script ssh2-enum-algos只出现白名单算法
认证通路密码/密钥各登一次均成功
文件传输sftp与scp各传一个文件均成功
证书链路openssl verify -CAfileOK

5.4 回滚演练:别等到真出事才第一次用

备份做完了不代表回滚能成功。我会在影子实例阶段就做一次"假回滚":把备份的sshd.bin拷回/usr/sbin/sshd,重启影子实例,确认能起来,然后再切回新版。这样备份的可用性就得到了验证。

用dpkg回滚的话,流程是:

apt-get install --reinstall openssh-server=<记下的版本号> apt-get install --reinstall openssh-client=<记下的版本号>

如果之前装了openssh-custom的 deb 包,先 remove 掉再重装原版,顺序反了会出现文件冲突报错。


6. 故障排查实录:我在真机上遇到过的七个问题

6.1 启动就报 symbol lookup error

现象是systemctl status ssh显示退出码 127,日志里一行symbol lookup error: /usr/sbin/sshd: undefined symbol: ...,或者version 'OPENSSL_3.x.x' not found。这基本就是链接库的问题:二进制编译时链的是新库,运行时动态加载器却找到了旧库。

排查顺序是:先ldd /usr/sbin/sshd | grep crypto看实际解析到哪个路径;再看readelf -d里的RPATH;然后确认LD_LIBRARY_PATH在 systemd 环境里是否生效。systemd 服务的环境变量和交互式 shell 是两套,/etc/profile里设的变量对它们无效,必须在单元文件里写Environment=或者写EnvironmentFile=。

6.2 能连上但认证失败

sshd起来了、端口也在听、能拿到 banner,但输密码一直"Permission denied"。八成是 PAM 配置的问题,检查三处:

grep -i "^UsePAM" /etc/ssh/sshd_config ls -l /etc/pam.d/sshd grep sshd /var/log/auth.log | tail -n 30

UsePAM必须为yes。另外如果你编译时没加--with-pam,sshd会完全绕过 PAM,某些依赖 PAM 的策略(账号锁定、密码过期、环境变量注入)就都不生效了,表现就是"能登录但行为不对"。

6.3 登录后立刻被踢出来

这个现象往往是pam_limits或者pam_systemd会话建立失败,日志里能看到PAM: pam_open_session(): ...。还有一种可能是/run/sshd或/var/lib/sshd权限不对导致权限分离进程起不来。用sshd -ddd前台跑一次,把日志逐行看,问题一目了然。

6.4 SFTP 报 subsystem failed

前面提过,路径问题。补充一点:如果sshd_config里指向了一个不存在的文件,sshd -t语法检查是不会报错的,因为语法没错,只是文件不存在。这个坑很好用sshd -T加手动ls组合来防。

6.5 老设备连不上新服务端

典型报错是"no matching key exchange method found"或"no matching host key type found"。原因是新版默认禁掉了ssh-rsa(SHA-1 签名)和某些老 KEX。解决方式不是把默认全放开,而是在服务端针对性补充:

HostKeyAlgorithms +ssh-rsa PubkeyAcceptedAlgorithms +ssh-rsa KexAlgorithms +diffie-hellman-group14-sha1

注意用的是+前缀表示追加,而不是直接覆盖默认列表。直接覆盖会让安全基线退化一大截,扫描器下次又来报警。

6.6 apt 或 curl 突然报 SSL 错误

只有在你替换了系统库的情况下才会遇到。现象是apt update报SSL: CERTIFICATE_VERIFY_FAILED,或者curl报协议错误。本质是新 OpenSSL 的默认安全级别(security level)比发行版配置更严,或者默认证书路径变了。应急做法是先把系统库恢复回去,然后用下面这条临时命令确认是不是库的问题:

LD_LIBRARY_PATH=/usr/local/openssl/lib64 curl -v https://example.com

如果加上这个前缀就正常,说明是加载路径问题;如果还是报错,就是配置或证书路径问题。

6.7 升级完之后ssh -V还是老版本

这个最常见也最气人。三个可能:一是which ssh找到的是/usr/local/bin/ssh而不是/usr/bin/ssh,你编的新版装到了别的地方;二是 shell 的命令哈希缓存,hash -r一下就好;三是PATH顺序问题,/usr/local/bin排在/usr/bin前面。用type -a ssh把所有同名命令列出来,一目了然。


7. 关于"到底要不要升"的几个现实判断

把技术细节讲完之后,我更想聊聊决策层面的东西。这些年做下来,我对这类操作有个比较清晰的排序逻辑:能用官方源解决的就绝不编译,能只编一个组件的就绝不动系统库,能在影子实例验证的就绝不在生产上试。

优先级从高到低大致是这样:先apt-get upgrade把修订号升到最新,把 changelog 拿给需求方看;解释不通的情况下,编译单个组件并且隔离路径;只有在极端情况下才考虑替换系统级的基础库,而且替换之前一定要做全局依赖扫描,明确知道会有多少程序受影响。

不同环境下的处理方式也不一样。WSL 里的 Ubuntu 我从来不折腾这套东西——它只是本地开发环境,SSH 服务本身很少对外,升级的收益几乎为零,风险却不小,因为 WSL 的网络栈和 systemd 支持和标准发行版有差异。容器镜像里的 SSH 同理,你要么重建镜像,要么用官方基础镜像的更新版本,而不是进去手工编译。

RPM 系(比如 CentOS、麒麟这类)和 DEB 系的差异也值得提一句,因为很多人是两边混着看教程的。RPM 系通常靠rpmbuild重打 SRPM 包,用--define覆盖版本号,最后用rpm -Uvh升级,好处是包管理器全程接管、回滚干净;DEB 系则更倾向于源码编译加checkinstall。两条路的配置参数、依赖包名、目录布局都不太一样,把 RPM 的教程照搬到 Ubuntu 上,最常见的后果就是路径写错导致服务起不来。

最后分享一个我养成的习惯:每次做这种改动,我都会在/root/下留一份UPGRADE_NOTES.md,写清楚改了什么、为什么改、备份在哪、回滚命令是什么、验证过哪些项。半年之后你自己或者接手的同事看到这台机器,不用去猜它是不是被人动过。这种东西平时看不出价值,真出事的时候能省下好几个小时。

至于版本号的选择,我的偏好是在长期支持分支上取最新,而不是一味追最新的主线版本。原因很简单:长期支持分支的补丁周期长、行为稳定、遇到问题也更容易搜到别人的处理记录。追最新版能获得新特性,但你也会成为最早踩坑的那批人——除非有明确的特性需求,否则没必要。

返回列表