大多数成功的服务器入侵,靠的不是精巧的 exploit,而是一个补丁在数月前就已发布的漏洞。原因通常不是懒:在一台跑着业务的服务器上手动更新是件吓人的事,因为 apt upgrade 有时会重启谁也没要求它重启的东西。自动更新能了结这个问题,但它名声不好——而这份名声恰好与它被草率配置的程度相称。

我们来看一套配置:安全补丁自己到来,而 PHP 的版本不会一夜之间变掉。

安装

sudo apt install unattended-upgrades apt-listchanges
sudo dpkg-reconfigure -plow unattended-upgrades

这个对话框会生成 /etc/apt/apt.conf.d/20auto-upgrades。请看看结果是什么:

APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";

这里的 1 表示「每天」。这一切由 systemd 的计时器驱动,而不是经典的 cron

systemctl list-timers | grep apt

最要紧的一点:只要安全更新

主文件是 /etc/apt/apt.conf.d/50unattended-upgrades。里面有一份 Allowed-Origins 清单(在 Debian 上叫 Origins-Pattern),而默认情况下那里只有安全仓库是启用的。就让它保持这样。

-updates 那行的注释去掉——「让所有东西都更新吧」——这种诱惑很强。别这么做:updates 仓库带来的是新版本而不只是修复,而夜里的意外正是从那里来的。安全和新鲜是两件不同的事,而后一件最好在你坐在键盘前的时候做。

如果某个软件包不该被自动更新,那么它有一份黑名单:

Unattended-Upgrade::Package-Blacklist {
    "mariadb-server";
    "php8.3-fpm";
};

请有意识地使用它:这里的每一行都意味着「这个我自己更新」——而这是一个必须兑现的承诺。

重启

关键的一行:

Unattended-Upgrade::Automatic-Reboot "false";

对一台带站点的服务器来说,把它留在 false 是对的:凌晨三点毫无预告地重启,比一个推迟一天的补丁更糟。但这个决定有一个强制性的后半段,而正是这后半段被人忘掉。

当内核、libcopenssl 被更新时,系统会创建文件 /var/run/reboot-required。在重启之前,修好的版本躺在磁盘上,而旧的仍在内存里运行——也就是说,尽管软件包算是更新过了,漏洞哪儿也没去。请明确地检查它:

ls -l /var/run/reboot-required
cat /var/run/reboot-required.pkgs

第二个文件写明了究竟是什么让重启成为必要。规则很简单:看到这个标记,就在接下来一两天内安排一个窗口,而不是「哪天再说」。

库是单独的一类。openssl 被更新了,而 nginx 和 PHP-FPM 仍带着内存里的旧版本继续运行,重启标记压根不会出现。谁需要重启由 needrestart 告诉你:

sudo apt install needrestart
sudo needrestart -b

只要把它设成「只报告、不发问」模式——否则它会在一次非交互式安装的中途开始提问,把安装挂在那儿。为此请往 /etc/needrestart/conf.d/ 里放一个含有 $nrconf{restart} = 'l'; 这一行的文件。

确认它确实在工作

不做实际安装的试运行:

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

输出会显示哪些包符合规则,哪些被拒绝以及原因。实际发生了什么则在日志里:

sudo tail -50 /var/log/unattended-upgrades/unattended-upgrades.log

此刻有多少更新在等着:

apt list --upgradable 2>/dev/null | grep -i security

最后这条命令是其中最有用的。配置好的自动更新有一种悄无声息坏掉的习惯:/boot 满了、仓库换了密钥、某次手动改动之后计时器被关掉了。表面上一切正常,而补丁已经几个月没装上了。

/boot 上的空间

经典情形:旧内核不被删除,/boot 分区被填满,新内核装不上,于是更新整体停摆。每隔几个月值得看一眼:

df -h /boot
sudo apt autoremove --purge

或者在同一个 50unattended-upgrades 里打开自动清理:Remove-Unused-Kernel-Packages "true"Remove-Unused-Dependencies "true"

留给人的部分

自动更新去掉的是例行操作,而不是盯着看这件事。有两个问题你在任何时刻都该答得上来:有多少安全更新在等着,以及重启标记是否已经立起。这两个答案在页面上就是一行字,而全部意义在于这一行能自己映入眼帘,而不是等你每季度主动去查一次。