この記事は、VPS や EC2 の Ubuntu / Debian で Web サイトを動かしている Web 担当者・制作会社向けです。
「自動更新は入っているはず」のサーバで、実際には更新が止まっていた・PHP だけ古いまま・再起動されず古いカーネルで動き続けていた、という状態はよくあります。
unattended-upgrades の設定を 7 つ点検して、セキュリティ更新が本当に当たり続けているかをコマンドで確認できるところまでをまとめます(前提は Ubuntu 22.04/24.04・Debian 12)。
まず全体像: 誰がいつ更新しているのか
Ubuntu / Debian の自動更新は、次の 3 つの組み合わせで動いています。
| 部品 | 役割 |
|---|---|
apt-daily.timer |
パッケージ一覧の更新・ダウンロード(既定 6:00 / 18:00 から最大 12 時間のランダム遅延) |
apt-daily-upgrade.timer |
実際のアップグレード(既定 6:00 から最大 60 分のランダム遅延) |
/etc/apt/apt.conf.d/20auto-upgrades |
上の 2 つを「何日おきに実行するか」("1" = 毎日 / "0" = 無効) |
/etc/apt/apt.conf.d/50unattended-upgrades |
どのリポジトリの更新を入れるか・メール・再起動などの挙動 |
ポイントは、タイマーが動いていても「入れる対象」に含まれていない更新は永久に入らないことです。以下の 7 設定で、動いているか・何が対象か・止まったら気づけるかを順に潰します。
設定1: 導入済みか・有効かを確認する
Ubuntu Server には既定で入っていますが、Debian は環境(インストール方法・クラウドイメージ)によって未導入のことがあります。
# インストール状況(ii で始まれば導入済み)
dpkg -l unattended-upgrades
# 未導入なら導入して有効化(Debian は dpkg-reconfigure で 20auto-upgrades が作られる)
sudo apt install unattended-upgrades
sudo dpkg-reconfigure -plow unattended-upgrades
# 最終的に効いている値を確認(ファイルの中身ではなく apt が解釈した結果を見る)
apt-config dump | grep -E '^APT::Periodic::(Update-Package-Lists|Unattended-Upgrade) '
次の 2 行が "1" になっていれば毎日動く設定です。
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";
タイマー自体が止められていないかも見ておきます。
systemctl list-timers 'apt-daily*'
NEXT が空欄なら無効化されています。sudo systemctl enable --now apt-daily.timer apt-daily-upgrade.timer で戻します。
設定2: 設定変更は「52-」の別ファイルに書く
50unattended-upgrades を直接書き換えると、パッケージ更新時に設定ファイルの差分確認が発生し、その更新自体が自動では進まなくなる原因になります。変更したい項目だけを別ファイルに書くのが安全です。apt の設定ファイルは名前順に読まれ、後から読んだ値が単一値を上書きします。
sudo tee /etc/apt/apt.conf.d/52unattended-upgrades-local >/dev/null <<'EOF'
// 50unattended-upgrades の既定を上書きする自社設定(変更点だけを書く)
// 変更者・日付・理由をここにコメントで残す
EOF
以降の設定例は、すべてこの 52unattended-upgrades-local に追記していきます。
設定3: 「入れる対象」に何が入っているかを確認する
既定の対象は OS 公式の release / security と、Ubuntu Pro の ESM(有効時のみ)です。Ubuntu の既定値は次のとおりです。
Unattended-Upgrade::Allowed-Origins {
"${distro_id}:${distro_codename}";
"${distro_id}:${distro_codename}-security";
"${distro_id}ESMApps:${distro_codename}-apps-security";
"${distro_id}ESM:${distro_codename}-infra-security";
};
Debian 12 は Origins-Pattern で label=Debian-Security などが指定されています。いずれにしても、OS 公式以外のリポジトリから入れたパッケージは対象外です。Web サーバでよくあるのが次のケースです。
- PHP を PPA(
ppa:ondrej/phpなど)や外部リポジトリから入れている - nginx / MySQL / Node.js を公式サイトのリポジトリから入れている
自分のサーバでどこから入っているかを確認します。
# 主要パッケージの「どのリポジトリ由来か」を確認(php8.3-fpm 等は環境に合わせて変更)
for p in nginx apache2 php8.3-fpm mysql-server mariadb-server openssl; do
dpkg -s "$p" >/dev/null 2>&1 || continue
echo "== $p"
apt-cache policy "$p" | sed -n '1,12p'
done
出力の release o=...,a=...,n=...,l=... の行がリポジトリの識別子です。OS 公式以外(o= が Ubuntu / Debian 以外)のものを自動更新に含めたい場合は、表示された値をそのまま使って対象に追加します。
// 52unattended-upgrades-local に追記
// apt-cache policy で確認した o= と n= の値を書く(例は PPA の書式)
Unattended-Upgrade::Origins-Pattern {
"origin=LP-PPA-ondrej-php,codename=${distro_codename}";
};
外部リポジトリは配布元の更新方針に依存します。追加するかどうかは、更新が自動で入ったときの影響範囲(PHP のマイナー更新でサイトが止まらないか)と天秤にかけてください。追加しない場合でも、設定7の監視で「手動で上げるべき更新がある」ことに気づける状態にしておきます。
設定4: 更新を止めているものを棚卸しする
一時的に止めたつもりの除外が、何年も残っていることがあります。hold と Package-Blacklist に入っているパッケージには、セキュリティ更新も入りません。
# apt-mark hold で固定されているパッケージ
apt-mark showhold
# unattended-upgrades の除外リスト(実際に効いている値)
apt-config dump | grep -A20 '^Unattended-Upgrade::Package-Blacklist'
何か出てきたら、「なぜ止めたのか」「いつ解除するのか」が説明できるかを確認し、理由が分からないものは解除を検討します。除外を新しく増やすのではなく、除外を減らす方向で整理するのが原則です。
設定5: 再起動の方針を決める
カーネルや glibc・OpenSSL などの更新は、ファイルが入れ替わっても、再起動するまで古いものが動き続けます。再起動が必要になると Ubuntu では /var/run/reboot-required が作られ、原因パッケージは /var/run/reboot-required.pkgs に記録されます。
# 再起動待ちか / 何のためか
ls -l /var/run/reboot-required 2>/dev/null && cat /var/run/reboot-required.pkgs
# 動いているカーネルと、インストール済みの最新カーネルの比較
uname -r
ls -1 /boot/vmlinuz-* | sort -V | tail -n 1
方針は次の 2 つのどちらかに決めます。
A. 深夜に自動再起動する(単独サーバで、数分の停止が許容できる場合)
// 52unattended-upgrades-local に追記
// 再起動が必要なときだけ、指定時刻に確認なしで再起動する
Unattended-Upgrade::Automatic-Reboot "true";
Unattended-Upgrade::Automatic-Reboot-Time "04:30";
アップグレード自体は既定で 6:00〜7:00 に走るため、再起動時刻より前に終わるようタイマーを前倒しします。
sudo systemctl edit apt-daily-upgrade.timer
[Timer]
OnCalendar=
OnCalendar=03:00
RandomizedDelaySec=30m
OnCalendar= の空行は既定値の打ち消しで、必須です。反映後に systemctl list-timers 'apt-daily*' で次回時刻を確認します。
B. 自動再起動はせず、再起動待ちを通知して計画再起動する(冗長構成・停止に調整が要る場合)
この場合は設定7の監視スクリプトで「再起動待ち」を毎日通知し、放置しない運用にします。どちらにするにせよ、「再起動待ちのまま数か月」という状態だけは避けてください。
サービス単位の再起動が必要なプロセス(更新済みライブラリを古いまま掴んでいる php-fpm など)は、needrestart を導入していれば一覧で確認できます。
# 一覧表示のみ(再起動はしない)
sudo needrestart -r l
設定6: 実行結果を確認できるようにする
まず、手動で「今なら何が入るか」を確認します。実際の更新は行いません。
sudo unattended-upgrade --dry-run -d 2>&1 | grep -E 'Allowed origins|Packages that will be upgraded|No packages found'
Allowed origins are: の行で、設定3で見た対象が反映されているかを確認できます。過去の実行結果はログに残っています。
# unattended-upgrades の実行ログ(実行のたびに追記される)
sudo tail -n 50 /var/log/unattended-upgrades/unattended-upgrades.log
# 実際に入ったパッケージの記録
sudo zgrep -h -A3 'Start-Date' /var/log/apt/history.log* | tail -n 40
# 失敗・手動対応が必要だったものがないか
sudo zgrep -hiE 'error|warning|conffile' /var/log/unattended-upgrades/unattended-upgrades.log* | tail -n 20
メール通知を使う場合は、mailx を提供するパッケージと、外部へ送信できるメール設定が別途必要です。
// 52unattended-upgrades-local に追記(送信できる環境のみ)
Unattended-Upgrade::Mail "infra@example.com";
Unattended-Upgrade::MailReport "only-on-error";
MailReport は always / only-on-error / on-change の 3 つです。サーバ台数が多いと on-change でも通知が埋もれるため、エラー時だけにして、次の監視で「止まっていないか」を別に見るのがおすすめです。
設定7: 「止まった」ことに気づく監視を入れる
自動更新のいちばん怖い失敗は、エラーではなく静かに止まることです。ログの更新日時・エラー・再起動待ち・hold を毎日見て、問題があるときだけ出力するスクリプトを置きます。
sudo tee /usr/local/sbin/check-auto-updates.sh >/dev/null <<'EOF'
#!/bin/bash
# 自動更新の健全性チェック: 問題があるときだけ標準出力に書く
set -u
LOG=/var/log/unattended-upgrades/unattended-upgrades.log
msgs=()
# 1) 48 時間以上 unattended-upgrades が実行されていない
if [ ! -f "$LOG" ] || [ -n "$(find "$LOG" -mmin +2880)" ]; then
msgs+=("unattended-upgrades が 48 時間以上実行されていません")
fi
# 2) 直近の実行でエラー・手動対応が必要な記録がある
if [ -f "$LOG" ] && tail -n 300 "$LOG" | grep -qiE 'error|conffile'; then
msgs+=("unattended-upgrades のログにエラー/手動対応の記録があります")
fi
# 3) 再起動待ち
if [ -f /var/run/reboot-required ]; then
pkgs=$(tr '\n' ' ' < /var/run/reboot-required.pkgs 2>/dev/null)
msgs+=("再起動待ちです: ${pkgs}")
fi
# 4) hold で更新が止まっているパッケージ
held=$(apt-mark showhold | tr '\n' ' ')
if [ -n "$held" ]; then
msgs+=("hold 中のパッケージ: ${held}")
fi
if [ ${#msgs[@]} -gt 0 ]; then
echo "[$(hostname)] 自動更新の点検結果"
printf -- '- %s\n' "${msgs[@]}"
fi
EOF
sudo chmod 755 /usr/local/sbin/check-auto-updates.sh
# まず手動で実行して出力を確認
sudo /usr/local/sbin/check-auto-updates.sh
毎朝、アップグレードが終わった後の時刻に実行します。出力があるときだけ cron がメールを送ります(チャットに送る場合は、出力をお使いの通知コマンドへパイプしてください)。
sudo tee /etc/cron.d/check-auto-updates >/dev/null <<'EOF'
MAILTO=infra@example.com
30 9 * * * root /usr/local/sbin/check-auto-updates.sh
EOF
反映の確認
設定を変えたら、次の 3 点を確認して終わりにします。
# 1) 設定ファイルの値が意図どおりか(52- の値が効いているか)
apt-config dump | grep -E '^Unattended-Upgrade::(Automatic-Reboot|Mail|MailReport)'
# 2) 対象リポジトリが意図どおりか
sudo unattended-upgrade --dry-run -d 2>&1 | grep 'Allowed origins'
# 3) タイマーの次回実行時刻
systemctl list-timers 'apt-daily*'
まとめ
| # | 設定 | 防げること |
|---|---|---|
| 1 | 導入・APT::Periodic ・タイマーの確認 |
「入っているつもり」で動いていない |
| 2 | 変更は 52unattended-upgrades-local に |
設定ファイル差分で更新自体が止まる |
| 3 | 対象リポジトリの確認(PPA・外部リポジトリ) | PHP や nginx だけ古いまま |
| 4 | hold / Package-Blacklist の棚卸し | 一時的な除外が恒久化する |
| 5 | 再起動方針(自動再起動 or 通知して計画再起動) | 更新済みなのに古いカーネルで動き続ける |
| 6 | dry-run とログで結果確認 | 何が入ったか分からない |
| 7 | 停止・エラー・再起動待ちの毎日監視 | 自動更新が静かに止まる |
自動更新は「有効にしたら終わり」ではなく、対象・再起動・停止検知の 3 点がそろって初めて効く仕組みです。まずは設定1と設定5のコマンドを今のサーバで流して、再起動待ちのまま放置されていないかを確認してみてください。
参考(公式ドキュメント):
- Ubuntu Server documentation「Automatic updates」 https://ubuntu.com/server/docs/how-to/software/automatic-updates/
- Debian Wiki「UnattendedUpgrades」 https://wiki.debian.org/UnattendedUpgrades
- unattended-upgrades(GitHub) https://github.com/mvo5/unattended-upgrades
関連記事
本記事のような設定の抜けは、Web サイトを 9 つの守りでまるごと守るサイトドックでまとめて対策できます → https://sitedock.jp