SSD Nodes Learn Hosting plans →
指南 Matt Connor作者: Matt Connor

Debian unattended-upgrades 不自动更新怎么办?

Debian 默认禁用 unattended-upgrades,导致系统无法自动安装补丁。本文教你如何通过 dpkg-reconfigure 启用服务,排查 apt-daily 定时器配置,并验证 Origins-Pattern 规则以确保自动升级正常运行。

为什么 unattended-upgrades 在 Debian 新安装后不运行

在 Debian 上,即使安装了 unattended-upgrades,它也可能从未执行过任何升级。这是因为安装软件包和启用该功能是两个独立的步骤。该软件包在配置前会询问一个 debconf 问题,而 Debian 安装程序会为该问题存储 false 答案。Ubuntu 对同一问题的回答方式不同,这就是为什么同一个软件包在 Ubuntu 上看起来运行正常,而在 Debian 上却显得无法工作的原因。

系统中没有任何地方会提示这一点。启动时没有错误,登录时没有警告,也没有可供查阅的日志文件,因为负责写入该日志的代码从未被调用过。执行一条命令即可将其开启。本指南的其余部分将介绍开启后仍会阻止其运行的四个因素:允许从哪些仓库进行升级、systemd 定时器何时真正触发、如何获知运行失败,以及机器是否被允许自动重启。

查看系统当前配置,再进行任何修改

dpkg -l unattended-upgrades | tail -n 1
sudo apt install -y debconf-utils
sudo debconf-show unattended-upgrades
cat /etc/apt/apt.conf.d/20auto-upgrades
apt-config dump APT::Periodic

debconf-show 会输出 unattended-upgrades/enable_auto_updates 的已保存值。该行开头的 * 表示该值已被显式设置,而不是使用软件包默认值。在 Debian 安装程序构建的机器上,执行设置的是安装程序。

cat 可能会输出 cat: /etc/apt/apt.conf.d/20auto-upgrades: No such file or directory。仅这一点就能解释为什么没有任何动作:没有该文件时,就不会有定期任务,因此不会安排任何操作。

apt-config dump 是关键命令。APT 会按文件名顺序读取 /etc/apt/apt.conf.d/ 中的文件并合并配置,因此在 99local 中设置的普通值会覆盖 20auto-upgrades 中的同名值。只有没有扩展名或使用 .conf 扩展名的文件名才会生效,因此类似 99local.bak 的文件会被跳过。读取单个文件只能看到该文件中的配置。apt-config dump 才能显示 APT 实际会执行的操作。

以下两个键决定是否会执行任何操作:

  • APT::Periodic::Update-Package-Lists 刷新软件包列表,这也是手动执行 apt update 时完成的任务。
  • APT::Periodic::Unattended-Upgrade 执行升级本身。

它们的值不是 true 或 false,而是以天为单位的间隔。"1" 表示“如果过去 1 天内尚未执行,则执行此操作”,"7" 表示每周执行,"0" 表示永不执行。APT::Periodic::Unattended-Upgrade "0"; 是有效配置,但会永久禁止所有操作,且不会报告任何错误。如果输出中该键的值为 0,或者完全没有该键,那么原因就在这里。

启用方式:使用 dpkg-reconfigure,或手动写入密钥

sudo apt update
sudo apt install -y unattended-upgrades
sudo dpkg-reconfigure --priority=low unattended-upgrades

该问题的优先级较低,因此普通安装过程中不会显示。dpkg-reconfigure 会显示低优先级问题,不受当前配置的 debconf 优先级影响,因此 --priority=low 只是明确执行它原本就会执行的操作。应避免使用的唯一选项是 --default-priority,它会关闭该行为并再次隐藏问题。在对话框中选择 yes。随后,软件包的 postinst 脚本会根据你的回答写入 /etc/apt/apt.conf.d/20auto-upgrades。

如果通过脚本构建计算机,就没有可供回答的对话框,因此应先设置答案:

echo 'unattended-upgrades unattended-upgrades/enable_auto_updates boolean true' \
  | sudo debconf-set-selections
sudo dpkg-reconfigure -f noninteractive unattended-upgrades
apt-config dump APT::Periodic

也可以直接写入这两个密钥:

printf 'APT::Periodic::Update-Package-Lists "1";\nAPT::Periodic::Unattended-Upgrade "1";\n' \
  | sudo tee /etc/apt/apt.conf.d/20auto-upgrades
apt-config dump APT::Periodic

这种方式会立即生效,但会留下隐患。debconf 中的答案仍然是旧值,因此下次执行 dpkg-reconfigure 或重新安装该软件包时,软件包会根据 debconf 重新写入文件,并且完全不输出任何信息地撤销你的修改。应同时设置这两处,或者只设置 debconf,并让 postinst 负责管理该文件。

这也是它与另一类系统之间唯一真正的区别。在另一类系统中,安装程序会为你启用同一个软件包,因此 Ubuntu 的 unattended-upgrades 设置 从一台已经自动安装补丁的计算机开始,主要用于调整配置。以下内容适用于两类系统。

Debian 实际会为您安装哪些更新?

启用定时器并不等于同意安装所有内容。每个软件包源都包含发行版元数据:来源、标签、套件、代号和站点。unattended-upgrades 会查看每个可升级软件包的候选版本,读取其来源的元数据;只有当该来源匹配 Unattended-Upgrade::Origins-Pattern 中的条目或旧版 Unattended-Upgrade::Allowed-Origins 列表中的条目时,才会安装该版本。没有匹配项就不会升级,这是设计如此,且不会显示提示。

先输出您的匹配模式,再输出用于匹配的元数据:

apt-config dump Unattended-Upgrade::Origins-Pattern
grep -H -E '^(Origin|Label|Suite|Codename|Components):' /var/lib/apt/lists/*Release
apt-cache policy

grep -H 会在每行保留文件名,因此您可以看到每组元数据属于哪个软件源。这些字段值就是匹配模式的右侧内容。模式中的 ${distro_codename} 会在运行时替换为当前发行版的代号,因此发行版升级后同一个文件仍可继续使用。

请将您自己的模式与旁边的元数据一起查看,因为“我只会获得安全修复,还是也会获得点版本更新”的答案只写在这两份列表中:

  • 指定 label=Debian-Security 的模式会匹配安全归档。这是 Debian 发布安全公告的位置。
  • 指定 -updates 套件的模式会匹配 stable-updates。该套件包含 Debian 在点版本之间发布的更新,例如时区数据。
  • 指定普通发行版套件的模式会获取已发布的点版本变更。这会带来更多变更,也需要您进行更多测试。
  • 您自行添加的软件源在为其编写模式之前完全不会匹配任何内容。

最后一种情况经常令人意外。第三方软件源有自己的来源和标签,因此 unattended-upgrades 会看到候选版本,也会看到其来源没有匹配任何模式,然后跳过它。为第三方软件源添加模式时应谨慎决定,因为供应商软件源可能在同一套件中发布新的主版本。这样一来,您就同意 unattended-upgrades 在夜间自动升级该软件的主版本。

unattended-upgrades 也绝不会将系统从一个 Debian 发行版迁移到另一个发行版。它只会在当前发行版内部升级软件包。从一个 stable 发行版升级到下一个版本,仍然需要由您自行安排并手动执行。

修改列表时,请记住 APT 列表采用追加方式。另一个文件中的第二个 Origins-Pattern 块会追加到系统提供的列表,而不是替换它。如果您的目的是替换,请先清空原列表:

#clear Unattended-Upgrade::Origins-Pattern;
Unattended-Upgrade::Origins-Pattern {
  "origin=Debian,codename=${distro_codename},label=Debian-Security";
  "origin=Debian,codename=${distro_codename}-security,label=Debian-Security";
};

请将本地修改放在一个按排序顺序排在系统提供文件之后的新文件中,例如 /etc/apt/apt.conf.d/52unattended-upgrades-local。50unattended-upgrades 是由软件包管理的配置文件,因此编辑后,如果后续软件包升级提供了该文件的新版本,升级过程会暂停并询问如何处理您的版本。单独使用新文件不会发生冲突。

还可以顺便输出另外两个值得注意的键。Unattended-Upgrade::Allowed-Origins 是同一机制的旧格式,写成 origin:archive 对,并且系统仍会读取它。因此,从教程复制配置后,通常会同时存在两份列表,却无法明确判断实际匹配的是哪一份。Unattended-Upgrade::Package-Blacklist 保存与软件包名称匹配的正则表达式;其中过于宽泛的表达式可能会阻止远超预期的软件包升级。下面的试运行会输出实际生效的配置。

为什么任务没有按预期时间运行?

两个 systemd 定时器负责驱动此功能,它们各司其职。apt-daily.timer 启动 apt-daily.service,用于刷新软件包列表并下载更新。apt-daily-upgrade.timer 启动 apt-daily-upgrade.service,该服务负责调用 unattended-upgrade。两者均使用不同的参数运行 /usr/lib/apt/apt.systemd.daily。如果第二个定时器被禁用或屏蔽,即使两个周期性密钥都能读取 1,也不会安装任何内容。

systemctl list-timers 'apt-daily*' --all
systemctl is-enabled apt-daily.timer apt-daily-upgrade.timer
systemctl cat apt-daily-upgrade.timer

list-timers 会显示每个单元的 NEXT、LEFT、LAST 和 PASSED。没有 NEXT 的定时器不会运行。is-enabled 输出 masked 表示该服务已被强制关闭,此时修改 apt.conf.d 中的内容也无法使其生效。

现在查看 systemctl cat 输出的 [Timer] 部分。OnCalendar 是定时器可能触发的最早时间。RandomizedDelaySec 会在此基础上增加一个随机等待时间,以避免大量 Debian 机器在同一秒内访问镜像源。这就是 NEXT 列显示的时间与 OnCalendar 不符的原因,也是为什么昨天的运行时间与今天不同的原因。这是设计使然。Persistent=true 表示如果机器在预定时间处于关机状态,任务将在下次开机后立即运行,而不是跳过当天。

若要调整时间窗口,请覆盖单元配置,而不是直接编辑原文件:

sudo systemctl edit apt-daily-upgrade.timer
[Timer]
OnCalendar=
OnCalendar=03:30
RandomizedDelaySec=30m

必须包含空的 OnCalendar= 行,因为列表类型的单元设置会进行累加:如果省略该行,系统会保留原有的调度计划并增加一个新的。systemctl edit 会自动重新加载 systemd 配置,请使用 systemctl list-timers 'apt-daily*' 确认结果,并查看新的 NEXT。

测试时无需等待定时器触发:

sudo systemctl start apt-daily-upgrade.service
journalctl -u apt-daily-upgrade.service --since -1h

在 /etc/cron.daily 中查找内容时,有一点容易引起困惑:APT 仍然为没有 systemd 的系统提供 /etc/cron.daily/apt-compat。使用 cat 查看该文件。在 systemd 系统上,它会提前退出,因此任务不会被重复执行。

验证其是否有效:unattended-upgrade --dry-run --debug

sudo unattended-upgrade --dry-run --debug

命令使用单数形式,而软件包名称使用复数形式。Debian 12 和 13 还提供了 /usr/bin/unattended-upgrades,因此其中的复数形式不再以 command not found 结尾;但文档规定的命令,以及本指南中始终使用的命令,仍是单数形式的 unattended-upgrade。

这条命令几乎可以回答所有“为什么跳过了那个软件包”的问题,因为它会输出自身的判断依据。输出靠前的位置会列出根据您的匹配模式计算出的来源:

Allowed origins are: ...

随后为每个候选软件包输出一行,并附带它将安装的版本对应的来源记录:

Checking: curl ([<Origin component:'main' archive:'stable' origin:'Debian' label:'Debian' site:'deb.debian.org' isTrusted:True>])

然后输出它将处理的软件包列表;如果当前系统没有需要处理的内容,则输出:

No packages found that can be upgraded unattended and no pending auto-removals

结合前两部分,诊断结果就很明确。找到您预期会升级的软件包对应的 Checking: 行。将该行的来源记录逐字段与上方输出的允许来源进行比较。只要有一个字段不匹配,最常见的是 label 或 archive,这就是该软件包被跳过的全部原因。

--dry-run 只在内存中模拟操作,不会安装任何内容,因此可以随时运行。它仍会追加写入 /var/log/unattended-upgrades/unattended-upgrades.log。

如果来源匹配,但软件包仍被保留,请依次检查以下情况:

  • apt-mark showhold 会列出您或其他工具设置了固定版本的软件包。unattended-upgrades 不会处理被保持的软件包。
  • 此次升级需要删除或添加其他软件包。除非相关设置项允许,unattended-upgrades 会避免执行此类操作。请与 sudo apt-get -s upgrade 的结果进行比较,该命令会在不应用这些安全规则的情况下显示相同的判断。
  • 一次中断的运行导致 dpkg 处于半配置状态。使用 sudo dpkg --configure -a 修复,然后再次检查。
  • /var 没有可用空间,因此无法下载或解包任何内容。使用 df -h /var 检查。
  • /boot 中积累了旧内核,导致下一次内核升级失败。检查 df -h /boot,并输出 apt-config dump Unattended-Upgrade::Remove-Unused-Kernel-Packages,确认是否启用了清理功能。

如果定时器运行期间手动执行 apt,您会看到 Could not get lock /var/lib/dpkg/lock-frontend。这条消息表示 unattended-upgrades 正在正常工作。等待它完成即可。

如何排查运行失败?

首先查看日志,无论是否进行其他配置,日志始终存在:

sudo tail -n 40 /var/log/unattended-upgrades/unattended-upgrades.log
sudo tail -n 40 /var/log/unattended-upgrades/unattended-upgrades-dpkg.log
journalctl -u apt-daily-upgrade.service --since -7d

第一个文件是决策日志:记录了检查内容、选择的操作以及安装的软件包。第二个文件包含原始的 dpkg 输出,如果软件包的 postinst 脚本执行失败,错误信息会显示在此处。如果升级过程发生在机器关机期间,则会生成单独的关机日志。

邮件是常用的报告途径:

Unattended-Upgrade::Mail "you@example.com";
Unattended-Upgrade::MailReport "on-change";

MailReport 支持 always、on-change 和 only-on-error。only-on-error 听起来是更规范的选择,但在无人登录的服务器上通常是错误的,因为如果机器彻底停止了升级,它也不会发送任何错误报告。此时,正常的服务器和故障的服务器都会陷入静默。on-change 会在每次安装完成后发送邮件,因此邮件本身也可作为定时任务仍在运行的证明。

只有在机器能够发送邮件时,邮件才会发出。unattended-upgrades 将消息提交给本地邮件系统,因此你需要一个 MTA(邮件传输代理),例如 postfix,或者兼容 sendmail 的中继客户端,例如 msmtp。请使用 command -v sendmail 和 command -v mail 进行检查。如果两者都不存在,报告将无法发出,升级虽然成功,但整个失败过程将不可见。此外请注意,新的 VPS IP 地址没有发送信誉,直接发送到公共邮箱的邮件常被判定为垃圾邮件。相比自建邮件服务器,通过你现有的邮件服务商进行中继更为可靠。

如果你不想配置邮件,可以通过现有的监控系统监控日志的时间戳:

stat -c '%y %n' /var/log/unattended-upgrades/unattended-upgrades.log

如果时间戳在一周内没有更新,说明定时任务已停止,无论配置如何显示。此项检查应纳入 你的 Linux 服务器日常维护检查 中。

系统应自动重启吗?

Unattended-Upgrade::Automatic-Reboot "false";
Unattended-Upgrade::Automatic-Reboot-WithUsers "true";
Unattended-Upgrade::Automatic-Reboot-Time "02:00";

将 Automatic-Reboot 设置为 true 后,unattended-upgrades 无需确认即可重启系统,但只有在运行结束后文件 /var/run/reboot-required 存在时才会重启。在 Debian 12 和 13 中,unattended-upgrades 软件包会通过内核钩子 /etc/kernel/postinst.d/unattended-upgrades 自行创建该标记文件;安装内核软件包时会运行此钩子。升级库不会触发它。不要假设该文件一定存在。下次升级内核后检查:

ls -l /var/run/reboot-required /var/run/reboot-required.pkgs
uname -r
dpkg -l 'linux-image-*' | grep '^ii'

如果该文件在你的机器上始终不出现,Automatic-Reboot "true" 就永远不会触发。你可能连续几个月都以为系统会因内核更新而重启,但实际上仍在运行旧内核。对最新安装的 linux-image 软件包运行 uname -r,即可确认这一点。

Automatic-Reboot-Time 会在指定时间安排重启,而不是立即重启。将 Automatic-Reboot-WithUsers 设置为 false 后,只要仍有用户登录,系统就不会重启;对于始终有会话处于登录状态的服务器,这意味着系统完全不会自动重启。

需要决定的是系统重启后如何恢复运行,而不是是否重启本身。手动启动的服务不会自动恢复。启动时需要输入密码的加密卷不会挂载。相互依赖的两台机器可能以错误的顺序恢复。对于可接受在 02:00 中断一分钟的无状态 Web 服务器,可以启用自动重启。对于需要人工处理的系统,应关闭自动重启,改为监控标记文件,在合适的时间由人工重启。两者之间还有 needrestart:它会重启仍在使用已升级库的服务,因此可以处理除内核以外的更新。其默认模式会在重启服务前询问确认,因此在无人值守运行中依赖它之前,请先阅读 /etc/needrestart/needrestart.conf。

这在 testing 和 unstable 版本上效果一样吗?

上述所有内容均针对 Debian stable 版本,该版本通过独立的归档库提供安全修复,并带有特定的标签。正是这种结构使得“仅安全更新”这一设置能够生效。其他套件的构建方式不同,因此直接从 stable 服务器复制的配置可能无法达到预期效果,且在滚动更新的套件上开启自动升级意味着会自动进行大版本变更。这属于完全不同的操作范畴。如果您正在权衡这一选择,在服务器上运行 Debian stable、testing 或 unstable 一文涵盖了各版本的承诺。在 Red Hat 生态中,相同任务使用不同的工具和术语,Rocky Linux 和 AlmaLinux 上的 dnf-automatic 通过其自身的计时器和配置文件实现了该功能。

自动升级缩短了修复发布到修复安装之间的时间间隔。它们无法告知您当前仍存在哪些风险,因此请务必配合 服务器已知 CVE 检查 使用。CVE 代表通用漏洞披露(Common Vulnerabilities and Exposures),是追踪修复方案的公共标识符。

五分钟检查

  1. apt-config dump APT::Periodic 输出两个非零值的密钥。
  2. systemctl list-timers 'apt-daily*' 输出两个计时器的 NEXT 时间。
  3. sudo unattended-upgrade --dry-run --debug 输出包含您代号对应安全归档的允许来源。
  4. sudo systemctl start apt-daily-upgrade.service 执行完毕,/var/log/unattended-upgrades/unattended-upgrades.log 上的时间戳发生更新。
  5. 一周后,同一日志会列出已安装的软件包。

通过前四项检查说明机器配置正确。通过第五项检查说明系统运行正常。

FAQ

为什么 Debian 会安装 unattended-upgrades,却将其关闭?

该软件包会提出一个 debconf 问题:unattended-upgrades/enable_auto_updates,并根据答案写入 /etc/apt/apt.conf.d/20auto-upgrades。Debian 安装程序会为该问题保存 false,因此当该软件包作为任务的一部分或依赖项被安装时,会被配置为不执行任何操作。运行 sudo debconf-show unattended-upgrades 查看保存的答案,然后运行 sudo dpkg-reconfigure --priority=low unattended-upgrades 修改它。该问题的优先级较低,因此普通安装不会显示;但 dpkg-reconfigure 默认会显示低优先级问题,所以即使不加该选项,它也会在这里出现。

如何在不等待计时器的情况下测试 unattended-upgrades?

运行 sudo unattended-upgrade --dry-run --debug。它会打印将接受的软件源,并为每个可升级软件包打印一行 Checking:,其中附带该软件包的软件源记录;同时打印它将安装的软件包列表,但不会实际安装任何内容。要测试实际执行路径,请运行 sudo systemctl start apt-daily-upgrade.service,然后结合 /var/log/unattended-upgrades/unattended-upgrades.log 阅读 journalctl -u apt-daily-upgrade.service --since -1h。

unattended-upgrades 是否也会安装常规更新,而不仅是安全修复?

只有匹配规则允许时才会。源软件包的软件源匹配 Unattended-Upgrade::Origins-Pattern 中的条目或较旧的 Unattended-Upgrade::Allowed-Origins 时,才会安装软件包升级。安全存档、stable-updates 套件以及您自行添加的任何软件仓库,都是独立条目。在您自己的计算机上打印 apt-config dump Unattended-Upgrade::Origins-Pattern 和 apt-config dump Unattended-Upgrade::Allowed-Origins,然后与 grep -H -E '^(Origin|Label|Suite|Codename):' /var/lib/apt/lists/*Release 对比。它也不会将系统升级到其他 Debian 发行版。

为什么升级不会在计时器设定的时间运行?

apt-daily-upgrade.timer 会在 OnCalendar 的基础上设置 RandomizedDelaySec,因此 systemd 会在该时间窗口内随机选择时刻运行,而不是在日历时间点触发。这样可以将负载分散到所有指向同一组镜像的 Debian 计算机上。systemctl list-timers 'apt-daily*' 会显示它实际选择的时刻。要调整该时间窗口,请运行 sudo systemctl edit apt-daily-upgrade.timer,并为其提供一行空的 OnCalendar=,后面再接您自己的值。

我是否应该为安全更新启用自动重启?

只有在非计划重启不会造成问题的情况下才应启用。每次运行后,只要存在 /var/run/reboot-required,Unattended-Upgrade::Automatic-Reboot "true" 就会在不确认的情况下重启系统。在 Debian 12 和 13 中,该标记文件由 unattended-upgrades 软件包提供的内核钩子创建,因此内核升级后会出现,库升级后不会出现。在信任此设置前,请先在内核升级后确认该文件会在您的计算机上出现。如果计算机启动时需要输入口令,或运行着由人员手动启动的服务,请将该设置保持为 false,并在出现该标记文件时发出告警,以便由人员决定重启时间。