
简介在无外网或内网受限的环境下为 Ubuntu 20.04 桌面版启用 SSH 服务常因缺少服务端安装包及依赖而中断。这份离线资源正是针对这一场景面向需要远程管理、自动化运维的系统管理员与开发者把常用材料预先封装成可直接使用的压缩包。资源总共 4 个文件压缩后仅 1.05 MB包含 3 个 deb 安装包分别对应 OpenSSH 客户端、服务端和 SFTP 模块另有 1 个自动化安装脚本使用者无需配置在线软件源也无需手动解决依赖顺序执行脚本即可覆盖安装、启动两个关键环节。目前已有 1251 人学习/下载。拿到资源后只需将文件整体拷贝到目标机器运行脚本即可自动安装 sshd 并设置开机自启随后可以正常使用密码或密钥进行远程登录在此基础上还可继续修改端口、禁用 root 登录等安全加固为离线网络环境提供一个轻量、可复用的 SSH 部署基础。1. 断网机器上的 sshd离线包不是拷一个 deb 那么简单机房内网一台 Ubuntu 20.04物理隔离没有 apt 源连 DNS 都出不去而 OpenSSH Server 刚被加固脚本清掉或者装机镜像里压根没勾选。要在这台机器上把 sshd 拉起来第一反应是apt install openssh-server然后眼睁睁看着 Sources 列表刷新超时。这种时候需要的是一份完整的 sshd 离线安装包不止 openssh-server 一个 deb而是它背后一整套依赖包括 openssh-client、openssh-sftp-server、libssl1.1、libpam0g、ucf 这几个关键件。这篇文章就是写给要做这件事的人怎么在有网的机器上把依赖一次抓干净怎么在离线机上用 dpkg 装起来以及六个我实际踩过的坑。适合实施工程师、内网运维以及在实验室里被断网折磨过的每一个人。2. 先拆依赖链openssh-server 到底拖出多少个包2.1 依赖树里的主角与配角先说结论Ubuntu 20.04 的 openssh-server 包版本是 1:8.2p1-4ubuntu0.x它不是一个自包含的二进制而是把服务端拆成三个同版本包互相配合openssh-server 提供 sshd 主程序、配置和 systemd 单元openssh-client 提供 ssh、scp、ssh-keygen 等客户端工具服务端生成 host key 也要靠它openssh-sftp-server 是 sftp 子系统实现。三个包版本号必须一致混装会出现 sftp 握手失败、scp 行为异常这类难查的问题。再往下编译期链接的共享库和运行时要调用的工具才是离线下载的难点。常见做法是先用apt-cache depends把依赖递归列一遍完整命令我放在第 3 章这里先明确一件事你到底需要哪些包。以下是我在一台干净 20.04 上抓出来的清单按层级排包名在 sshd 里的角色缺失时的典型表现openssh-serversshd 主服务、sshd_config、ssh.servicedpkg 报 unmet dependency无 ssh 服务openssh-clientssh/scp/ssh-keygen 客户端工具ssh 命令不存在host key 无法生成openssh-sftp-serversftp 子系统sftp 登录后直接掉线libssl1.1OpenSSL 1.1.1 运行库sshd: error while loading shared libraries: libcrypto.so.1.1libpam0gPAM 认证通道密码认证阶段卡死或循环重试libgssapi-krb5-2GSSAPI 与 Kerberos 支持GSSAPIAuthentication 时报错ucf配置文件合并工具postinst 依赖dpkg --configure -a 卡住不动procps进程与资源管理工具服务启停脚本异常lsb-baseLSB 初始化脚本基础init 风格脚本无法运行zlib1g、libc6基础运行库20.04 base 一般自带缺了全崩debconfdeb 安装交互框架静默安装时 postinst 卡在询问这张表可以直接当缺口对照表用dpkg 报缺哪个包回来看它在表里的角色就知道能不能跳过、要不要重下。最容易漏的是 libssl1.1它是被 libcrypto 等底层库间接带出来的手动抓依赖时经常被跳过后面的翻车记录里我会专门讲一次。2.2 换源救不了断网的机器离线包才是不变量很多人的第一反应是给 Ubuntu 20.04 换源——这个操作本身热度很高做法也不难把 sources.list 里的 archive.ubuntu.com 换成国内公网镜像源apt update后下载速度确实会快很多。但这里的适用前提是这台机器至少能出网只是源服务器慢。物理隔离的机器没有上游路由你换成任何源都没用因为根本没有数据流能回来。所以在我自己的工作流里离线包的地位是不变量在一台能上网的机器上把 deb 文件抓齐拷进内网之后离线机上所有操作都不再依赖网络。这样做的另一个好处是可重复同一个目录可以在十台同版本机器上反复安装出问题时可以收缩到包是否齐全、顺序是否正确两个变量而不是去猜网络玄学。抓依赖的时候宁可多抓十个不要少抓一个deb 平均几百 KB多抓的成本很低而漏掉一个要再回有网环境一趟这个时间成本在项目现场往往是致命的。2.3 版本匹配8.2p1 是 20.04 的宿命下载之前一定要确认两件事目标机器是 Ubuntu 20.04deb 包也必须来自 focal 源。OpenSSH 的版本号带 epoch 前缀完整写法是 1:8.2p1-4ubuntu0.xx 是安全更新号20.04 的 security 源里一直在推补丁。如果帮 20.04 下载时手里只有一台 22.04 的机器一个典型事故是22.04 的源里已经没有 libssl1.1 了你会得到一个 not installable 的绝望提示。验证版本最直接的办法是在有网的机器上执行apt-cache policy openssh-server看 candidate 是不是 focal 的 1:8.2p1 系列。如果手头只有一台 22.04 实体机就用 docker 起一个 ubuntu:20.04 容器去下载这是最不容易翻车的做法具体命令在下一章。另一个容易忽略的坑是 openssh-client 必须和服务端同版本两个包编译时带着相同的 ABI 约定跨版本混装时 sftp-server 尤其容易出问题。3. 在有网的机器上把离线包打出来两条路径3.1 路径 Aapt-cache depends 递归抓全依赖在能上网的 Ubuntu 20.04 机器或容器里先更新包索引确保能看到最新安全补丁版本apt update apt-cache policy openssh-serverpolicy 输出里 candidate 那一行如果是 1:8.2p1-4ubuntu0.11说明源正常。接下来用递归方式把 openssh-server 的所有依赖列出来交给 apt-get download 下载mkdir -p /opt/ssh-offline-deb cd /opt/ssh-offline-deb DEPS$(apt-cache depends --recurse --no-recommends --no-suggests \ --no-conflicts --no-breaks --no-replaces --no-enhances openssh-server \ 2/dev/null | grep -v ^ | grep -v :i386 | sort -u) apt-get download $DEPS命令的逻辑拆开讲apt-cache depends加--recurse会一层层展开依赖直到没有新包出现--no-recommends和--no-suggests去掉推荐级和建议级的包服务端运行不依赖它们--no-conflicts等参数避免把冲突关系和替代关系的包名也带进来。grep -v ^ 过滤掉输出里以空格开头的候选行这些行是或关系的备选包不是实际要装的grep -v :i386处理多架构环境防止把 i386 候选也列进下载名单。sort -u去重。2/dev/null丢弃 apt-cache 的警告输出避免污染变量。下载完成后目录里应该躺着十几个 deb。有一点经验之谈不要只抓 openssh-server 一个包再靠apt-get install -f去补离线环境下没有源-f修复只会白等超时。3.2 路径 Bdownload-only 配合干净容器如果手边没有 20.04 实体机docker 是最省事的下载环境docker run -it --rm -v /opt/ssh-offline-deb:/out ubuntu:20.04 bash apt update apt-get install --download-only --reinstall -o Dir::Cache::archives/out \ openssh-server openssh-client openssh-sftp-server ls -l /out这里用--reinstall和--download-only的组合让 apt 即使判断当前包已安装也强制把包体下载到指定目录而不是轻飘飘地提示已经是最新版本。-o Dir::Cache::archives是 apt 的运行时参数直接指定 deb 缓存目录省得从默认的 /var/cache/apt/archives 里翻文件。需要注意download-only只会下载你在命令行里点名的包依赖不会自动全部下载。所以进容器后我一般会先跑一遍 3.1 的递归下载再用download-only补包验证两条路径不冲突A 抓全量B 兜底确认版本。3.3 打包、校验、搬上 U 盘装完目录后先做一次完整性校验再离开有网环境cd /opt/ssh-offline-deb ls -l *.deb | wc -l md5sum *.deb md5sum.txt dpkg-deb --info openssh-server_*.deb | grep -E Package|Version|Dependsdpkg-deb --info用来抽查关键依赖行确认抓下来的确实是 focal 版本而不是本机 apt 缓存的旧版本。搬运介质我用 U 盘居多FAT32 分区没问题但要注意文件名一个都别改目录保持平铺结构。拷贝完成后回到有网机器上对比一次md5sum -c md5sum.txt因为 USB 读卡器偶尔会把大文件写坏这种损坏要等到 dpkg 安装阶段才暴露比拷贝时多消耗一顿午饭的时间。提示整个目录打成 tar.gz 带走也可以但到了内网机器上先tar -tzf看包内路径不要用绝对路径解包避免把以 / 开头的目录结构直接解到根分区。4. 离线机上的安装实战从 dpkg -i 到 ssh 服务拉起来4.1 装前检查系统版本与残留U 盘插到内网机器上第一步先确认系统本身是 focal再看有没有历史残留lsb_release -a dpkg -l | grep -E openssh|libssl1.1 || true为什么要先查残留很多内网镜像在装机时其实装过 openssh后来被加固脚本移除但 /etc/ssh 目录、旧 host key、旧配置可能还在。有残留时直接 dpkg -i 会触发 postinst 里的配置合并逻辑弹出交互式询问这在离线静默现场非常烦。常见做法是先把配置目录备份走sudo cp -r /etc/ssh /etc/ssh.bak-$(date %Y%m%d)备份完之后如果存在旧版本 openssh 包用 purge 把配置也清掉注意第一次安装不需要执行这一段sudo apt-get remove --purge -y openssh-server openssh-client openssh-sftp-server 2/dev/null || true--purge会连配置文件一起删除保证后续安装拿到的是干净的默认状态。如果卸载时报依赖错误不要硬删先看是哪个包在依赖它多半是 openssh-server 被 libpam 模块引用这种场景直接 dpkg -i 覆盖安装通常也能过。4.2 dpkg -i 的正确姿势所有 deb 放在同一个目录后一次全部传入比一个个装更稳cd /opt/ssh-offline-deb sudo dpkg -i ./*.debdpkg 会先解压所有包再按依赖关系统一配置同一次调用内互相满足依赖大部分情况一次成功。如果屏幕末端出现 Errors were encountered while processing: openssh-server先别慌也不要立刻apt-get install -f而是看具体缺什么dpkg -l openssh-server sudo dpkg --configure -adpkg -l第二列如果显示 iU说明包已解压unpacked但未配置postinst 没跑完。dpkg --configure -a会重新执行所有未完成的配置只要目录里依赖齐全这一步通常能把 sshd 的 postinst 拉通。这是我在现场处理离线 dpkg 时最常用的两句救场命令比任何自动化脚本都直接。-a是--pending的缩写只会处理半配置状态的包不会把所有东西重装一遍。4.3 首次启动前补 host key装完直接 start十有八九会在日志里看到 sshd: no hostkeys available。原因是 postinst 在生成 host key 时被打断或者 /etc/ssh 被清理过。手动补上sudo ssh-keygen -A sudo dpkg-reconfigure openssh-serverssh-keygen -A会为 rsa、ecdsa、ed25519 三种算法各生成一对 host keydpkg-reconfigure重新触发 postinst把缺失的服务端配置和目录权限补齐。然后检查一下文件属性和权限ls -l /etc/ssh/ssh_host_*_key.pub sudo chmod 600 /etc/ssh/ssh_host_*_key sudo chown root:root /etc/ssh/ssh_host_*_key公钥文件可以 644但私钥必须是 600 且属主 root否则 sshd 直接拒绝启动报 bad permissions。4.4 启动服务与开机自启单元名是 ssh不是 sshd这是 20.04 上最经典的认知错位sudo systemctl enable ssh sudo systemctl start ssh sudo systemctl status ssh --no-pager看到active (running)之后用端口检查确认服务真的在监听ss -tlnp | grep 22如果在容器里练习容器环境没有 systemd这一步换成service ssh start。实体服务器上 systemd 是标准不解释。4.5 离线环境把 sshd_config 调成可用状态新装好的 sshd 在离线环境有两个不舒服的点一是 UseDNS 开启时会反向解析客户端域名断网环境直接等超时登录卡两到五秒二是 GSSAPIAuthentication 默认开启内网没配 Kerberos 时每次认证都多一次超时等待。把这两项关掉登录延迟体感能明显改善cat /etc/ssh/sshd_config.d/95-offline.conf EOF UseDNS no GSSAPIAuthentication no PasswordAuthentication yes EOF sudo sshd -t sudo systemctl restart ssh这里刻意用sshd_config.d下的 drop-in 文件而不是去改主配置。OpenSSH 的规则是同一个参数先出现的生效主配置文件在前、drop-in 在后所以只有主配置里没有显式写过的参数drop-in 的写入才真正生效。Ubuntu 20.04 默认主配置里PasswordAuthentication是注释状态drop-in 写 yes 就能让普通用户密码登录。如果主配置里已经被手动打开过这一行再在 drop-in 里改就不生效了这就是很多人改了配置没变化的原因所在。注意drop-in 文件名必须以 .conf 结尾放在 /etc/ssh/sshd_config.d/ 下才会被 Include 读取数字前缀决定读取顺序。sshd -t先做语法检查通过再重启避免把唯一的远程通道重启到起不来。5. 避坑与常见问题六条真实翻车记录5.1 Unit sshd.service could not be found服务名不是 sshd现象systemctl start sshd报 Unit sshd.service could not be found管理端界面提示 the ssh or sshd service is unavailable。原因Ubuntu 20.04 的 openssh-server 包安装的 systemd 单元名是 ssh.service 和 ssh.socket没有 sshd.service。ssh 是服务名sshd 是二进制名两者经常被混着说。解决启动和察看都用systemctl start ssh排障看实时日志用journalctl -u ssh -f。如果系统启用了 ssh.socket 的 socket 激活注意停掉 ssh.service 不会立刻掐断已建立的连接要一起处理 socket 单元。5.2 Depends: libssl1.1 but it is not installable依赖递归漏了包现象dpkg -i 时提示 openssh-server 依赖 libssl1.1但 apt 无法给出可安装方案。原因下载时只执行了apt-get download openssh-server没有递归抓依赖。libssl1.1 是被底层运行库间接带出来的包装机镜像默认不一定带手动抓包时最容易跳过。解决回到有网环境用第 3 章的递归命令重抓或者在 ubuntu:20.04 容器里执行apt-get install -y libssl1.1后把 deb 拷出。这里有个更隐蔽的坑如果下载环境是 22.04源里根本没有 libssl1.1只能从 focal 源目录拿不要尝试跨版本硬装。5.3 no hostkeys availablepostinst 没跑完现象systemctl start ssh后状态是 failedjournalctl 里一行 sshd: no hostkeys available。原因包解压完成但配置阶段没执行完常见于加了--force-depends强行安装、安装过程被中断或者 /etc/ssh 目录被加固脚本清空。解决sudo ssh-keygen -A重新生成三套 host key再sudo dpkg-reconfigure openssh-server补跑 postinst。我的习惯是每次装完都顺手执行一次ssh-keygen -A即使没报错——host key 不存在时 ssh 客户端会直接在指纹确认阶段拒绝连接而服务器端往往只留这一行日志非常容易被误判成离线包有问题。5.4 deb 包损坏U 盘拷贝带来的玄学问题现象dpkg -i 时提示 unexpected end of file 或者 package architecture (amd64) does not match system。原因U 盘拷贝大文件时被写坏或者下载时混入了 i386 架构的依赖包。解决拷入内网后第一件事执行md5sum -c md5sum.txt不匹配的重新拷贝。架构不匹配则回到有网环境把 3.1 命令里的grep -v :i386加上删掉目录里的 i386 deb 重新下载。这类问题排查顺序是先校验文件完整性再谈依赖关系不要在损坏的包上浪费时间。5.5 Permission denied 但密码明明正确root 被 prohibit-password 挡住现象用户名密码完全正确ssh 登录始终提示 Permission denied登录端日志里连 Failed password 都没有。原因OpenSSH 7.0 之后默认的PermitRootLogin策略是 prohibit-passwordroot 用户只允许密钥登录不允许密码登录。很多离线机器现场只有一个 root 账户于是出现密码正确但永远进不去的诡异现象。解决先用普通用户登录验证基础链路。如果现场必须用 root在 drop-in 里显式写PermitRootLogin yes配合密码认证等密钥分发完成后再改回 no。配置调整后sshd -t systemctl restart ssh一条龙走完这个流程省不了。5.6 登录卡顿三五秒UseDNS 和 GSSAPI 双重超时现象客户端输入密码后要等三秒以上才出现 shell局域网内 SSH 尤其明显。原因sshd 开启 UseDNS 时在反向解析客户端 IP断网环境直接等待超时GSSAPIAuthentication 在尝试 Kerberos 交换但没有 KDC 响应。解决把 4.5 的 95-offline.conf 里的UseDNS no和GSSAPIAuthentication no配置落到目标机器重启 ssh。这个优化在完全隔离的机房效果立竿见影属于成本最低、收益最明显的一项。5.7 排障日志的正确打开方式离线环境没有浏览器也没有搜索引擎排障全靠本地日志。三个视图基本够用sudo journalctl -u ssh --no-pager | tail -50 sudo tail -50 /var/log/auth.log sudo sshd -d -D -p 2222sshd -d是 debug 模式前台运行并输出所有认证细节适合处理密码正确但进不去这类问题。生产端口别动用 2222 临时测试测完直接 CtrlC 停掉。6. 装完只是开始用 sshd -T 和 auth.log 做一次可验证的收尾6.1 三步验证法装完不等于能用我习惯按固定三步验收sudo sshd -T | grep -E passwordauthentication|permitrootlogin|port sudo ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub grep -E sshd.*(Accepted|Failed) /var/log/auth.log | tail -20sshd -T会解析全部配置并输出实际生效值。看到passwordauthentication yes、permitrootlogin no才说明配置真实落地而不是你以为的落地。host key 指纹打印出来客户端首次连接时对照 TOFU 指纹确认链路没有中间人。最后在 auth.log 里应该只能看到刚才测试登录产生的那一条 Accepted 记录如果混着大量 Failed password说明这台机器已经在被探测了。6.2 离线环境更要收紧安全基线离线容易让人放松实际上内网机器被扫、被爆破是常态装完 sshd 顺手把这张表里的项全部过一遍半小时内能结束配置项推荐值理由PasswordAuthenticationno密钥分发完成后离线环境改密码周期不可控密钥更稳PermitRootLoginnoroot 远程登录一旦被爆破后果不可逆MaxAuthTries3快速失败减小爆破窗口AllowUsers白名单用户不配置时任何系统用户都可尝试登录Port保留 22 或走内网约定端口改端口挡脚本扫描但会抬升管理成本每个参数写进独立的 drop-in 文件并把主配置文件里对应的行注释掉避免改了不生效的乌龙。有过一次深刻教训我给客户机房装完 sshd密码认证留着就撤了第二天早上 auth.log 里全是 Failed password for invalid user 的爆破记录那一刻才意识到离线不等于安全。从那以后每次离线装完 sshd我都会强制走一遍sshd -T加这张安全基线段确认输出没问题再交付。希望帮到你。本文还有配套的精品资源点击获取