如果你在 Ubuntu 上执行systemctl start ssh,结果被系统甩回来一句Unit ssh.service not found,先别急着怀疑 SSH 服务“坏了”,更别冲动重装系统。这个报错的信息量其实很明确:你请求 systemd 启动一个名为ssh.service的单元,而 systemd 在它的单元搜索路径里翻了半天,根本没找到这个单元文件。翻译成人话就是——不是服务崩溃了,是这台机器上压根没有对应的服务单元。
我最近帮人排查过好几台这样的环境,有从镜像恢复出来的旧系统,有最小化安装的新机器,还有跑在容器里的 Ubuntu 调试环境。它们的共同点非常一致:openssh-server这个软件包要么没装上,要么安装方式比较特殊,导致 systemd 里没有注册ssh.service。这篇文章我就按实际排查顺序来拆解,从报错原理到快速定位,从 Ubuntu 服务名差异到 WSL、容器里的特殊处理,最后再补几个我踩过的坑。你照着走一遍,基本都能让 SSH 恢复正常。
1. "Unit ssh.service not found"到底在说什么
1.1 systemd 找单元文件的过程
systemd 是 Linux 系统里的服务管理器,unit(单元)是它管理各种资源的最小单位,而service是其中最常见的一种单元类型。你执行systemctl start ssh时,systemd 要做的事情很简单:去规定好的路径下找名为ssh.service的单元文件,找到后解析里面的配置,再按配置把进程拉起来。
它搜索单元文件的位置是有优先级顺序的,主要包括:
/etc/systemd/system:管理员或第三方脚本放置的单元文件,优先级最高;/run/systemd/system:运行时生成的单元文件,重启后会消失;/lib/systemd/system:发行版软件包安装时写入的单元文件,是所有常规服务的来源。
当 systemd 把这三个主要位置都找遍了,仍然没有发现ssh.service,就会返回你在终端里看到的报错:Unit ssh.service not found.或者更完整一点:Failed to start ssh.service: Unit ssh.service not found.
这个流程可以类比成宿舍楼的访客登记:你报了一个房客的名字,宿管在花名册上查了一圈,发现根本没这个人。这时候任何人都不会认为“这位房客生病了”,只会觉得“花名册上压根没有登记”。系统报错也是同一个逻辑。
1.2 最常见的三类原因
虽然报错原因千奇百怪,但我在实际环境里遇到最多的是下面三种:
openssh-server软件包没有安装。这是最典型的情况,尤其是 Ubuntu 的最小化安装、精简版镜像,或者刚拉起来的容器环境里,系统只带了 SSH 客户端(openssh-client),并没有安装服务端。服务端没装,自然就不会有ssh.service单元。服务名记错了。SSH 的二进制程序叫
sshd,但 systemd 服务单元名不一定叫sshd.service。在 Ubuntu 和 Debian 系里标准单元名是ssh.service;在 RHEL、CentOS、Rocky 等发行版里才是sshd.service。如果你在 Ubuntu 上敲systemctl start sshd,同样可能出现 not found。单元文件缺失或 systemd 缓存异常。这种情况比较少见,通常出现在有人手动清理过
/lib/systemd/system目录,或者某些脚本和第三方包把一个不符合规范的单元文件写到了系统里。基本可以通过重新安装软件包或执行daemon-reload搞定。
1.3 先排除拼写和权限的干扰
systemd 对单元名是区分大小写的。你写成ssh、ssh.service都是合法的,但写成SSH或者SSH.service,systemd 还是会如实告诉你找不到。这个细节虽小,但我在指导别人排查时确实见过有人因为启动命令里混入大写字母,多绕了好几分钟。
另外要注意:如果你没有用sudo执行systemctl start ssh,系统通常不会返回 not found,而是会提示权限不足、需要 root 身份才能操作。所以当你完整看到Unit ... not found时,基本可以排除权限因素,把重心放到“单元是否存在”上。
下面是几条常见的提示和判断方向,方便你对照:
| 报错或现象 | 更可能的原因 | 下一步动作 |
|---|---|---|
Unit ssh.service not found. | 服务端未安装或单元名不对 | 检查openssh-server是否安装 |
Failed to start ssh.service: Access denied | 权限不足 | 命令前加sudo |
Unit SSH.service not found. | 大小写写错 | 改成ssh或ssh.service |
Failed to get unit file state for ssh.service: No such file or directory | 单元文件确定不存在 | 安装对应软件包 |
2. 别急着重装系统:三分钟定位问题出在哪一层
2.1 一条命令链路从上到下探一遍
很多人一看到报错就开始瞎试,试了一圈没效果才回头查包,效率很低。我的习惯是先跑一组命令,从上到下把 SSH 这个问题拆成“客户端、服务端、单元文件、进程、端口”五个层,逐层确认。
打开终端,按顺序执行:
ssh -V # 检查 SSH 客户端是否存在及版本 which sshd # 检查 sshd 二进制是否存在 dpkg -l | grep openssh-server # 检查服务端软件包是否安装 systemctl list-unit-files | grep -i ssh # 列出系统里所有和 ssh 有关的 systemd 单元 systemctl status ssh --no-pager # 查看 ssh.service 当前状态 sudo ss -tlnp | grep ':22 ' # 查看 22 端口是否在监听 ps -ef | grep '[s]shd' # 查看 sshd 进程是否在跑这些命令本身都不复杂,但它们组合在一起,能很快告诉你问题出在哪一层。我通常这样解读输出:
| 检查项 | 期望结果 | 如果异常说明什么 |
|---|---|---|
ssh -V | 输出 OpenSSH 客户端版本 | 如果提示 command not found,说明连客户端都没装 |
which sshd | 输出/usr/sbin/sshd | 如果没输出,服务端二进制不存在 |
dpkg -l | grep openssh-server | 看到ii开头的行 | 如果没任何输出,服务端包没装 |
systemctl list-unit-files | grep -i ssh | 看到ssh.service | 如果只有ssh.socket而没有ssh.service,需要进一步处理 |
systemctl status ssh | 显示 loaded 且 active | 如果提示 not found,回到包安装这一步 |
sudo ss -tlnp | 22 端口处于 LISTEN | 如果没监听,服务可能没起来或配置了其他端口 |
ps -ef | grep '[s]shd' | 能看到 sshd 主进程 | 看不到进程可能是服务没启动或 socket 激活模式未触发 |
这组命令跑完,绝大多数问题都能定位到八九不离十。我自己在实际排查中,经常发现最终原因就是最简单的第一条:dpkg -l | grep openssh-server没有输出,也就是服务端压根没装。后面的 systemctl 报错只是“果”,不是“因”。
2.2 "服务没启动"和"服务不存在"完全是两回事
插入一个很容易被混淆的概念:systemctl status ssh显示inactive(服务已存在但没跑起来),和systemctl start ssh报Unit ssh.service not found(服务单元根本不存在),是两个完全不同的状态。
前者是“人已经住进宿舍楼了,只是现在不在房间里”,你随时可以叫他回来,也就是执行sudo systemctl start ssh就能启动。后者是“花名册上查无此人”,你无论执行多少次start或者enable,系统都只会告诉你查无此人。
我在社区里经常看到有人贴出 not found 的报错,然后追问“为什么我systemctl enable ssh也不行”。原因很简单:enable的作用是让一个已经存在的单元文件开机自启,不是创建一个单元文件。单元文件都不存在,enable 自然无从谈起。
如果你想知道系统里到底有哪些 SSH 相关的单元,可以执行:
systemctl list-unit-files | grep -i ssh输出里常见的可能有ssh.service、ssh.socket、ssh@.service等。只要这些名字出现在列表里,说明软件包装得没问题,剩下的只是启动哪个、怎么启动的问题。
3. Ubuntu 的 SSH 服务单元比你想象中多一个 ssh.socket
3.1 服务名不是按二进制名来的
SSH 服务端的程序二进制叫/usr/sbin/sshd,这是一个很固定的命名。但 systemd 单元文件叫什么,是发行版打包的人决定的,不一定和二进制名保持一致。
我见过很多从 CentOS 转过来的朋友,习惯性地在 Ubuntu 上敲systemctl status sshd,然后一脸懵地看到 not found。其实只要把思维调成“先看发行版,再记服务名”就能避免:
| 系统 / 发行版系 | systemd 单元名 | 说明 |
|---|---|---|
| Ubuntu / Debian | ssh.service | 服务端由openssh-server提供 |
| Ubuntu / Debian 较新版本 | ssh.socket | 可选启用,按需激活 SSH |
| RHEL / CentOS / Rocky / AlmaLinux | sshd.service | 保持传统的 sshd 命名 |
| Fedora | sshd.service | 同样是 sshd |
| Arch Linux / Manjaro | sshd.service | 也使用 sshd |
所以在 Ubuntu 上,最稳妥的命令永远是systemctl status ssh。如果你敲systemctl status sshd也成功了,那多半是系统里存在sshd对ssh的别名或兼容单元,但不要依赖这个行为,按发行版官方推荐来最省心。
3.2 ssh.socket 导致"服务没跑却能连上"
Ubuntu 较新版本的openssh-server除了ssh.service之外,还会额外安装一个ssh.socket单元。这个东西的作用是开启 socket 激活模式:系统启动后并不直接拉起 sshd 进程,而是由 systemd 在 22 端口上创建一个监听 socket。当有客户端真的发起连接时,systemd 才临时启动ssh.service去处理连接。
这种设计在低配机器上能省一点内存,因为不连 SSH 的时候 sshd 进程不常驻。但它带来的困惑也很明显:systemctl status ssh显示inactive,你以为服务挂了,结果ssh localhost又能秒连上,服务在连接瞬间被自动拉起来了。
如果你遇到这种情况,先别急着反复 restart,先看一眼:
systemctl status ssh.socket sudo ss -tlnp | grep ':22 '如果ssh.socket显示 active,并且 22 端口处于 LISTEN 状态,那么 SSH 服务实际上是可用的,只是没有常驻进程而已。
如果你不喜欢按需激活,希望 sshd 开机就常驻运行,可以把模式切成传统方式:
sudo systemctl enable --now ssh.service sudo systemctl disable --now ssh.socket我建议先启动ssh.service再关闭ssh.socket,这样可以避免中途出现空窗期。改完之后再执行systemctl status ssh,应该能看到active (running)。
4. 从零修复:安装、启用、放行、验证一条龙
4.1 最小环境下的完整操作步骤
如果是刚装好的 Ubuntu 最小系统,或者容器镜像里什么都没配,下面是完整的修复链路。前提是你有 root 权限,或者能用 sudo。
第一步,更新软件源索引:
sudo apt update第二步,安装 OpenSSH 服务端:
sudo apt install -y openssh-server这个包会把你需要的/usr/sbin/sshd二进制、配置文件、systemd 单元文件一次装齐。装完之后可以用下面的命令确认单元文件已经存在:
dpkg -L openssh-server | grep systemd systemctl list-unit-files | grep -i ssh正常能看到类似/lib/systemd/system/ssh.service的路径,以及ssh.service出现在 list-unit-files 输出中。
第三步,启用并启动服务:
sudo systemctl enable --now ssh如果一切正常,这一步不会报错。再执行一次状态确认:
systemctl status ssh --no-pager看到active (running)就说明服务端已经起来了。
第四步,在本机验证一次 SSH 回环连接:
ssh 你的用户名@127.0.0.1第一次连接会提示确认主机指纹,输入yes再输入密码,能登录就说明整个 SSH 链路已经通了。
4.2 本机能连和外网能连是两件事
很多人卡在这一步:本地ssh 127.0.0.1能通,但从另一台机器连就是超时。问题往往出在监听地址和防火墙。
先检查 sshd 实际监听的地址:
sudo ss -tlnp | grep ':22 '如果输出里的地址是127.0.0.1:22,那说明 sshd 只监听了回环地址,外部网络当然连不进来。这通常是因为/etc/ssh/sshd_config里显式配置了ListenAddress 127.0.0.1。把这一行注释掉,或者改成0.0.0.0(表示监听所有IPv4地址),然后重启服务:
sudo systemctl restart ssh如果监听地址已经是0.0.0.0:22,外部还是连不上,继续检查防火墙:
sudo ufw status如果 UFW 是 active 状态,而且 22 端口没放行,那就执行:
sudo ufw allow OpenSSH这条命令会放行 OpenSSH 的默认端口 22。如果你改了端口,比如改成 2222,就要相应执行:
sudo ufw allow 2222/tcp还有一层不能忽略:如果你用云服务器,云控制台里的安全组规则同样需要放行对应端口。本地防火墙放行了,安全组没放行,照样连不上。这两层是并列关系,不是替代关系。
4.3 改端口、改监听地址时最容易把自己锁在外面
我见过不止一次:为了安全,有人把 SSH 端口从 22 改成 2222,重启服务之后发现连接断了,自己也被锁在外面,最后只能通过云平台的网页 console 或本地登录救回来。
这里分享一个我自己的铁律:改 SSH 配置之前,先开好第二个终端会话,保持一个已登录的连接不要断开。改完配置后,在第二个会话里做验证,确认新端口能登录了,再退出旧会话,或者再重启服务。
另外,改完/etc/ssh/sshd_config之后不要盲重启,先用下面的命令做语法预检:
sudo sshd -t如果配置有语法错误,这条命令会明确告诉你出错的行。等检查通过,再重启或重载:
sudo systemctl reload ssh这里要注意reload和restart的区别:修改密码认证、允许 root 登录这类选项,reload就能让配置平滑生效,不影响已有连接;修改端口这类涉及监听方式的选项,必须restart才会重新绑定端口。
4.4 安装后仍然 not found 的少数情况
按上面的步骤装完包之后,99% 的情况都能解决。但如果systemctl status ssh仍然报 not found,就要考虑是不是单元文件损坏或缓存问题。
先看看单元文件是否真的存在、内容是否正常:
ls -l /lib/systemd/system/ssh.service再让 systemd 重新加载所有单元:
sudo systemctl daemon-reload必要时还可以重新安装一次软件包,把文件覆盖回去:
sudo apt install --reinstall -y openssh-server还有一种可能:系统里同时存在ssh.service和ssh.socket,你启用了其中一个,另一个状态很奇怪。这种时候不要纠结,直接关掉 ssh.socket,切回传统常驻模式,最简单也最直观。
5. WSL 和容器里没有 systemd 怎么办
5.1 WSL 老版本:用 service 而不是 systemctl
很多人喜欢在 Windows 的 WSL 里装 Ubuntu 当开发环境,然后同样遇到 not found 或者更诡异的报错。这里要分情况讲,因为 WSL 的 init 系统版本差异很大。
先确认当前环境里 pid 1 是什么:
ps -p 1 -o comm=如果输出是init或者init.exe,说明这个 WSL 发行版没有启用 systemd。这种情况下,你执行systemctl可能得到的是另一类错误,比如:
System has not been booted with systemd as init system (PID 1). Can't operate.
也可能是当前的 systemctl 命令本身就不存在。无论哪种,解决思路都一样:不要依赖 systemd,用传统的service命令管理 SSH。
先装包:
sudo apt update sudo apt install -y openssh-server然后启动:
sudo service ssh start sudo service ssh status这种环境下,SSH 服务是可以正常跑起来的。如果你想用回systemctl那套体系,也可以给 WSL 显式启用 systemd。编辑/etc/wsl.conf,加入:
[boot] systemd=true之后在 Windows 侧执行wsl --shutdown重启 WSL,再进来执行systemctl status ssh就能用正常姿势管理了。
5.2 容器里直接拉起 sshd 进程
容器环境又是一个高频踩坑点。绝大多数容器镜像默认没有 systemd,特别是用 Ubuntu 官方镜像跑起来的时候,你会发现 systemctl 要么不存在,要么报 D-Bus 相关的错误。这并不是 Ubuntu 坏了,而是容器设计上就不希望跑 init 系统。
在容器里想开 SSH,最直接的办法是绕开 service manager,直接启动 sshd 进程。Dockerfile 可以这样写:
FROM ubuntu:22.04 RUN apt update && apt install -y openssh-server \ && mkdir -p /run/sshd \ && echo 'root:你的密码' | chpasswd EXPOSE 22 CMD ["/usr/sbin/sshd", "-D"]这里有三个细节值得留意:
第一,mkdir -p /run/sshd不能省。sshd 启动时需要一个权限分离目录/run/sshd,容器里这个目录默认不存在,缺了它会报错Missing privilege separation directory: /run/sshd。
第二,容器里默认 root 用户登录是受限的。如果要方便调试,可以在 sshd_config 里临时加上PermitRootLogin yes,但生产环境千万不要这么干,直接用docker exec进容器比开 SSH 安全得多。
第三,如果容器镜像不是 Ubuntu 而是 Alpine 这类轻量发行版,包管理器变成了 apk,命令也完全不同。标题虽然是 Ubuntu,但遇到容器问题时先确认基础镜像,再决定用 apt 还是 apk,能省不少时间。
6. 排查 unit not found 的通用三板斧
6.1 先搞清楚 unit 文件由哪个包提供
遇到任何Unit xxx.service not found,我的首要动作永远是问一个问题:这个服务名对应的软件包装了吗?
在 Ubuntu 上,你可以反查某个 systemd 单元文件属于哪个包:
dpkg -S /lib/systemd/system/ssh.service如果输出类似openssh-server: /lib/systemd/system/ssh.service,说明文件确实由该包提供。如果没有输出,说明包没装,问题就到这里结束。
想知道系统里是否存在某个服务的在线信息,可以借助apt-file:
sudo apt install -y apt-file sudo apt-file update apt-file search /lib/systemd/system/ssh.service这个方法对排查一些不熟悉的服务名特别有用,比如你想知道nginx.service应该装哪个包,一条搜索就能找到答案。
6.2 再确认 systemd 状态与单元是否被加载
既然已经知道是 systemd 在管服务,那就把 systemd 相关的几个状态一次看完。
systemctl list-unit-files | grep name解决的是“这个单元文件存不存在”。如果存在,再看看单元状态:
systemctl status 名称这里可能会碰到第三层干扰:单元文件在,但 systemd 的加载缓存里没有它。这种情况通常发生在你手动往/etc/systemd/system里放了新单元文件之后。解决办法很简单:
sudo systemctl daemon-reload让 systemd 重新扫描所有单元文件路径,然后再次启动服务。如果单元文件在但状态显示为masked(被屏蔽),那就需要先解除屏蔽:
sudo systemctl unmask ssh不过ssh.service很少会被 mask,倒是某些第三方服务如docker.service偶尔会碰到。了解这个机制后,举一反三同样适用。
6.3 我这些年养成的几个小习惯
最后分享几个看起来不起眼、但真实帮我少踩过很多坑的习惯,也算是对这次排查做个收尾。
第一,拿到新机器的第一分钟先看两样东西:ps -p 1 -o comm=看 init 类型,systemctl list-unit-files | grep -i ssh看服务单元是否存在。这两个输出比任何教程都可靠,直接决定后续该用哪套管理命令。
第二,远程维护 SSH 之前永远准备退路。改端口、改认证方式、调防火墙,任何可能有风险的操作,都先开第二个已登录会话,或者确认云平台的本地 console 可用。改完配置后先用另一个会话验证,验证不过就立即改回去。这条规矩帮我避开了好几次把自己锁在门外的尴尬。
第三,遇到报错先读原文,再动手。Unit ssh.service not found和Connection refused是两个完全不同的故障方向,前者大概率是没装服务端或服务名错了,后者可能是没启动、端口错误或防火墙拦截。跟着报错原文走,比到处搜索“Ubuntu SSH 连不上”这种大而全的关键词要快得多。
整套排查下来,你会发现 SSH 在 Ubuntu 上出问题,绝大多数都不是什么高深的技术难题,而是“服务端没装”或“服务名用错”这类基础认知问题。把这个流程走熟之后,再遇到新手问同样的问题,你也能一眼定位、直接给出答案。希望这篇能帮你把这条链路彻底理顺,下次再看到 not found,不再慌。