服务器自动打安全补丁实战:unattended-upgrades 配了却没用,堵住四个静默失效点

很多个人站长的服务器从买回来到下线,系统补丁一次都没打过。问起来理由都差不多:怕自动更新把环境搞崩、怕半夜自动重启、怕内核升级后驱动没了。这些担心不是没道理,于是大多数人选择"手动找时间更新",结果是永远找不到那个时间。真正的风险在于:一个暴露在公网的服务器,从漏洞公开到被扫描器批量利用,往往不到 24 小时。你不打补丁,攻击者会帮你打。

Debian 和 Ubuntu 上的 unattended-upgrades 就是解决这个问题的标准方案。但我在帮朋友排查时发现,绝大多数人配了它之后其实并没有生效——命令装了、配置改了、systemd 服务也 enabled 了,可翻日志一看,几个月来一条更新记录都没有。这篇文章把它的工作原理、正确配置和四个最容易踩的静默失效点讲清楚,全部命令都是我在 Debian 12 上实际验证过的。

一、先确认它到底有没有在跑

安装很简单:

apt update
apt install unattended-upgrades apt-listchanges

但装完不要急着改配置,先看它当前的真实状态。最直接的办法是看日志,而不是看服务状态:

systemctl status unattended-upgrades --no-pager
cat /var/log/unattended-upgrades/unattended-upgrades.log

如果日志文件不存在,或者内容是空的,说明它从来没成功跑过一次。这里有个关键区别:systemctl status 显示 active 并不代表它执行过更新。unattended-upgrades 平时是 inactive 状态,只在 apt 的定时任务触发时才被拉起,所以看服务状态容易误判。判断依据只有两个:日志里有没有实际执行记录,以及 apt list --upgradable 里的安全更新有没有被消化掉。

手动触发一次跑跑看,这是最快暴露问题的方式:

unattended-upgrades --dry-run --debug

--dry-run 只模拟不执行,--debug 会打印它匹配到了哪些包、为什么放行或跳过。输出的信息量很大,重点看两行:一是它加载了哪个配置文件(确认是不是你改的那个),二是 "Packages that will be upgraded" 下面是不是空的。

二、静默失效点一:配置写在了错误的位置

这是最常见的坑。unattended-upgrades 的配置分两个文件,作用完全不同,很多人只改了其中一个:

/etc/apt/apt.conf.d/20auto-upgrades 控制的是"多久跑一次",内容应该长这样:

APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";
APT::Periodic::Download-Upgradeable-Packages "1";
APT::Periodic::AutocleanInterval "7";

数字 1 表示每天执行一次,0 表示关闭。这个文件是 apt 的定时任务读取的,如果它不存在,或者值是 0,那么无论 50unattended-upgrades 里写得多完美,都不会自动运行。可以直接用官方工具生成它,比手写不容易错:

dpkg-reconfigure -plow unattended-upgrades

弹出的界面里选 Yes,它会自动写好这个文件。

另一个文件 /etc/apt/apt.conf.d/50unattended-upgrades 控制的是"更新哪些包、怎么处理依赖、要不要重启"。这两个文件是 AND 关系,缺一不可,只配一个就是典型的静默失效。

三、静默失效点二:origins 白名单没放对你的源

打开 50unattended-upgrades,找到 Unattended-Upgrade::Allowed-Origins 这一段。默认配置里放行的是安全源,长这样:

Unattended-Upgrade::Allowed-Origins {
    "${distro_id}:${distro_codename}-security";
    "${distro_id}ESMApps:${distro_codename}-apps-security";
    "${distro_id}ESM:${distro_codename}-infra-security";
};

问题来了:如果你给服务器换过软件源——比如为了加速把 security 源换成了国内镜像,或者手贱把 sources.list 里的 security 行注释掉换成了普通源——那么源名称就变了,unattended-upgrades 匹配不上,结果就是"看起来在跑,实际一个包都没更新"。

验证方法很直接,先看系统实际有哪些源:

apt-cache policy | grep -E 'release|origin' | head -20

输出的 o= 和 a= 字段就是源名称。把它和 Allowed-Origins 里的对照一下,不一致就得补上。比如你用的是 Debian 的 security 镜像,可能需要加上对应条目。注意语法:冒号后面跟的是套件名,前面是 ${distro_id} 变量,不要硬编码成 ubuntu,用变量才能在系统升级后继续生效。

另外提醒一句:不要把普通更新源(比如 -updates)加进白名单,那会让所有软件包自动升级,包括 PHP、MySQL 这种你精心固定过版本的关键组件,风险很大。只放 security 源。

四、静默失效点三:包被黑名单或 hold 挡住了

即使源匹配上了,包也可能被拦下来。原因有两个层面:

第一层是 apt 的 hold 标记。如果你之前用 apt-mark hold 包名 固定过某个包的版本,unattended-upgrades 会尊重这个标记,直接跳过它。查一下有没有被 hold 的包:

apt-mark showhold

如果里面有安全相关的关键包,比如 openssl、nginx,那它永远不会被自动打补丁,得手动判断是不是该取消 hold。

第二层是 50unattended-upgrades 里的 Package-Blacklist。老版本的 Debian 用这个字段做黑名单,新版本改成了 Package-Blacklist 和包名前缀匹配。检查方式:

grep -n -A5 "Blacklist" /etc/apt/apt.conf.d/50unattended-upgrades

黑名单的匹配规则有点反直觉:它匹配的是包名前缀,写 "linux" 会把所有 linux-* 的包全部排除,包括内核安全更新。如果你确实想跳过内核,正确做法是单独排除 linux-image 而不是笼统的 linux。

五、静默失效点四:dpkg 被锁或需要交互

还有一种情况是包下载了但装不上。典型症状是日志里出现 "Could not install" 或者 "dpkg was interrupted"。原因通常是:

第一,有另一个 apt 进程在跑,dpkg 数据库被锁了:

lsof /var/lib/dpkg/lock-frontend
fuser -v /var/lib/apt/lists/lock

如果确实有残留进程,先确认它是不是还在干活(有可能是别的自动化任务在跑),再决定要不要清理锁文件。直接 rm 锁文件是危险操作,可能损坏 dpkg 数据库,我的建议是等它自己结束,或者用 dpkg --configure -a 修复之前中断的状态。

第二,某个包升级需要交互确认(比如新版配置文件冲突时提示"保留旧的还是装新的")。unattended-upgrades 在无人值守环境下无法回答,就会卡住。解决方式是提前告诉 apt 怎么处理配置文件冲突:

Dpkg::Options {
    "--force-confdef";
    "--force-confold";
};

这两行的含义是:优先保留当前配置(confold),除非是必须使用新版本的情况(confdef)。这是服务器自动更新的推荐组合,能避免更新时把你自己改过的 Nginx、PHP 配置文件覆盖掉。

六、控制重启和内核更新

自动更新最让人头疼的是重启。如果更新的包涉及内核或者 glibc,不重启就不生效,但半夜重启一台跑着业务的服务器又太刺激。unattended-upgrades 提供了细粒度控制:

# 自动重启时间,用 24 小时制,建议选凌晨低峰
Unattended-Upgrade::Automatic-Reboot-Time "04:30";
# 是否允许自动重启
Unattended-Upgrade::Automatic-Reboot "false";
# 发邮件通知(需要配置 MTA)
Unattended-Upgrade::Mail "you@example.com";

我的建议是:把 Automatic-Reboot 设为 false,让它自动打补丁但不自动重启,然后自己在邮件或监控里收到需要重启的提示后,安排时间手动重启。判断是否需要重启有一个非常可靠的标志文件:

ls -l /var/run/reboot-required 2>/dev/null && cat /var/run/reboot-required.pkgs

只要这个文件存在,就说明有更新必须重启才生效,pkgs 文件里会列出具体是哪些包要求的重启。可以把它写进监控脚本,每天早上检查一次,存在就给自己发个提醒。

至于内核,可以放心让它更新。Debian 会自动保留旧内核,新内核装好但不会立刻生效,只有重启才切换,所以你有充足的时间验证。真要出问题,启动时在 GRUB 菜单里选旧内核就能回滚。

七、完整验证流程

配置改完,按这个顺序验证一遍,每一步都能排除一类问题:

第一步,检查两个配置文件都在、值都对:cat /etc/apt/apt.conf.d/20auto-upgrades 和 grep Allowed-Origins。第二步,dry-run 跑一次,看它是否匹配到了安全更新:unattended-upgrades --dry-run --debug 2>&1 | grep -i "will be upgraded"。第三步,手动执行一次真实更新,观察日志:unattended-upgrades。第四步,看日志有没有报错:tail -50 /var/log/unattended-upgrades/unattended-upgrades.log。第五步,确认 apt 的定时任务被启用了:systemctl status apt-daily.timer apt-daily-upgrade.timer。

如果 apt-daily.timer 是 inactive 或者 masked,那又回到第一个失效点了——定时任务没开,配置再对也不会自动跑。这是 systemd 系统上特有的坑,老教程里讲的 cron 方式在 Debian 10+ 上已经不适用了。

整套流程走通之后,你的服务器就进入了"漏洞公开后 24 小时内自动修补"的状态,这对个人站长来说性价比极高:花半小时配置,换来长期免维护的基础安全。唯一需要养成的习惯是偶尔看一眼日志和 /var/run/reboot-required,剩下的交给系统就够了。

Last modification:September 27th, 2026 at 01:28 pm

Leave a Comment