如果你在一台 Ubuntu 24.04 机器上执行apt update,输出里突然出现一行带着sources.list(5)的告警,还明确说某个源缺少Signed-By,第一反应多半是懵的:系统刚更新完,什么都没动过,为什么源配置突然不合法了?这个报错我最近陆续处理了好几遍,表面看是某个源文件写得不够新,本质是 APT 在 24.04 上把安全校验收紧了一圈。这篇文章我就按照“看到报错 -> 定位根因 -> 补上 Signed-By -> 验证修复”的顺序,把整条排查链路完整讲一遍。
1. 报错现场:这条“Signed-By”错误到底出现在哪一步
1.1 我遇到的具体报错形态
先说最容易让人困惑的地方。报错不是出现在安装软件时,而是集中在apt update阶段。正常跑更新时,APT 会逐个读取/etc/apt/sources.list和/etc/apt/sources.list.d/下的源定义,然后去远程仓库拉取索引文件。到了 Ubuntu 24.04,如果某个源定义里没有声明Signed-By,而系统又找不到对应的信任密钥,就会看到类似下面这段输出:
W: https://packages.example.com/ubuntu/dists/noble/InRelease: Signed-By is not set for source 'example_source' (sources.list(5)) E: The repository 'https://packages.example.com/ubuntu noble InRelease' does not have a Release file. N: Updating from such a repository can't be done securely, and is therefore disabled by default. N: See apt-secure(8) manpage for repository creation and user configuration details.不同版本和不同仓库的输出措辞会有差异,有的 W 行写在前面,有的 E 行直接把仓库跳过,但核心信息是一致的:APT 想验证这个仓库的 Release 文件签名,却发现你没有在源定义里告诉它该用哪把钥匙。在我处理过的案例里,最典型的情况就是升级前本来正常的第三方源,升级后直接变成了“不能安全更新”的禁用品。
1.2 sources.list(5)里的数字5是什么意思
很多读者第一次看到sources.list(5)会误以为这是某个文件路径或者报错编号,其实这是 Linux 手册页的引用写法。sources.list(5)表示sources.list这份文档位于 man 手册的第 5 节,这一节专门放系统配置文件格式说明。APT 在告警里带上这个标记,是在提醒你:去查man sources.list,它会解释源定义文件应该怎么写。
我自己的排查习惯是,遇到 APT 的源相关报错,先打开手册再动手。24.04 的man sources.list和旧版本很不一样,里面详细解释了两种格式:传统的单行格式deb http://...,以及新的 DEB822 格式。绝大多数人报错,就是因为系统自动迁移之后你的源定义碰巧卡在两种格式之间,字段不全。
1.3 报错不只是烦人,它代表apt的安全防线失效
这条报错不是一个简单的格式问题。APT 的核心安全模型是:软件源索引文件必须经过 GPG 签名,系统要用一把你信任的公钥去验证签名。Signed-By这个字段就是用来指明“验证这个仓库应该用哪个 keyring 文件”。
如果缺失Signed-By,APT 理论上会退回使用全局信任的/etc/apt/trusted.gpg.d/和旧式trusted.gpgkeyring。但是 Ubuntu 24.04 对这套遗留机制的态度已经很明确了:apt-key被标记为弃用,全局信任区不再是无条件的默认信任。于是一个安全防线失效的源,就会被 APT 判定为不可信,宁可跳过也不冒险启用。理解了这一点,你就知道为什么不能简单地用trusted=yes绕过校验一劳永逸。
2. 为什么偏偏在更新系统之后爆发:apt源机制的两层变化
2.1 Ubuntu 24.04的源定义换成了DEB822格式
Ubuntu 22.04 时期,系统开始支持一种新的源定义格式,后缀是.sources,不再是单纯的行文本,而是一种“块状配置”,看起来很像 INI 文件。到了 24.04,新安装的系统默认就是这种写法。典型内容如下:
Types: deb URIs: http://archive.ubuntu.com/ubuntu/ Suites: noble Components: main universe restricted multiverse Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg每个字段一行,语义清晰。URIs是仓库地址,Suites对应发行版代号,Components是组件,Signed-By是密钥环路径。和传统deb http://... noble main相比,这种格式最大的优势就是能把源地址、套件、组件、签名钥匙分开管理,不再挤在一行里靠括号选项区分。
问题也出在这里:当系统升级或者第三方安装脚本自动写入源配置时,如果只写地址和套件,忘记补Signed-By,APT 在 24.04 上会直接把它当成一个没有安全来源的仓库来拒绝。
2.2 从apt-key到独立keyring:Signed-By为什么成了必需品
在旧版本 Ubuntu 上,第三方软件源安装流程通常是:执行apt-key adv --keyserver ... --recv-keys,或者下载公钥后用apt-key add导入。apt-key会把密钥统一放到/etc/apt/trusted.gpg.d/和/etc/apt/trusted.gpg里,所有源都依赖这个全局信任区。
问题在于,全局信任区过于集中。你只是为某个第三方仓库装了一把钥匙,结果这把钥匙拥有了系统里所有源的最高信任级别,一旦钥匙泄露,攻击者可以给任意仓库签名。更新到 24.04 之后,APT 社区明确建议每个源使用独立 keyring,并在源定义里用Signed-By指向它。这样的隔离设计下,第三方源的密钥没有资格验证官方源,官方源的密钥也管不到第三方仓库。
所以现在第三方源的标准做法变成两步:第一步下载公钥并转换为独立的 keyring 文件,第二步在源定义里通过Signed-By指定这个 keyring。跳过任何一步,都可能触发你看到的报错。
2.3 系统升级工具迁移源定义时会丢什么
有人会疑惑:为什么升级之前明明没报错,升级之后就有了?这里有两种常见情况。
第一种,升级工具把旧版/etc/apt/sources.list内容迁移到了新的.sources文件里,但迁移时只保留最基础的字段,没有自动补上Signed-By。旧版靠全局信任区工作,新版只见源定义、见不到钥匙,于是报错。
第二种,升级前你通过第三方脚本添加了软件源,脚本只写了源地址,钥匙用的还是老的apt-key。系统升级之后apt-key被弃用甚至不可用,全局信任区也没有对应 keyring,APT 无法完成签名验证,自然只能跳过。
我遇到过最隐蔽的情况是:第三方源定义在/etc/apt/sources.list.d/里,但对应的 keyring 文件存放在/etc/apt/keyrings/,升级过程中/etc/apt/keyrings/目录没有被迁移或者权限被改,最终整个目录空空如也,源定义里的Signed-By指向了一个不存在的文件。这类问题不仔细查目录根本发现不了。
3. 排查链路:先定位是哪个源、丢的是哪把钥匙
3.1 第一步:把apt update的完整输出存下来看
不要只看屏幕上最后几行,建议把完整输出重定向到日志文件:
sudo apt update 2>&1 | tee /tmp/apt-update.log然后重点找三类行:W:开头的告警行、E:开头的错误行、以及N:开头的提示行。告警行通常直接告诉你是哪个 URL、哪个源条目出了问题,比如上面例子里的https://packages.example.com/ubuntu/。这一步看起来很简单,但很多人会跳过,结果修了半天都不知道是哪个源在报错。
如果输出里没有 URL,只有源内部名称,那么去检查源定义文件里name或者URIs字段就能对上号。我建议不要依赖肉眼扫屏幕,日志文件能帮你反复比对。
3.2 第二步:盘点所有源定义文件
系统读取源定义的位置有两个:传统文件/etc/apt/sources.list,以及目录/etc/apt/sources.list.d/。在 24.04 上,官方源通常被放进/etc/apt/sources.list.d/ubuntu.sources。排查时要一起看:
cat /etc/apt/sources.list ls -l /etc/apt/sources.list.d/ cat /etc/apt/sources.list.d/ubuntu.sources我习惯用 grep 把关键字段全部列出来,这样一眼就能看到哪些源没有写Signed-By:
grep -rn "URIs\|Signed-By\|deb " /etc/apt/sources.list /etc/apt/sources.list.d/第三方源常见的文件名像example.list、example.sources,里面可能只有一行deb http://...,没有括号选项。看到这种文件基本就可以锁定问题源了。
3.3 第三步:确认密钥环的“三查”
确认源缺Signed-By之后,还要搞清楚对应密钥到底有没有装。做三个检查:
ls -l /usr/share/keyrings/ ls -l /etc/apt/keyrings/ ls -l /etc/apt/trusted.gpg.d//usr/share/keyrings/是系统软件包放置 keyring 的目录,/etc/apt/keyrings/是很多第三方安装脚本约定俗成放 keyring 的目录,/etc/apt/trusted.gpg.d/则是旧版 apt-key 遗留的信任区。如果一个源定义里写了Signed-By: /usr/share/keyrings/example-keyring.gpg,但这个文件不存在,那就是典型的“钥匙丢失”问题。
接着还要检查权限。APT 以_apt用户身份执行网络请求和验证,如果 keyring 文件权限是600且属主是 root,那么_apt用户可能读不到,报错信息里会出现类似“not readable by user '_apt'”的提示。正确的权限通常是644。
stat -c "%a %U %G %n" /usr/share/keyrings/example-keyring.gpg如果 keyring 存在、权限没问题,还可以看一眼里面的密钥指纹:
gpg --no-default-keyring --keyring /usr/share/keyrings/example-keyring.gpg --list-keys这一步能确认 keyring 里面到底有几把钥匙,是不是被升级过程清空了。
3.4 第四步:区分官方源、第三方源、PPA的特点
排查时要先把源分成三类,因为修法完全不同。
官方源本身带系统默认 keyring,缺失Signed-By报错多半是源文件被改写坏了。第三方二进制仓库,比如某些软件官方提供的 apt 源,一般需要单独 keyring 和单独的Signed-By字段。PPA 则属于第三方在 Ubuntu 生态上搭建的构建服务,在 24.04 里用新的add-apt-repository命令会自动写好带Signed-By的源定义,但如果是从旧系统升级过来的,可能还残留着旧式不完整源定义。
遇到问题先判断类别,再去翻对应的安装文档。我碰到过不少用户把第三方源的文档看完,然后跑回来抱怨官方源报错,其实是搞混了对象。
4. 修复方案:针对三种来源的Signed-By补法
4.1 官方默认源:重建ubuntu.sources里的标准字段
如果排查后确定问题出在官方源,最稳妥的办法不是手动去改 URL,而是把/etc/apt/sources.list.d/ubuntu.sources恢复成标准内容。核心是补上Signed-By,指向系统自带的官方 keyring:
Types: deb URIs: http://archive.ubuntu.com/ubuntu/ Suites: noble noble-updates noble-backports Components: main universe restricted multiverse Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg注意 24.04 的官方源文件里也可能有noble-security,它通常单独指到安全更新仓库。如果你不确定当前系统应该包含哪些套件,最简单的办法是在改之前先备份原文件:
sudo cp /etc/apt/sources.list.d/ubuntu.sources /etc/apt/sources.list.d/ubuntu.sources.bak备份之后把字段补全,再执行sudo apt update。如果网络源本身没问题,这时告警应该消失。官方源修复不需要重新下载密钥,因为ubuntu-archive-keyring.gpg由基础系统提供,几乎一定存在。
4.2 第三方二进制仓库:单独keyring加Signed-By
第三方仓库修复要分三步走。以packages.example.com/ubuntu为例。
第一步,下载公钥并转换为独立 keyring 文件。这里推荐用gpg --dearmor把 ASCII 格式的公钥转成二进制 keyring,而不是直接用apt-key:
curl -fsSL https://packages.example.com/key.gpg | sudo gpg --dearmor -o /usr/share/keyrings/packages-example-keyring.gpg第二步,修改源定义。如果是旧式单行格式文件/etc/apt/sources.list.d/example.list,改成:
deb [signed-by=/usr/share/keyrings/packages-example-keyring.gpg] https://packages.example.com/ubuntu/ noble main如果是 DEB822 格式的example.sources,则写成:
Types: deb URIs: https://packages.example.com/ubuntu/ Suites: noble Components: main Signed-By: /usr/share/keyrings/packages-example-keyring.gpg第三步,修复 keyring 文件权限:
sudo chmod 644 /usr/share/keyrings/packages-example-keyring.gpg这步很关键,我在前面提过_apt用户读取权限的问题。文件权限不对,即使Signed-By写对了,APT 依然会报错。
4.3 历史遗留PPA:用add-apt-repository安全重置
如果你确认源是一个 PPA,尽量不要手动拼接Signed-By,因为 PPA 的公钥 URL 是特定服务生成的,手动写错一个字符都会导致验证失败。标准做法是先移除再重新添加:
sudo add-apt-repository --remove ppa:example-team/example-ppa sudo add-apt-repository ppa:example-team/example-ppa在 Ubuntu 24.04 上,add-apt-repository会帮你把 keyring 下载到/etc/apt/keyrings/或者写入合适的位置,并自动在源定义中补上Signed-By。如果命令因为源已经损坏而无法执行,那就要先手动删除对应的源文件:
sudo rm /etc/apt/sources.list.d/example-team-example-ppa-*.list或者删除.sources文件,然后再用add-apt-repository重新添加。这比手动去找 PPA 的公钥指纹要省事得多。
4.4 特殊自建源:指向本地keyring的完整实例
如果你维护自建软件仓库,处理思路和第三方仓库一致,但可以同时用多个字段控制细节。比如只对某个架构开放:
Types: deb URIs: https://mirror.internal.example/repo/ Suites: noble Components: main Architectures: amd64 arm64 Signed-By: /usr/share/keyrings/self-hosted-keyring.gpg多写一个Architectures字段的好处是,避免 APT 去抓取本机根本不关心的架构索引,减少apt update的工作量和困惑。如果你还想限制语言文件,可以再加Languages: en之类的字段,但这和Signed-By无关了。
必须强调一句:如果仓库根本没有任何签名,那么任何Signed-By都没法解决。这时你要么给仓库生成并配置签名密钥,要么只能临时用trusted=yes绕过。用trusted=yes等于告诉 APT 这个源不需要验证,只适合在内网测试环境临时使用,公网机器上这么干等于把自己暴露在中间人攻击风险里,我非常不建议。
5. 修复后的验证与默认参数检查
5.1 apt update“零告警”才叫修复完成
补完Signed-By之后,重新执行sudo apt update,并且要确认输出里没有W:相关消息。有时候告警不会立刻消失,这是因为 APT 对源的缓存还停留在旧状态,可以先清理缓存再试:
sudo rm -rf /var/lib/apt/lists/* sudo apt update清理lists目录会强制 APT 重新下载所有源索引。这一步也能验证新的Signed-By是否真正生效,因为签名验证失败时 APT 会在下载阶段直接报错。
如果 update 正常,再用apt policy查看某个软件包是否能正确看到候选版本:
apt policy example-package能看到候选版本,说明源索引已经被正常读取。此时还要再确认没有遗留错误:
sudo apt upgrade --dry-run5.2 验证密钥环权限的几个关键点
修复完成后,我给每个处理的机器做一次权限快照,防止修复无效。关键点是 keyring 必须能同时被 root 和_apt用户读取,目录也要能进入。如果放在/usr/share/keyrings/下,目录本身应该是755;文件最好是644。
用下面这条命令可以批量检查:
find /usr/share/keyrings /etc/apt/keyrings -type f -name "*.gpg" -exec ls -l {} \;如果哪个 keyring 权限异常,直接改:
sudo chmod 644 /path/to/keyring另外要注意,用curl下载公钥之后再gpg --dearmor转换出来的 keyring 文件,默认属主是当前用户。如果当前用户是普通用户,用sudo写入/usr/share/keyrings/后属主会变成 root,这是好事。但如果文件被写进某个用户主目录下的keyrings文件夹,APT 可能因为路径不在系统目录里而影响读取,这时候我会把 keyring 统一挪到/usr/share/keyrings/下,因为这是最标准的位置。
5.3 不同报错变体会有什么不同表现
修了这么多次,我发现同一个根因可以表现出不同的报错文案,整理成表格方便对照:
| 报错表现 | 常见根因 | 处理方向 |
|---|---|---|
| 告警提示 Signed-By is not set (sources.list(5)) | 源定义缺少 Signed-By 字段 | 按源类型补上字段并指向正确 keyring |
| 错误提示 repository does not have a Release file | 源 URL 或套件名不对,或源被安全机制跳过 | 核对 URIs、Suites 和仓库实际目录结构 |
| 密钥文件存在但无法读取 | keyring 权限或属主不对 | 将权限设为 644,放在 APT 可读目录 |
| 提示 key is stored in legacy trusted.gpg.d | 使用了旧版 apt-key 机制 | 迁移到独立 keyring 并更新 Signed-By |
| 提示 no public key / KEYEXPIRED | keyring 缺失或密钥过期 | 重新下载公钥并更新指纹 |
看到表格里第一条和第二条经常同时出现,因为 APT 因签名校验失败而跳过源,往往也会连带提示找不到 Release 文件。不要只盯着E:行处理,先解决Signed-By缺失,再回头看E:是否自然消失。
6. 预防下次踩坑:升级前后要做的小动作
6.1 升级前备份源配置和密钥
Ubuntu 大版本升级之前,最值得做的备份操作就是打包/etc/apt目录。源配置、keyring、自定义权限都在这里:
tar czf /tmp/apt-backup-$(date +%Y%m%d).tar.gz /etc/apt升级后如果源报错,直接解压对比就能看出是哪个文件被动过。我处理过太多类似问题,用户根本想不起来升级前装过什么第三方源,备份就是最后的对照答案。
升级过程也可能触发新 apt 版本对旧格式的容忍度变化,所以升级完的第一件事是sudo apt update,而不是急着去安装软件。看到任何Signed-By告警,先记下来,不要用trusted=yes糊弄过去。
6.2 养成手动维护sources文件而不是依赖脚本的习惯
第三方安装脚本的写法参差不齐,有的会执行apt-key,有的会把源写入/etc/apt/sources.list.d/但完全不理会 keyring。在 24.04 上,我给自己的原则是:任何第三方源,一律手动走“独立 keyring + Signed-By”流程。
下载公钥时,优先选择仓库官方提供的key.gpg文件,而不是从某个 PGP keyserver 随机拉取,因为 keyserver 上的公钥指纹和仓库实际签名用钥可能不一致。如果仓库文档里已经给了明确的 keyring 下载链接,直接用那个链接,签名验证的匹配概率最高。
在手动维护源文件时,我还习惯在每个源文件顶部写注释,注明这个源是给哪个软件用的、密钥文件在哪。下次升级系统出问题时,不用满目录翻找上下文。
6.3 我处理这类问题时的几条原则
这类报错看起来吓人,实际上每次根因都很固定:要么Signed-By没写,要么写了但指向的 keyring 不存在,要么 keyring 存在但权限不对。排查顺序就按这个来:先定位源,再看 keyring,最后改配置。
我自己在 Ubuntu 24.04 上维护第三方源时,会刻意少用ppa:这种带全局信任色彩的方式,尽量每个源一个 keyring。效果是升级之后其他同事的机器陆续出现Signed-By报错,而我这台永远干净。所谓“避坑”,其实就是把当初踩过的每一步都固化成默认习惯。
如果你现在手头正被这个报错卡住,我的建议是:先别急着删源或加trusted=yes,按文章第三部分的排查链路走一遍,定位到具体源之后,再对照第四部分的修复方案操作。整个过程不会超过十分钟,而且修完后你会对 APT 的安全模型有一个非常扎实的理解。