简介面向需要在离线环境下为 Ubuntu 20.04 桌面版部署 SSH 服务的用户这是一份可直接使用的 openssh-server 离线安装包。资源内已整理好安装所需的核心组件与一键辅助脚本解决无网络环境下手动收集依赖、逐个安装的麻烦适合系统管理员、运维人员及在内网或隔离网络中搭建远程管理通道的开发者。压缩包仅 1.05MB共 4 个文件包含 3 个 amd64 架构的 deb 软件包覆盖 ssh 客户端、ssh 服务端及 sftp 服务模块和 1 个安装引导脚本结构简单便于快速部署。目前已有 1253 人学习下载。通过该资源用户可在离线 Ubuntu 20.04 上按步骤完成 SSH 服务的安装与启动同时获得端口修改、公钥认证等安全加固思路适合作为离线环境运维工具包直接留存复用。1. Ubuntu 20.04 sshd 离线安装包内网机器装 OpenSSH 服务端的最短路径刚拿到一台 Ubuntu 20.04 的内网服务器开机发现连 sshd 都没有。管理员不让出外网apt update 全部超时systemctl start ssh直接报 Unit not found。这种情况我在维护隔离网络时碰到不止一次最稳的解决办法就是提前准备好一套 sshd 离线安装包拷进去一次装齐。这份资源是在同版本 Ubuntu 20.04 上拉取的 OpenSSH 服务端及全部依赖包覆盖 openssh-server、openssh-client 和底层 openssl、zlib、PAM 相关依赖解决内网机器装 SSH 服务端的最后一公里。适用人群是维护内网机房、做批量装机、或者想彻底搞懂 sshd 依赖链的人。下面我用实际安装过的方式把这套离线包的选型、生成、安装和排坑讲透。2. 为什么离线装一台 sshd 这么麻烦依赖链、架构判别与包选型2.1 sshd 不是一个文件客户端、服务端与系统库的关系Ubuntu 20.04 默认只装了 OpenSSH 客户端sshd 服务端需要单独安装。这里的门道在于 sshd 不是一个孤立的二进制/usr/sbin/sshd启动时动态链接 libcrypto、libssl、zlib、libpam、libsystemd、libselinux 等一系列库这些库在最小化安装的服务器上未必都在。很多第一次做离线包的人只下载一个 openssh-server 的 .deb 就以为完事了拷过去dpkg -i直接报依赖缺失就是这个原因。离线安装不能靠拷一个/usr/sbin/sshd文件解决。二进制文件拷过去动态库路径、配置目录、systemd 服务单元全都没有。正确的做法是准备一整组 .deb 包交给 dpkg 去安装它会负责把/etc/ssh/sshd_config放到正确位置、生成主机密钥、注册 ssh.service 单元。这个选型理由也是这套资源的核心思路——用官方 .deb 而不是手工拷贝才能让 sshd 变成被系统管理的服务而不是一个裸文件。说句实在话能用包管理器解决的事就不要自己去搬二进制后者在升级和排障时会变成一个黑匣子。2.2 动手前先查三样东西系统版本、CPU 架构、包架构在联网机器上拉包之前先确认三件事目标机的系统版本、内核架构、dpkg 架构名。lsb_release -a看系统版本uname -m看内核架构dpkg --print-architecture看包管理器使用的架构代码。这三项里任何一项对不上离线包装上去都可能报版本或架构不匹配。lsb_release -a uname -m dpkg --print-architecturelsb_release -a的输出会带上 Distributor ID、Description、Release、Codename这里要看 Release 是不是 20.04Codename 是不是 focal两者决定了源里包版本的选择区间。uname -m打印的是 CPU 架构的内核表示x86_64 对应 x86_64 架构aarch64 对应 ARM64。dpkg --print-architecture是 dpkg 自己用的架构名离线包文件名里通常带的就是这个名字。注意它和uname -m不一定完全一致uname -m返回 x86_64 时dpkg 一般写 amd64返回 aarch64 时dpkg 一般写 arm64。uname -m 输出dpkg 架构名常见场景x86_64amd64绝大多数服务器与桌面aarch64arm64常见的 ARM 服务器armv7larmhf老款 32 位 ARM 板子这个对应关系在下载时用得上你在联网机器上看到 .deb 文件名里的 amd64就能确定它不是 arm64 版。额外提醒一句判断系统版本时不要只看uname -a它只显示内核版本和架构不会告诉你这个系统是 20.04 还是 22.04。内核版本相同的两个系统也可能因为发行版源不同而存在 OpenSSL 版本差异这点在 2.3 会再展开。2.3 用 apt-cache 和 apt-rdepends 把依赖链摊开看在联网机器上确认目标环境信息后先看一眼源里能解析出什么依赖再决定下载清单。apt-cache depends openssh-server执行后输出里以 Depends 开头的行就是安装它必须满足的包openssh-client、libssl1.1、libpam-modules、zlib1g、libsystemd0 这些都会冒出来。只要任一缺失dpkg 就会在安装阶段报错。apt-cache depends openssh-server apt-cache show openssh-server | grep -E ^(Package|Version|Depends)apt-cache depends适合快速看一层apt-cache show则给出包的详细元信息从中可以看到 Depends 字段的完整约束比如某个版本下限。但如果想一次性把依赖的依赖也找齐靠眼睛一层层翻会漏。常见做法是先装 apt-rdepends 这个工具它对指定包做递归依赖分析一行命令就把整棵依赖树展开sudo apt-get install -y apt-rdepends apt-rdepends openssh-serverapt-get install -y apt-rdepends中的-y跳过确认sudo 是因为安装工具要写系统目录。apt-rdepends openssh-server的输出是缩进式的依赖树左边不带缩进的包是被依赖的父级每往下一层多一重缩进。把不带缩进的包名全部收拢就是离线环境需要的最小集合。如果网络环境不太干净也可以把 Recommends 行一并纳入按场景取舍。这里有个参数细节apt-cache 本身也支持递归不装 apt-rdepends 也可以用下面这行命令拿到类似效果apt-cache depends --recurse --no-recommends --no-suggests \ --no-conflicts --no-breaks --no-replaces --no-enhances \ openssh-server | grep -E ^[a-zA-Z0-9] | sort -u--recurse打开递归--no-recommends和--no-suggests只保留 Depends 强制依赖--no-conflicts --no-breaks --no-replaces --no-enhances这四项把冲突、破坏、替代、增强四类关系全部排除只留真正决定能否装得上的包。grep -E ^[a-zA-Z0-9]过滤掉带缩进的依赖行sort -u去重。多数情况下手边没有 apt-rdepends 时这条命令就是全包替代方案。如果输出里混进了gcc-9-base、libc6-dev这类只在编译期使用、运行时根本用不到的工具说明依赖树里带了 build-dep 级的包离线安装时不需要它们进 dpkg 列表可以手工删掉这些包名。更保守的做法是把列表里所有包名交给 apt-get download然后在目标机上按依赖顺序装只要 Depends 行能全部满足即可。这里没有黑匣子依赖树长什么样是可以每次跑一遍看一遍的。3. 一条命令拉齐离线依赖生成、拷贝、安装与验证四连3.1 在联网机器上把依赖包全部拽下来确定依赖清单后用 apt-get download 把这一批包下载到本地目录。注意这里不能用 apt-get install因为 install 会把包安装进当前系统破坏你正在用的联网机器download 只是下载 .deb 到当前目录不改变系统状态。这是离线包制作和日常安装的本质区别。mkdir -p ~/sshd-offline cd ~/sshd-offline apt-get download $(apt-rdepends openssh-server | grep -v ^ | sort -u)mkdir -p创建目录时不会因为目录已存在而报错cd进入目录shell 的$()把 apt-rdepends 的输出转成参数列表喂给 apt-get download。grep -v ^ 去掉带前导空格的行这些行是依赖树里的被依赖描述行只提取真正的包名。sort -u去重避免同一个包反复下载。如果你把命令换成 2.3 的 apt-cache 递归版本把grep -v ^ 换成grep -E ^[a-zA-Z0-9]效果一样。拉完包后建议立刻检查目录这一步只花五秒钟却能避免后面的整场返工ls -lh *.deb dpkg-deb -I openssh-server*.deb | grep -E ^(Package|Version|Architecture)ls -lh看每个 .deb 的体积dpkg-deb -I读取 deb 的控制信息grep -E ^(Package|Version|Architecture)确认主包的三个关键字段。Package 是不是 openssh-serverVersion 是不是和源机器一致Architecture 是不是 amd64。如果这里发现架构写的是 arm64 而目标机是 amd64直接回到 2.2 重新核对。3.2 把包带到目标机器三种现实可行的传输方式传输方式取决于目标机器当前状态。如果目标机器已经能 ssh比如你只是要补装或升级 sshd直接用 scp 推过去就行。如果目标机器是全新的、连 sshd 都没有scp 推不进去只能用 U 盘、文件服务器或通过内网 Web 服务下载。scp -r ~/sshd-offline/ user192.168.x.x:/tmp/scp -r递归拷贝整个目录地址里的 user 换成目标机用户名IP 换成目标机实际内网地址这里只是示例。如果没有 sshd把整个目录拷进 U 盘再到目标机上执行cp -r /media/usb/sshd-offline /tmp/。文件名和扩展名不要动dpkg 处理 deb 时不看文件名但保持原样能让排查时更快找到对应包。拷完后第一件事不是安装而是先在目标机上看一遍包清单是不是完整。离线安装最怕的就是少一个依赖包还看不出来等到 dpkg 报错再回头找来回一折腾就是半天。我在实际分发时通常会把整个目录压缩成 tar 包再传输一方面减少文件数量另一方面tar tf能快速校验文件有没有中途损坏。3.3 dpkg 安装与依赖修复先后顺序决定成败进入目录后可以直接尝试dpkg -i *.debdpkg 内部会做依赖排序多数情况下一次过。但离线环境里如果之前残留过半安装的包或者依赖列表里有遗漏一次-i会中途停下。此时不要盲目再跑一遍先把包解包到系统、再统一配置cd /tmp/sshd-offline sudo dpkg -i *.debsudo dpkg -i *.deb中星号由 bash 展开成所有 deb 文件dpkg 逐个安装。如果看到类似 dependency problems prevent configuration of openssh-server 的报错执行下面这段补救流程sudo dpkg --unpack *.deb sudo dpkg --configure -a sudo apt-get -f install -y --no-install-recommendsdpkg --unpack只把每个包的文件释放到系统目录并更新 dpkg 数据库不做配置dpkg --configure -a对所有处于 unpacked 状态的包执行配置脚本apt-get -f install -y尝试修复依赖关系。在离线环境里 apt 源不可用-f不会真的去网上下载它只是检查本地已解包的状态并尝试补齐依赖记录。如果这一步输出没有报错说明离线包已经全部就位。这里有个常见误用拿到离线包就急着dpkg --force-all -i强装。大多数情况下没必要force 参数会跳过配置脚本反而让 sshd 的 systemd 单元不生成后面服务起不来。优先用 unpack 加 configure 的组合force 只留给 4.2 那种库文件被误删的恢复场景。3.4 安装后的三重验证语法、服务、端口装完的瞬间不要以为就结束了。离线环境里没有 apt 能帮你验证一切只认三条配置语法对不对、服务有没有起来、端口有没有在监听。sudo sshd -t sudo systemctl start ssh sudo systemctl enable ssh systemctl status ssh --no-pagersshd -t只检查/etc/ssh/sshd_config语法不启动服务没有任何输出或者打印 OpenSSH 版本信息都算通过。如果它报错说明配置里有非法参数先别启动服务。接下来systemctl start ssh启动systemctl enable ssh设置开机自启systemctl status ssh --no-pager查看运行状态Active 行出现 active (running) 就说明服务进程活着。服务活着不代表端口就在监听再补一条ss -tlnp | grep :22ss是 iproute2 自带的网络状态工具-t只看 TCP-l只看监听-n不做域名解析-p显示进程名grep :22过滤 22 端口。如果输出里有LISTEN 0 128 0.0.0.0:22或[::]:22说明 sshd 已经就绪。没有输出就把grep :22换成grep sshd看是不是端口被改过或监听地址不是默认。4. 避坑指南sshd 离线包常见的五种翻车现场4.1 依赖缺失dpkg 提示 openssh-server 依赖的包没装现象sudo dpkg -i *.deb执行到中间停住终端打印类似 openssh-server depends on openssh-client; however: Package openssh-client is not installed 的提示openssh-server 卡在 configured 状态。原因离线包生成时依赖清单没拉全。常见做法是只下载了 openssh-server 一个 deb或者用apt-get install --download-only但过滤掉了 Recommends 和 Suggested。另一个原因是目标机器是最小化安装连 openssh-client 这种被当作基础包的软件都没装。解决回到联网机器重新执行 2.3 里的apt-rdepends openssh-server全量依赖命令把输出里所有包名下载齐全。如果时间紧、暂时不想重新拉包可以在目标机上用最小清单手工分层安装sudo dpkg -i libssl1.1*.deb libpam*.deb zlib1g*.deb libsystemd0*.deb sudo dpkg -i openssh-client*.deb sudo dpkg -i openssh-server*.deb手工指定顺序时基础库在最前面客户端在服务端之前依赖方向只能由被依赖方指向依赖方。dpkg 自己可以排序但手工分层能把报错定位缩小到一个具体的阶段。4.2 libcrypto.so.1.1 找不到OpenSSL 库文件被外力改动现象openssh-server 装好了systemctl start ssh后服务秒退journalctl -u ssh或/var/log/auth.log里出现 error while loading shared libraries: libcrypto.so.1.1: cannot open shared object file 的报错。原因Ubuntu 20.04 的 OpenSSL 版本是 1.1 系列sshd 二进制链接的是 libcrypto.so.1.1。如果目标机器上之前有人编译安装过新版本 OpenSSL或手动删过老库系统里只剩 .so.3 而没有 .so.1.1sshd 就会在启动时找不到库。这是离线环境里最典型的看起来装完了、其实没法用的场景。解决离线包里保留 libssl1.1 对应的 deb用它把库文件恢复到系统目录sudo dpkg -i --force-depends libssl1.1*.deb sudo ldconfig ldconfig -p | grep libcrypto--force-depends会跳过依赖检查只把库文件放回系统ldconfig重建动态链接缓存最后ldconfig -p | grep libcrypto确认缓存里出现 libcrypto.so.1.1 与 libcrypto.so.3 都属于正常。这里用 force 是可接受的因为本质不是缺依赖而是库文件丢失。4.3 服务名对不上Unit ssh.service not found现象systemctl start ssh报 Failed to start ssh.service: Unit ssh.service not found但dpkg -l | grep openssh明明显示 openssh-server 已经 install。原因不同版本的 openssh-server 生成的 systemd 单元文件名不完全一致。Ubuntu 官方包通常生成 ssh.service而某些第三方维护的包生成的是 sshd.service。如果包是从其它源或者旧版本那边拉来的服务名就可能对不上。另外还有一类情况openssh-server 实际装好了但安装时配置脚本没执行系统里根本没有生成 systemd 单元。解决先看系统里实际有哪些单元文件systemctl list-unit-files | grep -E ssh|sshd如果输出里有 ssh.service就用 ssh 启动如果只有 sshd.service就用 sshd 启动。两个都在时优先用 openssh-server 包自带的那个。都没有说明安装的配置阶段失败了执行sudo dpkg --configure openssh-server重新跑一遍配置脚本。4.4 防火墙和端口双重限制本机能连跨网段连不上现象在服务器本机用ssh localhost能连上同一局域网里其它机器也能连但从外部网段访问超时或者ssh -v userhost卡在 connect 阶段。原因常见是两层问题叠加。一层是 ufw 默认策略拦了 22 端口另一层是/etc/ssh/sshd_config里既写了 Port 22 又写了 ListenAddress 指向内网地址导致外网接口上根本没有监听。解决先放行端口再看 sshd 实际的监听地址sudo ufw allow 22/tcp sudo ufw status sudo grep -E ^(Port|ListenAddress|PermitRootLogin) /etc/ssh/sshd_configufw allow 22/tcp与ufw allow OpenSSH等价写 22/tcp 更明确。grep 的^锚定行首只显示没有被注释掉的配置行。如果 ListenAddress 写死了某个内网 IP把这行注释掉或改为0.0.0.0然后执行sudo sshd -t sudo systemctl reload ssh重载配置。这里注意改配置之前先把当前 ssh 连接保持住避免自己把自己关在门外。4.5 两台同型号机器装不上离线包环境与目标环境版本漂移现象在一台机器上把 sshd 离线包装成功了把同一个目录拷到另一台同型号、同系统版本的机器上dpkg 报版本冲突比如 libssl1.1 已经存在且版本比离线包里的更高。原因离线包是从源机器的 apt 状态生成的它和目标机器的软件快照并不一定一致。如果目标机器之前升级过 openssl、zlib 或 systemd离线包里带的是旧版本dpkg 会拒绝降级安装。这是批量装机最容易踩的坑同一批硬件、同一张镜像不代表两台机器的软件版本完全一样。解决先看目标机器现有包的版本再决定是降级还是另找源apt-cache policy libssl1.1 openssh-server dpkg -l | grep -E openssh|libsslapt-cache policy同时显示已安装版本与源里可用版本dpkg -l列出两个包的实际安装状态。如果目标机的库版本比离线包新就不要强行覆盖重新在另一台与目标机软件快照一致的机器上生成离线包或者直接修改 sources.list 指向内网 apt 镜像让 apt 自己解析版本。血泪经验是离线包要跟着目标机器当前软件清单走不要跟着生成机器走。5. 安装完只是起点sshd 配置加固与回环自测5.1 回环自测ssh -v localhost 是最快的体检离线环境没有复杂的监控工具验证 sshd 是否真能用一条回环命令就够了ssh -v localhost-v让客户端打印完整调试过程。连接成功后日志末尾会输出 Authenticated to localhost。如果停在 Permission denied是认证配置的问题如果停在 Connection refused说明 sshd 没在监听。想看得更细用ssh -vvv userhost并过滤关键阶段ssh -vvv userhost 21 | grep -E kex|auth|debug21把错误输出合并到标准输出grep -E kex|auth|debug滤出密钥交换、认证、调试三段信息。kex 阶段看加密算法协商是否一致auth 阶段看公钥还是密码被接受。这一步能直接区分服务没起来和认证不过两种截然不同的故障。5.2 最小暴露面配置与密钥登录sshd 能用之后建议尽快把默认配置收紧。默认的 Ubuntu 20.04 允许密码登录且允许 root 直接登录这对离线内网也许不致命但如果这台机器将来要接入其它网络风险会放大。我一般会在/etc/ssh/sshd_config里把下面几项改成显式配置配置项建议值作用PermitRootLoginprohibit-password禁止 root 密码登录保留密钥登录PasswordAuthenticationno关闭密码认证只接受公钥PubkeyAuthenticationyes显式开启公钥认证Port2222改默认端口降低扫描面改完配置不要直接重启服务先用sudo sshd -t确认语法没有把服务搞挂。之后在客户端生成密钥并投递公钥ssh-keygen -t ed25519 -a 100 -C offline-host ssh-copy-id -p 2222 userhostssh-keygen -t ed25519生成 ed25519 密钥-a 100把 KDF 迭代次数调到 100 轮-C加备注方便区分钥匙。ssh-copy-id会自动把本机公钥追加到目标机的~/.ssh/authorized_keys并设置权限。没有 ssh-copy-id 时手工追加也一样cat ~/.ssh/id_ed25519.pub | ssh -p 2222 userhost \ mkdir -p ~/.ssh chmod 700 ~/.ssh cat ~/.ssh/authorized_keys chmod 600 ~/.ssh/authorized_keys管道把公钥内容传进 ssh 命令远程那边先建目录、收紧目录权限再把公钥追加进 authorized_keys最后把文件权限收紧到 600。sshd 会拒绝权限过宽的 authorized_keys这一步是很多人连不上公钥的玄学原因其实只是权限位不对。5.3 平滑重载与权限检查配置全部到位后正式生效走 reload 而不是 restart这样不会踢掉现有连接sudo sshd -t sudo systemctl reload ssh systemctl status ssh --no-pagersshd -t在 reload 之前再跑一遍是最低成本的后悔药语法错了连接全部断掉离线环境里你只能跑去机房。reload 是 SIGHUP 信号sshd 会重新读取配置但保留现有会话。systemctl status ssh --no-pager最后确认状态Active 行显示 active (running) 就收工。以前我在一次远程变更里改完配置文件直接 reload结果少了个参数所有连接全部断掉最后只能跑一趟机房。从那以后我每次离线环境装完 sshd都强制把sshd -t、回环自测、权限检查、reload 这条链完整走一遍再收工。这套流程看着琐碎但每走一步都在把装得上变成一定能用。希望这篇笔记能帮到正在内网机房折腾的你。本文还有配套的精品资源点击获取