サーバーをセキュアに保つには、まず「何が動いていて、何がインストールされているか」を知る必要があります。把握していないものは、更新も削除もできないからです。

この記事では、Ubuntu の状態を確認する手順と、確認したあとにパッチを当てる・バージョンを上げる手順をまとめます。1〜5 章のコマンドはすべて読み取り専用です。システムを変更するのは 6 章と 7 章です。7 章では、2026 年 10 月に公開された PHP の脆弱性「PHP2Shell」を例に、特定のソフトウェアだけを緊急で更新する流れを追います。

各コードブロックの上に Command(入力するもの)と Output(表示される結果)の見出しを付けています。 出力例は Ubuntu 26.04 LTS(EC2)での表示形式に合わせていますが、ホスト名・ユーザー名・パッケージの一覧やバージョンなどは説明用の例です。

確認する順番#

次の順に見ていくと整理しやすくなります。

  1. OS とカーネル
  2. インストール済みパッケージ
  3. apt の管理外のもの
  4. パッケージのバージョンと修正状況
  5. 更新状況・サービス・ポート・ユーザー
  6. パッチ適用とバージョンアップ
  7. 事例: PHP2Shell に対応する

1. OS とカーネルの情報#

OS のバージョンを確認します。

Command

cat /etc/os-release

Output(抜粋)

PRETTY_NAME="Ubuntu 26.04.1 LTS"
NAME="Ubuntu"
VERSION_ID="26.04"
VERSION="26.04.1 LTS (Resolute Raccoon)"
VERSION_CODENAME=resolute
ID=ubuntu

VERSION_CODENAME(この例では resolute)は、あとで出てくる apt のリポジトリ名に使われます。

カーネルのバージョンを確認します。

Command

uname -a

Output

Linux web01 7.0.0-1014-aws #14-Ubuntu SMP PREEMPT Fri Sep 18 00:50:36 UTC 2026 aarch64 GNU/Linux

7.0.0-1014-aws がカーネルのバージョンです。-aws は AWS 向けに調整されたカーネルであることを示します。

ホスト名や仮想化の種類をまとめて確認します。

Command

hostnamectl

Output(抜粋)

 Static hostname: web01
       Icon name: computer-vm
         Chassis: vm
  Virtualization: amazon
Operating System: Ubuntu 26.04.1 LTS
          Kernel: Linux 7.0.0-1014-aws
    Architecture: arm64
 Hardware Vendor: Amazon EC2
  Hardware Model: t4g.small

2. インストール済みパッケージ#

apt list の使い方#

apt list は、パッケージの一覧を表示するコマンドです。基本の形は次のとおりです。

apt list [オプション] [パッケージ名のパターン]

よく使うオプションは次のとおりです。

オプション内容
(なし)リポジトリにあるパッケージをすべて表示
--installedインストール済みのパッケージだけを表示
--upgradableアップグレードできるパッケージだけを表示
--manual-installed名前を指定して入れたパッケージだけを表示
--all-versions / -aリポジトリにあるすべてのバージョンを表示

パッケージ名には * などのパターンが使えます。シェルに展開されないように、クォートで囲みます。

Command

apt list 'openssh*'

Output(抜粋)

Listing...
openssh-client/resolute-updates,resolute-security,now 1:10.2p1-2ubuntu3.8 arm64 [installed,automatic]
openssh-known-hosts/resolute 0.6.3-1build1 all
openssh-server/resolute-updates,resolute-security,now 1:10.2p1-2ubuntu3.8 arm64 [installed]
openssh-sftp-server/resolute-updates,resolute-security,now 1:10.2p1-2ubuntu3.8 arm64 [installed]
openssh-tests/resolute-updates,resolute-security 1:10.2p1-2ubuntu3.8 arm64

1 行は次のように読みます。

openssh-server / resolute-updates,resolute-security,now  1:10.2p1-2ubuntu3.8  arm64  [installed]
  パッケージ名  /  取得元のリポジトリ                      バージョン            CPU    状態
  • 取得元: resolute-security はセキュリティ修正、resolute-updates は通常の修正です。now は、このマシンに入っていることを表します。
  • 状態: 角かっこの中が、このマシンでの状態です。何もなければ未インストールです。
状態の表示意味
[installed]名前を指定してインストールしたもの
[installed,automatic]依存関係として自動で入ったもの
[installed,local]現在のリポジトリにないもの(.deb を直接入れた、リポジトリを外した など)
[upgradable from: X]インストール済みで、新しいバージョンがある(X が今のバージョン)
[residual-config]削除済みだが設定ファイルが残っている

[installed,local] は、更新が届かなくなっている可能性があるので要注意です。

インストール済みのパッケージ数を数えます。

Command

apt list --installed 2>/dev/null | wc -l

Output

1422

先頭の Listing... の 1 行も数えているので、実際の数は 1 つ少なくなります。2>/dev/null は、次の警告を消すためのものです。

WARNING: apt does not have a stable CLI interface. Use with caution in scripts.

この警告のとおり、apt の出力形式は将来変わる可能性があります。スクリプトで処理するときは、あとで出てくる dpkg-query を使う方が安全です。

インストール済みの中から、特定のものだけを探すこともできます。

Command

apt list --installed 'python3-*' 2>/dev/null | head -5

Output

Listing...
python3-apt/resolute,now 3.1.0ubuntu1 arm64 [installed,automatic]
python3-attr/resolute,now 25.3.0-1 all [installed,automatic]
python3-babel/resolute,now 2.17.0-1 all [installed,automatic]
python3-bcrypt/resolute,now 4.2.0-2.1build2 arm64 [installed,automatic]

同じパッケージの、リポジトリにあるバージョンをすべて表示します。

Command

apt list -a openssl

Output

Listing...
openssl/resolute-updates,resolute-security,now 3.5.5-1ubuntu3.7 arm64 [installed,automatic]
openssl/resolute 3.5.5-1ubuntu3 arm64

下の resolute はリリース時のバージョン、上の resolute-security がその後に配信された修正版です。今は修正版が入っていることが分かります。

dpkg-query と apt list --installed の違い#

どちらもインストール済みのパッケージを一覧できます。この環境では、どちらも 1421 件で同じ件数でした。違うのは、情報の出どころと、表示できる内容です。

Command

apt list --installed 2>/dev/null | grep -m1 '^openssl/'
dpkg-query -W -f='${db:Status-Abbrev} ${Package}:${Architecture} ${Version}\n' openssl

Output

openssl/resolute-updates,resolute-security,now 3.5.5-1ubuntu3.7 arm64 [installed,automatic]
ii  openssl:arm64 3.5.5-1ubuntu3.7
dpkg-query -Wapt list --installed
情報の出どころdpkg のデータベース(/var/lib/dpkg/status)だけdpkg のデータベースに加えて、apt のキャッシュ(apt update で取得したリポジトリ情報)
出力形式-f で自由に指定できる固定(名前/リポジトリ,now バージョン アーキ [状態])
出力の安定性安定していて、スクリプト向き警告が出るとおり、将来変わる可能性がある
どのリポジトリ由来か分からない分かる(resolute-security など)
手動/自動の区別分からない分かる([installed,automatic])
ソースパッケージ名、Installed-Size取れる取れない
削除済みで設定ファイルだけ残るもの(rc)表示される場合がある表示されない(ii のみ)

「インストール済みのパッケージを確認する」という目的では、どちらも同じ結果になります。この環境でも、どちらも 1421 件で一致しました。更新後にバージョンを確認するだけなら、どちらを使っても確認できます。

ただし、完全に同じではありません。apt list --installed は最初からインストール済み(ii)だけを返しますが、dpkg-query -W は状態を問わず表示します。見られる項目も、上の表のように違います。

ポイントは次の 2 つです。

  • 「手動/自動」と「リポジトリ名」は、dpkg にはない apt 側の情報です。dpkg-query だけで手動インストールかどうかを調べることはできません。その場合は apt-mark showmanual を使います。
  • dpkg-query は状態を問わず表示するので、ii(インストール済み)だけにしたいときは awk '$1 == "ii"' などで自分で絞ります。

使い分けの目安は、人が目で見るなら apt list --installed、スクリプトで処理するなら dpkg-query -W -f=... です。

サイズの大きいパッケージを見る#

容量の大きいものから並べると、そのマシンが何に使われているかがだいたい分かります。単位は KB です。

Command

dpkg-query -W -f='${Installed-Size}\t${Package}\n' | sort -rn | head -8

Output

216141	ansible
146758	linux-modules-7.0.0-1014-aws
139622	mesa-vulkan-drivers
130672	libllvm21
127960	docker.io
117624	snapd
108192	python3-botocore
96528	containerd

一般に、カーネルモジュール、コンテナ基盤、コンパイラ一式、グラフィックス関連のライブラリ、構成管理ツールなどが上位に来やすいです。

セキュリティの観点では、次のようなものに目を向けます。

  • コンテナ基盤(Docker など): docker グループに入っているユーザーは、実質 root と同じ権限を持ちます。
  • コンパイラ: 侵入された場合に、攻撃者がツールをビルドする手段になります。使わないなら削除を検討します。
  • GUI 向けのライブラリ: 上の例の mesa-vulkan-drivers のように、GUI を使わないサーバーでは不要なことが多いです。
  • snapd: snap で入れたアプリは apt list --upgradable に表示されません。snap list で別に確認します。

あるパッケージが何に必要とされているかは、次のコマンドで確認できます。

Command

apt-cache rdepends --installed openssl

Output

openssl
Reverse Depends:
  ssl-cert
  ca-certificates
  ec2-instance-connect

Reverse Depends: の下に何も表示されなければ、ほかのパッケージからは依存されていません。

3. 「手動インストール」の意味に注意#

自分で入れたパッケージは次のコマンドで一覧にできます。

Command

apt-mark showmanual

Output(抜粋)

acl
ansible
bash
chrony
cloud-init
curl

ただし、ここでの「手動」は apt が管理しているパッケージのうち、名前を指定してインストールしたもの という意味です。「apt 以外の方法で入れたもの」という意味ではありません。依存関係として自動で入ったパッケージは表示されません。OS のインストール時に入ったもの(bash など)も含まれます。

入れ方showmanual に出るか
apt install <名前> で指定した出る(依存で入ったものは出ない)
PPA など追加リポジトリから apt install した出る
.deb を dpkg -i で入れた出る
ソースから make install した出ない
pip / npm / cargo などで入れた出ない
バイナリを置いただけ出ない
snap / flatpak出ない

注意が必要なのは apt の管理外 のものです。apt list --upgradable に出てこないため、更新を自分で追いかけないと、古く脆弱なまま残ってしまいます。

apt の管理外のものを探す#

ソースからビルドしたものや、配布元のバイナリを置いたものは /usr/local や /opt に入っていることが多いです。

Command

ls /usr/local/bin /opt

Output

/opt:
amazon  containerd  terraform

/usr/local/bin:
aws  aws_completer  terraform

この例では、AWS CLI と Terraform が apt の外で入っています。これらは自分で更新する必要があります。

言語ごとのパッケージ管理ツールも確認します。

Command

pip list --user

Output

Package    Version
---------- -------
requests   2.32.3

Command

npm list -g --depth=0

Output

/usr/local/lib
└── npm@11.6.0

Command

cargo install --list

Output

ripgrep v14.1.1:
    rg

ツールが入っていなければ command not found と表示されます。それ自体は問題ありません。

あるコマンドがどのパッケージに含まれるかを調べます。

Command

dpkg -S /usr/bin/ssh

Output

openssh-client: /usr/bin/ssh

dpkg の管理外のファイルを指定すると、次のように表示されます。

Command

dpkg -S /usr/local/bin/terraform

Output

dpkg-query: no path found matching pattern /usr/local/bin/terraform

登録されているリポジトリの一覧も確認しておきます。

Command

grep -rhE '^(deb|URIs)' /etc/apt/sources.list /etc/apt/sources.list.d/ 2>/dev/null

Output

URIs: http://ap-northeast-1.ec2.archive.ubuntu.com/ubuntu/
URIs: http://security.ubuntu.com/ubuntu
deb [arch=arm64 signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main

上の 2 行が Ubuntu 公式、3 行目が追加した外部リポジトリ(この例では GitHub CLI)です。外部リポジトリは、そこを運営している組織を信頼することになります。使っていないものは外します。

4. Ubuntu のパッチ込みのバージョンを確認する#

dpkg-query の ${Version} を使うと、Ubuntu のリビジョンまで含めたバージョンが表示されます。

Command

dpkg-query -W -f='${Package}\t${Version}\n' openssh-server openssl

Output

openssh-server	1:10.2p1-2ubuntu3.8
openssl	3.5.5-1ubuntu3.7

手動インストールしたパッケージのバージョンを、まとめて表示することもできます。

Command

apt-mark showmanual | xargs dpkg-query -W -f='${Package}\t${Version}\n'

Output(パッケージ名とバージョンは説明用の架空のものです)

example-tool    1.2.3-0ubuntu2.1
libfoo          4.5+dfsg-1ubuntu1
bar-daemon      2.0.1+ds1-0ubuntu1
baz-cli         3.4.0

バージョン表記の読み方#

Debian 系のバージョンは次の形をしています。

[epoch:]上流バージョン[-リビジョン]
部分例意味
epoch1:先頭の数字とコロン。付いていないことも多い
上流バージョン1.2.3開発元がリリースしたバージョン
Debian リビジョン-1 / -0Debian でのパッケージの版。0 は Debian ではまだパッケージ化されていないことを示す
Ubuntu リビジョンubuntu2Ubuntu が独自に加えたパッチの版
リリース後の更新.1security や updates で配信された修正

上の出力例を読み解くと、次のようになります。

  • 1.2.3-0ubuntu2.1: 上流 1.2.3 を Ubuntu が2回目にパッケージ化し、リリース後に修正が1回加わっています。末尾の .1 は、セキュリティ修正の可能性があります。
  • 4.5+dfsg-1ubuntu1: +dfsg は、Debian Free Software Guidelines に合わせて非フリーなファイルを取り除いたことを示します。Debian の版 1 に、Ubuntu のパッチが加わっています。
  • 2.0.1+ds1-0ubuntu1: +ds1 は、ソースを再パッケージしたことを示します。
  • 3.4.0: リビジョンがありません。Ubuntu のリポジトリではなく、配布元の公式リポジトリから入れたものによく見られる形です。

このほか、+ubuntuXX.YY.N のように、特定の Ubuntu リリース向けのビルドであることを示す表記もあります。

取得元は次のコマンドで確認できます。

Command

apt policy openssh-server

Output

openssh-server:
  Installed: 1:10.2p1-2ubuntu3.8
  Candidate: 1:10.2p1-2ubuntu3.8
  Version table:
 *** 1:10.2p1-2ubuntu3.8 500
        500 http://ap-northeast-1.ec2.archive.ubuntu.com/ubuntu resolute-updates/main arm64 Packages
        500 http://security.ubuntu.com/ubuntu resolute-security/main arm64 Packages
        100 /var/lib/dpkg/status
     1:10.2p1-2ubuntu3 500
        500 http://ap-northeast-1.ec2.archive.ubuntu.com/ubuntu resolute/main arm64 Packages
  • Installed: 今入っているバージョン
  • Candidate: apt upgrade で入る予定のバージョン。Installed と違えば、更新があります
  • ***: 今入っているバージョンの行。その下に取得元の URL が並びます

外部リポジトリから入れたものは、ここに外部の URL が表示されます。Ubuntu のセキュリティ更新とは別の経路で更新されます。

バージョン番号だけで脆弱性は判断できない#

上流のバージョンが古く見えても、Ubuntu が修正をバックポートしていることがあります。脆弱性が修正されているかどうかは、changelog に書かれた CVE 番号で確認します。

Command

apt changelog openssh-server | grep -i 'CVE-' | head -5

Output(CVE 番号は説明用の例です)

    - debian/patches/CVE-2026-0001.patch: upstream: sftp: be stricter in
    - CVE-2026-0001
    - debian/patches/CVE-2026-0002.patch: upstream: Reset GSSAPI client
    - CVE-2026-0002

調べたい CVE 番号が出てくれば、そのバージョンで修正されています。

Ubuntu の CVE トラッカー(https://ubuntu.com/security/cves)でパッケージ名を検索して、修正済みのバージョンを確認する方法もあります。

5. 更新状況・サービス・ポート・ユーザー#

アップグレードできるパッケージを確認します。

Command

apt list --upgradable 2>/dev/null

Output(抜粋)

Listing...
cloud-init/resolute-updates 26.2-0ubuntu1~26.04.1 all [upgradable from: 26.1-0ubuntu3~26.04.1]
containerd/resolute-updates 2.2.2-0ubuntu1.2 arm64 [upgradable from: 2.2.2-0ubuntu1.1]
libsystemd0/resolute-updates 259.9-0ubuntu0.1 arm64 [upgradable from: 259.8-0ubuntu0.1]

取得元が -security のものは、セキュリティ修正です。優先して当てます。この一覧は、最後に apt update した時点の情報です。最新の状態を見るには、先に sudo apt update を実行します(6 章)。

動作中のサービスを確認します。

Command

systemctl list-units --type=service --state=running

Output(抜粋)

  UNIT                 LOAD   ACTIVE SUB     DESCRIPTION
  apache2.service      loaded active running The Apache HTTP Server
  chrony.service       loaded active running chrony, an NTP client/server
  containerd.service   loaded active running containerd container runtime
  cron.service         loaded active running Regular background program processing daemon
  ssh.service          loaded active running OpenBSD Secure Shell server

知らないサービスが動いていたら、何のためのものかを調べます。不要なら止めて、自動起動を無効にします。

待ち受けポートを確認します。

Command

sudo ss -tulpn

Output(抜粋)

Netid State  Recv-Q Send-Q  Local Address:Port  Peer Address:Port Process
udp   UNCONN 0      0       127.0.0.53%lo:53         0.0.0.0:*    users:(("systemd-resolve",pid=3393,fd=17))
udp   UNCONN 0      0           127.0.0.1:323        0.0.0.0:*    users:(("chronyd",pid=821,fd=4))
tcp   LISTEN 0      4096          0.0.0.0:22         0.0.0.0:*    users:(("sshd",pid=1012,fd=3))
tcp   LISTEN 0      511                 *:80               *:*    users:(("apache2",pid=1388,fd=4))
tcp   LISTEN 0      4096        127.0.0.1:5432       0.0.0.0:*    users:(("postgres",pid=1120,fd=6))

Local Address の見方がポイントです。

待ち受けアドレス意味
127.0.0.1、127.0.0.53、[::1]そのマシンの中からしか接続できない
0.0.0.0、[::]、*すべてのネットワークから接続できる可能性がある

この例では、SSH(22)と Apache(80)が外に開いています。PostgreSQL(5432)は 127.0.0.1 なので、外からは接続できません。

ログインできるユーザーを確認します。

Command

grep -v -E 'nologin|false' /etc/passwd

Output

root:x:0:0:root:/root:/bin/bash
sync:x:4:65534:sync:/bin:/bin/sync
ubuntu:x:1000:1000:Ubuntu:/home/ubuntu:/bin/bash
sato:x:1001:1001::/home/sato:/bin/bash

sync は特殊なアカウントで、ログインには使われません。それ以外に、知らないユーザーがいないかを見ます。

sudo グループと docker グループのメンバーを確認します。

Command

getent group sudo docker

Output

sudo:x:27:ubuntu,sato
docker:x:109:sato

行の最後がメンバーです。どちらも root 相当の権限になるので、必要な人だけにします。

ファイアウォールの状態を確認します。

Command

sudo ufw status verbose

Output

Status: active
Logging: on (low)
Default: deny (incoming), allow (outgoing), deny (routed)
New profiles: skip

To                         Action      From
--                         ------      ----
22/tcp (OpenSSH)           ALLOW IN    Anywhere
80/tcp                     ALLOW IN    Anywhere
22/tcp (OpenSSH (v6))      ALLOW IN    Anywhere (v6)

Default: deny (incoming) で、許可したものだけを通す設定になっていることを確認します。Status: inactive なら、ufw は使われていません。クラウドでは、セキュリティグループなどほかの仕組みで制御していることもあります。

6. パッチ適用とバージョンアップ#

ここからはシステムを変更する操作です。本番環境では、次の準備をしてから行います。

  • 戻せるようにしておく: EC2 なら AMI やスナップショットを取得する
  • 先に検証環境で試す: 同じ構成の環境で、更新後にアプリが動くことを確かめる
  • 時間を決めて行う: サービスの再起動や OS の再起動が発生するので、影響の少ない時間に行う

流れ#

sudo apt update          パッケージの一覧を最新にする
  → apt list --upgradable  何が更新されるかを見る
  → apt -s upgrade         シミュレーションで確認する
  → sudo apt upgrade       適用する
  → 再起動が必要かを確認する

パッケージの一覧を最新にする: apt update#

apt update はパッケージ自体を更新するのではなく、リポジトリから最新の一覧を取ってくるだけ です。これをしないと、新しい修正があっても見えません。

Command

sudo apt update

Output(抜粋)

Hit:1 http://ap-northeast-1.ec2.archive.ubuntu.com/ubuntu resolute InRelease
Get:2 http://ap-northeast-1.ec2.archive.ubuntu.com/ubuntu resolute-updates InRelease [126 kB]
Get:3 http://security.ubuntu.com/ubuntu resolute-security InRelease [126 kB]
Fetched 1,284 kB in 2s (642 kB/s)
16 packages can be upgraded. Run 'apt list --upgradable' to see them.
  • Hit: 前回から変化なし
  • Get: 新しい情報を取得した

最後の行で、更新できるパッケージの数が分かります。

適用する前にシミュレーションする#

-s(simulate)を付けると、実際には何も変更せずに、何が起きるかだけを表示します。root 権限も不要です。

Command

apt -s upgrade

Output(抜粋)

NOTE: This is only a simulation!
...
Upgrading:
  cloud-init       libpam-systemd     libudev1            systemd-resolved
  cloud-init-base  libsystemd-shared  systemd             systemd-sysv
  libnss-systemd   libsystemd0        systemd-cryptsetup  udev

Not upgrading yet due to phasing:
  containerd  tmux  ubuntu-pro-client  ubuntu-pro-client-l10n

Summary:
  Upgrading: 12, Installing: 0, Removing: 0, Not Upgrading: 4
  • Upgrading: 更新されるパッケージ
  • Not upgrading yet due to phasing: Ubuntu が更新を少しずつ配信している途中のもの(段階的リリース)。数日待てば自動で対象になります。不具合があったときの影響を抑える仕組みなので、通常は待って問題ありません
  • Summary: Removing が 0 であることを必ず確認します

systemd やカーネルなど、重要なパッケージが含まれていないかもここで見ます。

適用する: apt upgrade と apt full-upgrade#

Command

sudo apt upgrade

Output(抜粋)

Summary:
  Upgrading: 12, Installing: 0, Removing: 0, Not Upgrading: 4
  Download size: 18.4 MB
  Space needed: 1,024 kB / 6,512 MB available

Continue? [Y/n] y
...
Setting up systemd (259.9-0ubuntu0.1) ...

確認が出るので、Summary を見てから y を入力します。

upgrade と full-upgrade の違いは次のとおりです。

コマンド新しいパッケージの追加既存パッケージの削除使いどころ
apt upgradeするしない日常のパッチ適用
apt full-upgradeするする(依存関係の解決に必要なら)依存関係が変わる更新。upgrade で更新しきれなかったとき

full-upgrade はパッケージを削除することがあるので、必ず apt -s full-upgrade で Removing の中身を確認してから実行します。

緊急でない定期対応: 一括で最新に保つ#

緊急の脆弱性対応でなければ、特定のパッケージだけでなく、更新できるものを 一括で最新に保つ 選択肢があります。月 1 回などのメンテナンス時間を決めて、次の流れで行います。

1. sudo apt update                更新候補を取得する
2. apt list --upgradable          一括で更新されるものを確認する
3. apt -s upgrade                 シミュレーションで、Installing / Removing を確認する
4. 必要なら apt-mark hold         止めたいものを除外する
5. sudo apt upgrade               一括で適用する
6. 再起動が必要かを確認する        needrestart / /var/run/reboot-required
7. 動作を確認する                 サービスの状態、ログ、アプリの疎通

一括で更新するときに見るのは、次の点です。

  • 何が上がるか: apt list --upgradable の一覧に、DB やミドルウェア、PHP のようにアプリの動作に影響するものが含まれていないかを見ます。
  • 削除や新規インストールがないか: apt -s upgrade の Removing と Installing を見ます。apt upgrade はパッケージを削除しませんが、カーネルのように新規に入るものはあります。削除が必要な更新は apt full-upgrade になるので、中身を確認してから実行します。
  • 止めたいものがないか: バージョンを固定したいものは、apt-mark hold で除外します。
  • 再起動が必要か: カーネルや共有ライブラリが更新されると、再起動が必要になります。

定期的に行うメリットは、更新の差分が小さく保たれ、1 回あたりの影響が読みやすくなることです。長く放置すると、更新が大きくなり、問題が起きたときに原因の切り分けも難しくなります。

運用の組み合わせとしては、次の分け方が一般的です。

種類方法
セキュリティ修正unattended-upgrades で毎日自動適用する(後述)
それ以外の更新定期のメンテナンス時間に、上の流れで一括適用する
緊急の脆弱性定期を待たず、次の節のように対象だけを先に更新する

本番環境では、いきなり一括適用せず、検証用の環境で同じ更新を先に試してから適用します。

セキュリティ修正の自動適用は、本番でも使われる#

unattended-upgrades による自動適用は、本番サーバーでも一般的です。標準では、自動適用されるのはセキュリティ修正(-security のリポジトリ)だけで、機能追加を含む更新は対象外です。毎日少しずつ適用するので、1 回あたりの変更も小さくなります。

本番で使うときは、次の点に注意します。

  • サービスの再起動: パッケージが更新されると、そのサービスが自動で再起動される場合があります。PHP-FPM やデータベースが、意図しない時間に再起動する可能性があります。
  • カーネルは自動で再起動されない: 標準では自動再起動しないため、再起動するまで古いカーネルで動き続けます。自動再起動を有効にするなら、時間帯を指定します。
  • 止めたいものを除外する: バージョンを固定したいものは、apt-mark hold か Package-Blacklist で除外します。
  • 失敗に気づく仕組み: メール通知や /var/log/unattended-upgrades/ の監視で、適用の失敗に気づけるようにします。

自動適用を使わない運用もあります。重要なシステムで検証環境を先に通す場合や、イメージを作り直して入れ替える場合です。

どの運用でも、パッチが出たことに気づく仕組みが必要#

「定期的に当てる」「自動で当てる」「作り直す」のどれでも、パッチが出たことに気づく必要があります。方針で変わるのは、気づいた後の適用の方法だけです。

運用方針適用の方法気づく必要がある理由
定期的に当てる一括で apt upgrade緊急のものは、定期を待たず先に対応するかを判断するため
自動で当てるunattended-upgrades自動適用の対象外のものや、失敗したものに気づくため
作り直すイメージを更新して入れ替える気づかないと作り直すきっかけがなく、古いパッケージのまま動き続けるため

気づく手段の例は、次のとおりです。

  • ディストリビューションの情報: Ubuntu Security Notices(USN)を購読する。CVE トラッカー(ubuntu.com/security/cves)を見る。
  • サーバー側の検知: apt list --upgradable や pro security-status で更新候補を定期的に確認する。脆弱性スキャナで、入っているパッケージを調べる。
  • 作り直す運用: イメージのビルド時にパッケージの更新を取り込む。定期ビルドか、脆弱性の通知をきっかけにした再ビルドにする。
  • ミドルウェアの情報: PHP のようにアプリの動作に直結するものは、公式のリリース情報を別に追う。

気づいた後の判断も、あらかじめ決めておくと対応が速くなります。

  • 緊急度の基準: 外部から攻撃できるか、すでに悪用されているか(7 章の PHP2Shell のような事例)を基準にします。
  • 対応の期限: 緊急度ごとに、いつまでに適用するかを決めます。
  • 適用の方法: 緊急なら対象のパッケージだけを先に更新し、緊急でなければ定期に一括で適用します。

更新の前後で何が変わったかを記録しておく#

更新の作業は、あとから「何がどう変わったか」を説明できるようにしておくと、障害の調査や監査のときに役立ちます。更新の前にスナップショットを取り、実行ログを残し、更新後に差分を取る運用です。

1. 更新前にスナップショットを取る

mkdir -p ~/upgrade-log/$(date +%F) && cd ~/upgrade-log/$(date +%F)

dpkg-query -W -f='${Package} ${Version}\n' | sort > before.txt   # 全パッケージの版
apt list --upgradable 2>/dev/null > upgradable.txt              # 更新候補
apt -s upgrade > simulate.txt                                   # 今回のシミュレーション
uname -r > kernel_before.txt                                    # 動いているカーネル
systemctl --failed > failed_before.txt                          # 失敗しているサービス

日付ごとのディレクトリに保存するので、過去の更新と比べられます。failed_before.txt を取っておくと、更新前から失敗していたサービスと、更新で新しく壊れたサービスを区別できます。

2. 更新の実行ログも残す

sudo apt upgrade -y 2>&1 | tee upgrade.log

端末全体を録画する script コマンドもありますが、更新のログを残すだけなら tee で足ります。-y は確認を省略するので、simulate.txt で Removing の中身を確認してから実行します。

3. 更新後に差分を取る

dpkg-query -W -f='${Package} ${Version}\n' | sort > after.txt
diff before.txt after.txt > diff.txt     # 版が変わったものだけが出る
cat diff.txt

ls /var/run/reboot-required 2>/dev/null; cat /var/run/reboot-packages.txt 2>/dev/null
sudo reboot
# 再起動後
uname -r > kernel_after.txt
systemctl --failed > failed_after.txt

diff.txt には、バージョンが変わったパッケージだけが、更新前(<)と更新後(>)の組で出ます。/var/run/reboot-required があれば再起動が必要で、reboot-packages.txt にその原因のパッケージが書かれています。

再起動のあとは、kernel_before.txt と kernel_after.txt を比べて、カーネルが新しくなったことを確認します。failed_before.txt と failed_after.txt を比べれば、更新で壊れたサービスにも気づけます。

ファイル内容使いどころ
before.txt / after.txt / diff.txt全パッケージのバージョンと差分何が上がったかの記録
upgradable.txt / simulate.txt更新候補とシミュレーション想定どおりの更新だったかの確認
upgrade.log実行時の出力エラーや警告の調査
kernel_before.txt / kernel_after.txtカーネルのバージョン再起動で新しいカーネルになったかの確認
failed_before.txt / failed_after.txt失敗しているサービス更新で壊れたサービスの検知

apt 自身も、/var/log/apt/history.log に更新の履歴を残しています(「自動更新を確認する」の節を参照)。ここで紹介した方法は、それに加えて「更新前の状態」と「更新後の差分」を、手元で見やすい形でまとめて残すものです。

特定のパッケージだけを更新する#

緊急の脆弱性対応などで、1 つだけ更新したいときは --only-upgrade を使います。入っていないパッケージを新しくインストールすることはありません。

Command

sudo apt install --only-upgrade openssh-server

Output(抜粋)

Upgrading:
  openssh-client  openssh-server  openssh-sftp-server

Summary:
  Upgrading: 3, Installing: 0, Removing: 0, Not Upgrading: 13

依存関係にあるパッケージも一緒に更新されることがあります。

--only-upgrade が「新しくインストールしない」のは、コマンドラインで名前を指定したパッケージだけです。更新に必要な依存パッケージは、新しく入ることがあります。実行前に apt -s install --only-upgrade <名前> でシミュレーションして、Installing と Removing の中身を確認します。

ソフトウェア単位で更新する具体的な流れは、7 章の PHP の例で詳しく見ます。

カーネルだけを更新する#

カーネルは、ほかのパッケージと更新のされ方が違います。Ubuntu のカーネルは バージョンがパッケージ名に入っています(例: linux-image-7.0.0-1015-aws)。新しいカーネルは「既存パッケージの更新」ではなく「別のパッケージの新規インストール」として入ります。

それを束ねているのが、バージョンを含まない メタパッケージ です。メタパッケージは「最新のカーネルに依存する」だけの空のパッケージで、これを更新すると、新しいカーネルが依存関係として入ります。

メタパッケージ役割
linux-aws下の 2 つをまとめたもの
linux-image-aws最新のカーネル本体(linux-image-<バージョン>-aws)に依存する
linux-headers-aws最新のカーネルヘッダーに依存する。DKMS でドライバをビルドするときに必要

-aws の部分は、使っているカーネルの種類で変わります。EC2 なら -aws、通常のサーバーなら -generic です。入っているものを確認します。

Command

apt list --installed 'linux-*aws*' 2>/dev/null

Output

Listing...
linux-aws/resolute-updates,resolute-security,now 7.0.0.1014.14 arm64 [installed,automatic]
linux-headers-7.0.0-1014-aws/resolute-updates,resolute-security,now 7.0.0-1014.14 arm64 [installed,automatic]
linux-headers-aws/resolute-updates,resolute-security,now 7.0.0.1014.14 arm64 [installed,automatic]
linux-image-7.0.0-1014-aws/resolute-updates,resolute-security,now 7.0.0-1014.14 arm64 [installed,automatic]
linux-image-aws/resolute-updates,resolute-security,now 7.0.0.1014.14 arm64 [installed,automatic]
linux-modules-7.0.0-1014-aws/resolute-updates,resolute-security,now 7.0.0-1014.14 arm64 [installed,automatic]

sudo apt update のあとで、カーネルの更新が来ているかを確認します。

Command

apt list --upgradable 'linux-*' 2>/dev/null

Output

Listing...
linux-aws/resolute-updates,resolute-security 7.0.0.1015.15 arm64 [upgradable from: 7.0.0.1014.14]
linux-headers-aws/resolute-updates,resolute-security 7.0.0.1015.15 arm64 [upgradable from: 7.0.0.1014.14]
linux-image-aws/resolute-updates,resolute-security 7.0.0.1015.15 arm64 [upgradable from: 7.0.0.1014.14]

ここに出るのはメタパッケージだけです。linux-image-7.0.0-1015-aws のような新しいカーネル本体は、まだ入っていないので upgradable には出ません。

メタパッケージを指定してシミュレーションします。

Command

apt -s install --only-upgrade linux-aws linux-image-aws linux-headers-aws

Output(抜粋)

NOTE: This is only a simulation!
...
Installing dependencies:
  linux-aws-headers-7.0.0-1015  linux-image-7.0.0-1015-aws
  linux-headers-7.0.0-1015-aws  linux-modules-7.0.0-1015-aws

Upgrading:
  linux-aws  linux-headers-aws  linux-image-aws

Summary:
  Upgrading: 3, Installing: 4, Removing: 0, Not Upgrading: 13

Installing dependencies に新しいカーネルが並んでいれば、想定どおりです。カーネルの更新では Installing が 0 にならないのが普通です。Not Upgrading: 13 は、カーネル以外の更新が手つかずで残っていることを示します。

問題なければ適用します。

Command

sudo apt install --only-upgrade linux-aws linux-image-aws linux-headers-aws

Output(抜粋)

Setting up linux-image-7.0.0-1015-aws (7.0.0-1015.15) ...
...
Processing triggers for linux-image-7.0.0-1015-aws (7.0.0-1015.15) ...
/etc/kernel/postinst.d/initramfs-tools:
update-initramfs: Generating /boot/initrd.img-7.0.0-1015-aws
/etc/kernel/postinst.d/zz-update-grub:
Generating grub configuration file ...
done

インストールしただけでは、まだ古いカーネルで動いています。新しいカーネルは再起動して初めて使われます。 再起動して、uname -r で切り替わったことを確認します。

Command

sudo reboot
# 再接続してから
uname -r

Output

7.0.0-1015-aws

古いカーネルは、新しいカーネルで起動できなかったときの予備として残っています。

Command

dpkg-query -W -f='${db:Status-Abbrev} ${Package}\n' 'linux-image-[0-9]*'

Output

ii  linux-image-7.0.0-1014-aws
ii  linux-image-7.0.0-1015-aws

新しいカーネルで問題なく動くことを確かめたら、sudo apt autoremove --purge で古いカーネルを整理できます。apt は、動作中のカーネルと 1 つ前のカーネルは自動削除の対象から外すので、すべてが消えることはありません。ただし /boot の容量が少ない環境では、カーネルがたまるとインストールに失敗するので、定期的に整理します。

逆に、カーネル以外だけを更新したい ときは、メタパッケージを一時的に hold します(hold については後述)。

Command

sudo apt-mark hold linux-aws linux-image-aws linux-headers-aws
sudo apt upgrade
sudo apt-mark unhold linux-aws linux-image-aws linux-headers-aws

再起動の時間が取れないときに、カーネル以外のパッチを先に当てる、といった使い方ができます。終わったら unhold を忘れないようにします。

なお、Ubuntu Pro の Livepatch を有効にすると、重大なカーネルの脆弱性の一部は再起動なしで修正されます。それでも、すべての修正が Livepatch で配信されるわけではないので、定期的な再起動とカーネルの更新は必要です。

再起動が必要かを確認する#

カーネルや一部のライブラリを更新したときは、再起動しないと新しいものが使われません。再起動が必要なときは、次のファイルが作られます。

Command

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

Output

*** System restart required ***
linux-image-7.0.0-1015-aws
linux-base

No such file or directory と表示されれば、再起動は不要です。

再起動までは不要でも、古いライブラリを使ったまま動いているサービス が残ることがあります。needrestart で確認できます。-r l は「一覧を出すだけで、再起動はしない」という指定です。

Command

sudo needrestart -r l

Output(抜粋)

Services to be restarted:
 systemctl restart apache2.service
 systemctl restart cron.service

ここに出たサービスは、再起動するまで古いコードのままです。脆弱性の修正を確実に効かせるには、サービスを再起動します。

更新したくないパッケージを止める: apt-mark hold#

アプリの互換性の都合で、特定のパッケージのバージョンを固定したいときは hold します。

Command

sudo apt-mark hold containerd
apt-mark showhold

Output

containerd set on hold.
containerd

hold したものは apt upgrade で更新されなくなります。セキュリティ修正も届かなくなる ので、理由と期限を記録しておき、不要になったら sudo apt-mark unhold containerd で戻します。

自動更新(unattended-upgrades)を確認する#

Ubuntu では、セキュリティ修正を自動で適用する unattended-upgrades が標準で有効になっています。

Command

cat /etc/apt/apt.conf.d/20auto-upgrades

Output

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

どちらも "1" なら、毎日、一覧の更新とセキュリティ修正の適用が自動で行われます。

自動・手動を問わず、いつ何が更新されたかは apt の履歴に残っています。

Command

grep -E '^(Start-Date|Commandline|Upgrade)' /var/log/apt/history.log | tail -6

Output(抜粋)

Start-Date: 2026-10-24  06:38:33
Commandline: /usr/bin/unattended-upgrade
Upgrade: zlib1g:arm64 (1:1.3.dfsg+really1.3.1-1ubuntu3.1, 1:1.3.dfsg+really1.3.1-1ubuntu3.2)
Start-Date: 2026-10-24  13:40:30
Commandline: apt install --only-upgrade openssh-server
Upgrade: openssh-server:arm64 (1:10.2p1-2ubuntu3.7, 1:10.2p1-2ubuntu3.8)

Upgrade: のかっこの中は「(更新前, 更新後)」です。障害が起きたときに「直前に何が更新されたか」を調べる手がかりになります。

apt の管理外のものも更新する#

3 章で見つけた apt の管理外のものは、apt upgrade では更新されません。それぞれの方法で確認します。

Command

snap refresh --list

Output

Name    Version   Rev   Size    Publisher    Notes
core22  20260909  2988  72.4MB  canonical**  base

snap は通常、自動で更新されます。ここに出ているのは、次の自動更新を待っているものです。

Command

pip list --user --outdated

Output

Package  Version Latest Type
-------- ------- ------ -----
requests 2.32.3  2.32.5 wheel

/usr/local/bin や /opt に置いたバイナリ(この記事の例では AWS CLI や Terraform)は、配布元のリリース情報を見て、自分で入れ直します。

元に戻す#

更新後に不具合が出たときは、リポジトリに古いバージョンが残っていれば、バージョンを指定して入れ直せます。使えるバージョンは apt list -a で確認します(2 章)。

Command

sudo apt install openssh-server=1:10.2p1-2ubuntu3

ただし、古いバージョンには修正前の脆弱性が残っています。また、リポジトリから消えたバージョンには戻せません。確実に戻す手段は、作業前に取ったスナップショット です。

Ubuntu のリリースを上げる: do-release-upgrade#

24.04 LTS から 26.04 LTS のように、Ubuntu 自体のリリースを上げるときは do-release-upgrade を使います。パッケージの更新とは影響の大きさがまったく違うので、別の作業として計画します。

新しいリリースが出ているかを確認します。

Command

do-release-upgrade -c

Output(24.04 LTS で実行した場合の例)

Checking for a new Ubuntu release
New release '26.04.1 LTS' available.
Run 'do-release-upgrade' to upgrade to it.

最新の LTS を使っている場合は、次のように表示されます。

Checking for a new Ubuntu release
There is no development version of an LTS available.

リリースを上げるときのポイントです。

  • LTS から LTS へは、.1 のリリース(例: 26.04.1)が出てから案内される のが標準です。それまでは -c を実行しても表示されません
  • 先に今のリリースのパッケージを最新にする: sudo apt update && sudo apt full-upgrade をして、再起動してから行います
  • SSH 越しに行うときは注意: 途中で切断されると、中途半端な状態で止まります。do-release-upgrade は SSH を検知すると予備の SSH ポートを開くので、ファイアウォールやセキュリティグループで許可しておくか、tmux の中で実行します
  • 外部リポジトリは無効になる: アップグレード中に外部リポジトリは無効にされます。終わったら、新しいリリースに対応しているかを確認してから戻します
  • クラウドでは作り直しも選択肢: EC2 などでは、新しいリリースの AMI からサーバーを作り直して移行する方が、安全で手順も単純なことが多いです

7. 事例: PHP2Shell に対応する#

ここまでの手順を、実際の脆弱性対応に当てはめてみます。題材は、2026 年 10 月に公開された PHP の脆弱性「PHP2Shell」です。

PHP2Shell の概要#

項目内容
CVECVE-2026-109618、CVE-2026-109619
内容PHP 本体のメモリ安全性の不具合。ログインなしで、リモートからコードを実行される
深刻度CVSS v4.0 で 9.2(Critical)。Ubuntu の優先度も Critical
経緯10 月 20 日に、修正版が出る前に詳細がネットに流出。10 月 21 日に PHP が修正版を公開し、Ubuntu も同日に USN-8948-1 を公開
上流の修正版PHP 8.5.12、8.4.27、8.3.36、8.2.35

CVE-2026-109619 はアップロードの進捗表示機能での解放後使用(use-after-free)、CVE-2026-109618 はメモリ不足が起きたときのメモリ破壊です。PHP は「設定変更だけでは完全には防げない」としていて、修正版への更新が唯一の確実な対策です。

インターネットに公開している PHP のサイトがあれば、すぐに対応が必要な種類の脆弱性です。

流れ#

1. PHP が入っているか、どこから入れたかを確認する
2. Ubuntu の CVE トラッカーで、修正済みのバージョンを確認する
3. apt update して、修正版が届いているかを見る
4. PHP のパッケージだけをシミュレーション → 更新する
5. changelog で CVE が修正されていることを確認する
6. PHP を読み込んでいるサービスを再起動する
7. apt の管理外の PHP がないかを確認する

1. PHP が入っているかを確認する#

Command

apt list --installed '*php*' 2>/dev/null

Output

Listing...
libapache2-mod-php8.5/resolute-updates,resolute-security,now 8.5.4-0ubuntu1.4 arm64 [installed,automatic]
php-common/resolute,now 2:96ubuntu1 all [installed,automatic]
php8.5-cli/resolute-updates,resolute-security,now 8.5.4-0ubuntu1.4 arm64 [installed,automatic]
php8.5-common/resolute-updates,resolute-security,now 8.5.4-0ubuntu1.4 arm64 [installed,automatic]
php8.5-mysql/resolute-updates,resolute-security,now 8.5.4-0ubuntu1.4 arm64 [installed,automatic]
php8.5-opcache/resolute-updates,resolute-security,now 8.5.4-0ubuntu1.4 arm64 [installed,automatic]
php8.5-readline/resolute-updates,resolute-security,now 8.5.4-0ubuntu1.4 arm64 [installed,automatic]
php/resolute,now 2:8.5+96ubuntu1 all [installed]

Ubuntu 26.04 LTS の PHP は 8.5 系です。libapache2-mod-php8.5 があるので、5 章で見た Apache が PHP を読み込んでいることが分かります。

CVE トラッカーは、ソースパッケージ の単位で修正状況を載せています。1 つのソースパッケージから、複数のパッケージ(バイナリパッケージ)が作られます。どのソースパッケージから来たものかを確認します。

Command

dpkg-query -W -f='${Package}\t${source:Package}\t${Version}\n' '*php*'

Output

libapache2-mod-php8.5	php8.5	8.5.4-0ubuntu1.4
php	php-defaults	2:8.5+96ubuntu1
php-common	php-defaults	2:96ubuntu1
php8.5-cli	php8.5	8.5.4-0ubuntu1.4
php8.5-common	php8.5	8.5.4-0ubuntu1.4
php8.5-mysql	php8.5	8.5.4-0ubuntu1.4
php8.5-opcache	php8.5	8.5.4-0ubuntu1.4
php8.5-readline	php8.5	8.5.4-0ubuntu1.4

2 列目がソースパッケージです。更新が必要なのは、ソースパッケージが php8.5 のものです。php と php-common は「PHP の既定のバージョンを選ぶ」ためのパッケージ(php-defaults)で、PHP 本体のコードは含みません。

取得元も確認します。

Command

apt policy php8.5-common

Output

php8.5-common:
  Installed: 8.5.4-0ubuntu1.4
  Candidate: 8.5.4-0ubuntu1.4
  Version table:
 *** 8.5.4-0ubuntu1.4 500
        500 http://ap-northeast-1.ec2.archive.ubuntu.com/ubuntu resolute-updates/main arm64 Packages
        500 http://security.ubuntu.com/ubuntu resolute-security/main arm64 Packages
        100 /var/lib/dpkg/status
     8.5.4-0ubuntu1 500
        500 http://ap-northeast-1.ec2.archive.ubuntu.com/ubuntu resolute/main arm64 Packages

Ubuntu 公式から入っていることが分かります。

PHP は、Ubuntu 公式ではなく 外部の PPA(ppa:ondrej/php など)から入れていることがよくあります。その場合、ここに PPA の URL が表示され、バージョンも 8.5.11-1+ubuntu26.04.1+deb.sury.org+1 のような形になります。PPA の PHP は Ubuntu のセキュリティ更新の対象外です。その場合は Ubuntu の CVE トラッカーではなく、PPA 側で修正版が公開されたかを確認します。

2. 修正済みのバージョンを確認する#

Ubuntu の CVE トラッカー(https://ubuntu.com/security/CVE-2026-109618)を見ると、リリースごとの修正版が載っています(抜粋)。

Ubuntuソースパッケージ修正済みのバージョン
26.04 LTS (resolute)php8.58.5.4-0ubuntu1.5
24.04 LTS (noble)php8.38.3.6-0ubuntu0.24.04.13
22.04 LTS (jammy)php8.18.1.2-1ubuntu2.28
20.04 LTS (focal)php7.47.4.3-4ubuntu2.29+esm5(Ubuntu Pro が必要)

ここで注目したいのは、上流の修正版が 8.5.12 なのに、Ubuntu の修正版は 8.5.4 のままだということです。Ubuntu は上流のバージョンを上げずに、修正だけをバックポートしています(4 章)。php -v の表示は更新後も PHP 8.5.4 のままなので、php -v を見て「8.5.12 より古いから脆弱」と判断するのは間違い です。比べるのは、末尾の Ubuntu のリビジョンまで含めたバージョンです。

今入っているバージョンが修正版より古いかを、dpkg --compare-versions で比べます。Debian 系のバージョンの大小は、文字列の比較とは規則が違うので、このコマンドを使うのが確実です。

Command

dpkg --compare-versions 8.5.4-0ubuntu1.4 lt 8.5.4-0ubuntu1.5 && echo "修正前のバージョンです"

Output

修正前のバージョンです

lt は「より小さい(less than)」です。この例では、まだ修正が入っていません。

3. 修正版が届いているかを見る#

まず、unattended-upgrades がすでに当てていないかを確認します。セキュリティ修正なので、自動更新が有効なら、1 日以内に適用されていることもあります。

Command

grep -A2 'Start-Date: 2026-10-2' /var/log/apt/history.log | grep -i php

何も表示されなければ、まだ当たっていません。一覧を最新にして、修正版が来ているかを確認します。

Command

sudo apt update
apt list --upgradable '*php*' 2>/dev/null

Output(apt list の部分)

Listing...
libapache2-mod-php8.5/resolute-updates,resolute-security 8.5.4-0ubuntu1.5 arm64 [upgradable from: 8.5.4-0ubuntu1.4]
php8.5-cli/resolute-updates,resolute-security 8.5.4-0ubuntu1.5 arm64 [upgradable from: 8.5.4-0ubuntu1.4]
php8.5-common/resolute-updates,resolute-security 8.5.4-0ubuntu1.5 arm64 [upgradable from: 8.5.4-0ubuntu1.4]
php8.5-mysql/resolute-updates,resolute-security 8.5.4-0ubuntu1.5 arm64 [upgradable from: 8.5.4-0ubuntu1.4]
php8.5-opcache/resolute-updates,resolute-security 8.5.4-0ubuntu1.5 arm64 [upgradable from: 8.5.4-0ubuntu1.4]
php8.5-readline/resolute-updates,resolute-security 8.5.4-0ubuntu1.5 arm64 [upgradable from: 8.5.4-0ubuntu1.4]

取得元に resolute-security があり、バージョンがトラッカーの修正版(8.5.4-0ubuntu1.5)と一致しています。

トラッカーで「修正済み」になっていても、ミラーへの反映には時間差があります。ここに出てこないときは、少し時間をおいて apt update からやり直します。

4. PHP のパッケージだけを更新する#

更新するパッケージを、ソースパッケージが php8.5 のものから自動で選びます。手で並べると、拡張モジュール(php8.5-mysql など)の指定漏れが起きやすいからです。

Command

PKGS=$(dpkg-query -W -f='${db:Status-Abbrev} ${Package} ${source:Package}\n' \
  | awk '$1 == "ii" && $3 == "php8.5" { print $2 }')
echo $PKGS

Output

libapache2-mod-php8.5 php8.5-cli php8.5-common php8.5-mysql php8.5-opcache php8.5-readline

$1 == "ii" で、インストール済み(設定ファイルだけ残っているものを除く)に絞っています。まずシミュレーションします。

Command

apt -s install --only-upgrade $PKGS

Output(抜粋)

NOTE: This is only a simulation!
...
Upgrading:
  libapache2-mod-php8.5  php8.5-common  php8.5-opcache
  php8.5-cli             php8.5-mysql   php8.5-readline

Summary:
  Upgrading: 6, Installing: 0, Removing: 0, Not Upgrading: 10

Upgrading が PHP のパッケージだけで、Removing が 0 であることを確認して適用します。

Command

sudo apt install --only-upgrade $PKGS

Output(抜粋)

Setting up php8.5-common (8.5.4-0ubuntu1.5) ...
Setting up php8.5-cli (8.5.4-0ubuntu1.5) ...
Setting up libapache2-mod-php8.5 (8.5.4-0ubuntu1.5) ...
...

緊急対応では、このように対象を絞って更新すると、ほかのパッケージの更新による影響を切り分けられます。ほかの更新は、あとで通常のメンテナンスとして当てます。

5. 修正されたことを確認する#

バージョンを確認します。

Command

dpkg-query -W -f='${Package}\t${Version}\n' $PKGS

Output

libapache2-mod-php8.5	8.5.4-0ubuntu1.5
php8.5-cli	8.5.4-0ubuntu1.5
php8.5-common	8.5.4-0ubuntu1.5
php8.5-mysql	8.5.4-0ubuntu1.5
php8.5-opcache	8.5.4-0ubuntu1.5
php8.5-readline	8.5.4-0ubuntu1.5

changelog に、対象の CVE 番号が載っていることも確認します。

Command

apt changelog php8.5-common | grep -E 'CVE-2026-10961[89]'

Output(パッチのファイル名は説明用の例です)

    - debian/patches/CVE-2026-109618.patch: fix request cleanup after
    - CVE-2026-109618
    - debian/patches/CVE-2026-109619.patch: fix use-after-free in
    - CVE-2026-109619

6. PHP を読み込んでいるサービスを再起動する#

パッケージを更新しただけでは、動作中の PHP は古いコードのままです。 mod_php なら Apache のプロセスの中、PHP-FPM なら FPM のプロセスの中に、修正前の PHP が読み込まれています。

Command

sudo needrestart -r l

Output(抜粋)

Services to be restarted:
 systemctl restart apache2.service

使い方に応じて、次のサービスを再起動します。

PHP の使い方再起動するもの
Apache + mod_php(libapache2-mod-php8.5)apache2
PHP-FPM(php8.5-fpm)php8.5-fpm
コマンドラインや cron で実行するスクリプト不要(実行のたびに新しい PHP が読み込まれる)
常駐するワーカー(キューの処理など)そのワーカー(supervisor や systemd で管理しているもの)

Command

sudo systemctl restart apache2
systemctl status apache2 --no-pager | head -3

Output

● apache2.service - The Apache HTTP Server
     Loaded: loaded (/usr/lib/systemd/system/apache2.service; enabled; preset: enabled)
     Active: active (running) since Sun 2026-10-25 10:12:41 JST; 4s ago

since の時刻が今になっていれば、再起動されています。もう一度 sudo needrestart -r l を実行して、何も出ないことを確認します。unattended-upgrades で自動適用された場合も、サービスが再起動されているとは限らないので、同じように確認します。

最後に、サイトが正常に表示されることを確認します。

7. apt の管理外の PHP がないかを確認する#

3 章で見たとおり、apt の管理外のものは apt upgrade では更新されません。PHP の場合、次のような入り方がよくあります。

Command

which -a php

Output

/usr/bin/php

ここに /usr/local/bin/php など別の場所のものが出てきたら、ソースからビルドしたものなどが残っています。上流の修正版(8.5.12 など)でビルドし直します。

コンテナで PHP を動かしている場合は、コンテナの中の PHP がホストとは別にあります。

Command

sudo docker ps --format '{{.Names}}\t{{.Image}}' | grep -i php

Output(説明用の例です)

legacy-app	php:8.5.11-apache

ホストの PHP を更新しても、コンテナの中の PHP は古いままです。修正版のイメージ(この例なら php:8.5.12-apache)に変えて、作り直します。

更新前後の確認観点のまとめ#

ここまでの確認を、更新の前と後に分けて整理します。

時点観点確認することコマンド
更新前入っているもの対象のパッケージと、更新前のバージョンを控えるdpkg-query -W -f='${Package}\t${Version}\n' $PKGS > before.txt
更新前状態の異常途中で止まった設定や、保留がないdpkg --audit、apt-mark showhold
更新前動き方CLI、PHP-FPM、Apache のどれで動いているかsystemctl list-units 'php*' apache2.service
更新後全部上がった更新前の控えと比べて、取りこぼしがないdpkg-query -W -f='${Package}\t${Version}\n' $PKGS | diff before.txt -
更新後修正が入っている対象の CVE が changelog にあるapt changelog <パッケージ> | grep CVE-
更新後設定が読める再起動の前に、文法エラーがないphp-fpm8.5 -t、apachectl configtest
更新後プロセスが新しい動いているプロセスが再起動されているsudo needrestart -r l
更新後機能が保たれている必要な拡張モジュールが読み込まれているphp -m を更新前と比べる
更新後アプリが動く実際のリクエストが成功し、ログにエラーが増えていないヘルスチェック用の URL への curl -I、journalctl -u php8.5-fpm --since "10 min ago"

パッケージのバージョンが上がっても、サービスが再起動されていなければ、メモリ上では古いコードが動き続けます。バージョンの確認だけで終わらせず、再起動とアプリの動作確認までを対応の完了とします。

すぐに更新できないとき#

検証が間に合わないなどの理由ですぐに更新できない場合でも、「何もしない」は選択肢になりません。PHP は設定変更だけでは完全に防げないとしています。英国の NCSC は、すぐに修正できないシステムはオフラインにすることも検討するよう呼びかけています。外部に公開しているサイトなら、一時的に公開を止める、アクセス元を絞る、といった手当てをしてから、検証と更新を進めます。

参考#

まとめ#

  • apt list は 取得元・バージョン・状態 が 1 行で分かる。--installed、--upgradable、-a を使い分ける
  • apt-mark showmanual の「手動」は、apt の管理内での話。セキュリティ上の盲点になりやすいのは、apt の管理外で入れたもの(ソースビルド、言語別のパッケージ管理、snap、外部リポジトリ)
  • バージョン番号だけで判断せず、changelog と CVE トラッカーで修正状況を確認する
  • パッチは apt update → シミュレーション → apt upgrade → 再起動の要否確認 の順で当てる。Removing が出たら立ち止まる
  • 特定のパッケージだけを更新するときは apt install --only-upgrade。カーネルはメタパッケージ(linux-aws など)を指定し、再起動して uname -r で切り替わりを確認する
  • 脆弱性対応は ソースパッケージで対象を特定 → トラッカーの修正版と比較 → 対象だけ更新 → changelog で確認 → サービスを再起動 の順で行う。php -v のような上流のバージョン表示では判断しない
  • hold したパッケージと apt の管理外のものは、自動では更新されない。自分で追いかける
  • リリースのアップグレードは別の作業として計画し、スナップショットを取ってから行う

全体を一通り把握できたら、lynis のような診断ツールで自動チェックするのも有効です。

Command

sudo apt install lynis
sudo lynis audit system

Output(抜粋)

  Lynis security scan details:

  Hardening index : 64 [############        ]
  Tests performed : 268

Hardening index が対策の進み具合の目安です。結果の下に、改善の提案(Suggestions)が並びます。