OPcacheとは?読み方とPHP 8.5の変更点・設定・確認方法
OPcacheは、PHPがリクエストごとに繰り返しているコンパイル処理を省くための仕組みです。読み方から設定値の意味までは調べれば出てきますが、実務で詰まるのはたいてい別のところにあります。PHP 8.5に上げた途端に apt install php8.5-opcache が通らなくなった、本番にデプロイしたのに古いコードが返り続ける、WordPressのサイトヘルスに「オペコードキャッシュが有効化されていません」と出た——この記事は、そうした実際に手が止まる場面から逆算して、PHP 8.5時点の一次情報にもとづきOPcacheを整理します。
まとめ
- 読み方:公式に読み仮名の定めはない。opcode cache の略で、日本語では「オーピーキャッシュ」「オプキャッシュ」と読まれ、訳語としては「オペコードキャッシュ」が使われる。
- PHP 8.5の最大の変更:OPcache拡張が常にPHPバイナリへ組み込まれ、常にロードされるようになった。
zend_extension=opcache.soの記述は警告を出す。 php8.5-opcacheパッケージは存在しない。OPcacheはPHP本体へ静的に組み込まれており、8.4以前のphp8.4-opcacheに相当する「入れるべきパッケージ」が無い。アップグレード用スクリプトが-opcacheを引きずっているとパッケージ未検出で落ちる。Dockerでもdocker-php-ext-install opcacheは8.5でビルドが失敗する。- JITはデフォルトで無効。PHP 8.4で
opcache.jitの既定値がtracingからdisableに変わった。「PHP 8以降はJITが効いている」という前提は現在の既定値では誤り。 - 最大の事故はデプロイ反映漏れ。
opcache.validate_timestamps=0にしたらデプロイ後のリロード(またはキャッシュクリア)が必須。CLIとPHP-FPMはキャッシュを共有しない。
OPcacheの読み方と、省略している処理
読み仮名の規定はなく、実勢は「オーピーキャッシュ」「オプキャッシュ」
OPcacheはZend OPcacheの通称で、PHP 5.5からPHP本体に同梱されている公式拡張です。公式ドキュメントに読み仮名の規定はなく、日本語では「オーピーキャッシュ」「オプキャッシュ」の両方が使われています。名称の由来は「opcode cache」の短縮で、訳語としての「オペコードキャッシュ」はWordPressの日本語UIなどで採用されています。読みが割れていること自体に実害はなく、「Zend OPcache」「オペコードキャッシュ」「OPcache」はすべて同一の機能を指すと考えて差し支えありません。
キャッシュしているのはソースではなくバイトコード
PHPはリクエストのたびに、ソースファイルを読み込み、字句解析・構文解析を経てオペコード(バイトコード)へコンパイルし、それをZend VMが実行します。ソースが1文字も変わっていなくても、この変換は毎回走ります。OPcacheは変換結果のオペコードを共有メモリに置き、2回目以降のリクエストでは読み込みとコンパイルを飛ばして実行フェーズへ直行させます。
置き場所が共有メモリなので、PHP-FPMの各ワーカープロセスは同じ領域を参照し、あるワーカーがコンパイルした結果を別のワーカーがそのまま使えます。逆に言えば、SAPI(PHP-FPM、Apacheモジュール、CLI)が違えば別プロセスであり、キャッシュは共有されません。CLIで opcache_reset() を叩いてもWebのキャッシュが消えないのは、この構造が理由です。
キャッシュされるのはオペコードとインターン文字列(クラス名・関数名・リテラルなどを1つの実体に集約したもの)で、リクエストごとの変数の値やDBの結果はキャッシュされません。OPcacheはページキャッシュやオブジェクトキャッシュとは層が違う、という切り分けを最初に押さえておくと設定で迷いません。
PHP 8.5でのOPcacheの変更点
拡張が本体に統合され、常にロードされる
PHP 8.5のUPGRADINGには「The Opcache extension is now always built into the PHP binary and is always loaded.(OPcache拡張は常にPHPバイナリに組み込まれ、常にロードされる)」と明記されました。INIディレクティブの opcache.enable と opcache.enable_cli は引き続き尊重されるため、無効化したい場合は従来どおりこれらを 0 にします。
実務上の影響は、拡張の読み込み方法です。8.4までは zend_extension=opcache.so(Windowsでは php_opcache.dll)をINIに書いて読み込ませていましたが、8.5ではこの記述が警告を発生させます。8.5では --enable-opcache 系のconfigureフラグが削除され、ビルドが opcache.so も php_opcache.dll も生成しなくなったため、環境によっては「Failed loading Zend extension」というエラーとしてログに出ます。バージョンアップ時は、自前のINIや conf.d 配下に残った zend_extension 行を削除してください。
「php8.5-opcache が見つからない」の正体
Debian・Ubuntu系(Ondřej Surý氏のPHPリポジトリ)でPHPを運用している環境では、8.4まで php8.4-opcache のようなパッケージを個別に入れていました。8.5ではこのパッケージが提供されません。拡張が本体へ静的に組み込まれ、独立した共有オブジェクトとして配布する必要がなくなったためです。php8.5-cli や php8.5-fpm の依存関係にもOPcache系のパッケージは現れません(8.4までは php8.4-cli が php8.4-opcache に依存していました)。探しても見つからないのが正常で、入れるべきパッケージは存在しません。
# 8.4までの手癖のまま実行すると失敗する
$ sudo apt install php8.5-opcache
E: Unable to locate package php8.5-opcache
# 8.5ではベースパッケージだけでOPcacheが入る
$ sudo apt install php8.5 php8.5-fpm
$ php -m | grep -i opcache
Zend OPcache
旧バージョンの導入済みパッケージ一覧から新バージョンのパッケージ名を機械生成してアップグレードする運用(dpkg -l 'php8.4-*' の結果を置換して apt install に渡すような手順)では、-opcache の1行が残るだけでコマンド全体が「Unable to locate package」で停止します。移行スクリプトからはこのエントリを除外してください。
jit_hot_loop 61 と file_cache_read_only ── 8.5の追加項目
opcache.jit_hot_loopの既定値が 64 から 61(素数) へ変更。ループ回数の倍数と衝突してJITの発火が偏るのを防ぐため。opcache.file_cache_read_onlyを新設。読み取り専用のファイルシステム上にopcache.file_cacheのディレクトリを置ける。コンテナイメージにビルド時のキャッシュを焼き込み、実行時は読むだけ、という構成が組める。opcache.file_cacheを設定せずに有効化すると起動時にエラーになるため、必ず併記する。opcache_is_script_cached_in_file_cache()を追加。特定のスクリプトがファイルキャッシュに入っているかを判定できる。opcache.memory_consumptionを共有メモリ構築後に変更した場合、これまで黙って無視されていたのが、失敗として報告されるようになった。
PHP 8.5全体の変更点はPHP8.5の新機能を総覧し開発者が知っておくべき重要ポイントで整理しています。OPcache以外の非互換にも同時に当たるため、アップグレード前に併せて確認してください。
有効化と動作確認の手順
php.iniでの最小構成
PHP 8.5では拡張のロード指定は不要です。有効・無効の制御と、実行環境に応じたサイズ調整だけを書きます。
; 本番環境の最小構成(php.ini もしくは conf.d/10-opcache.ini)
opcache.enable=1
opcache.enable_cli=0
opcache.memory_consumption=256
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=32531
opcache.validate_timestamps=1
opcache.revalidate_freq=2
設定を書き換えたらPHP-FPMのリロードが必要です(sudo systemctl reload php8.5-fpm)。opcache.enable は ini_set() で実行時に有効化できません(無効化のみ可能)ため、INIで設定します。
php -m と opcache_get_status() での有効確認
# 拡張が読み込まれているか
$ php -m | grep -i opcache
# 設定値の一覧(CLIのINIを見る点に注意)
$ php -i | grep opcache.enable
稼働中のPHP-FPMの実データは、Web経由で実行するスクリプトから確認します。
<?php
$s = opcache_get_status();
var_dump($s['opcache_enabled']); // true なら有効
var_dump($s['memory_usage']['free_memory']); // 空きSHM
var_dump($s['opcache_statistics']['opcache_hit_rate']); // ヒット率
var_dump($s['opcache_statistics']['num_cached_scripts']);
ここで踏みやすい落とし穴が、CLIとWebでINIが別なことです。php -i はCLI用のINIを表示します。PHP-FPMが実際に読んでいる値は、opcache_get_status() や phpinfo() をWeb経由で実行して確認してください。opcache.enable_cli の既定値は 0 なので、CLIで opcache_get_status() が false を返しても、Web側が無効とは限りません。
公式イメージ8.5で docker-php-ext-install opcache が失敗する理由
公式PHPイメージでは、8.4以前は docker-php-ext-install opcache でOPcacheを組み込むのが定番でした。8.5ではこの行を残しているとイメージのビルドが落ちます。拡張が本体へ統合され、個別にビルドできる共有オブジェクトが存在しなくなったためで、docker-library/phpのIssue #1605でも error: 'opcache' does not exist(docker-php-ext-enable opcache)や cp: cannot stat 'modules/*'(docker-php-ext-install opcache)として報告されています。「不要になった」ではなく「消さないと失敗する」が正しい理解です。
FROM php:8.5-fpm
# RUN docker-php-ext-install opcache ← 8.5では削除する(残すとビルド失敗)
COPY ./docker/php/opcache.ini /usr/local/etc/php/conf.d/opcache.ini
主要パラメータの既定値と決め方
| ディレクティブ | 既定値 | 範囲・制約 | 役割 |
|---|---|---|---|
| opcache.enable | 1 | 実行時の有効化は不可 | 有効・無効 |
| opcache.enable_cli | 0 | 短命プロセスでは効果薄 | CLI用 |
| opcache.memory_consumption | 128(MB) | 最小 8MB | 共有メモリ総量 |
| opcache.interned_strings_buffer | 8(MB) | 上限 32767MB(8.4以降・64bit) | 文字列領域 |
| opcache.max_accelerated_files | 10000 | 200〜1000000・素数リストへ切上 | ファイル数上限 |
| opcache.validate_timestamps | 1 | 0で更新を検知しない | 更新検知 |
| opcache.revalidate_freq | 2(秒) | 0で毎リクエスト | 更新チェック間隔 |
| opcache.file_cache | (未設定) | 8.5で read_only 対応 | 2次キャッシュ |
| opcache.preload | (未設定) | Windows 非対応 | 起動時の事前読込 |
opcache.interned_strings_buffer は memory_consumption の内側から切り出される点に注意してください(総量128MBのうち16MBをインターン文字列に割くと、オペコードに使えるのは112MBです)。opcache.file_cache を設定すると共有メモリが溢れた場合やプロセス再起動直後にディスク上のキャッシュを使えるため、大量のファイルを読むアプリでは復帰が速くなります。8.5の opcache.file_cache_read_only はこれを読み取り専用FSでも使えるようにしたものです。
サイズ系で見るべき数字は「使ったメモリ量」ではなく、opcache_get_status() が返す opcache_statistics の num_cached_scripts と max_cached_keys、memory_usage の free_memory と wasted_memory です。num_cached_scripts が max_accelerated_files に到達している、あるいは free_memory が枯れて wasted_memory が膨らんでいる状態は、キャッシュが溢れて再コンパイルを繰り返しているサインです。Laravelのように依存を含めて数千〜1万ファイルを読むアプリでは、既定の10000は余裕がありません。max_accelerated_files は内部で固定の素数リスト(223、463、983、1979、3907、7963、16229、32531…)の次の値へ切り上げられるため、中途半端な数を書いても実効値はこのいずれかになります。上の設定例で 32531 を指定しているのはそのためです。
開発環境と本番環境の分け方
開発環境では opcache.validate_timestamps=1 かつ opcache.revalidate_freq=0(毎リクエストで更新チェック)にします。保存したコードが即座に反映され、キャッシュ由来の混乱がなくなります。本番環境は revalidate_freq を数秒に設定するのが無難で、validate_timestamps=0 は次章の運用が組めている場合にだけ選びます。
JIT有効化の判断基準(8.4以降の既定は disable)
結論から言えば、一般的なWebアプリケーション(WordPress、Laravel、社内API)ではJITを有効化する必要はありません。PHP本体もその判断に寄っており、PHP 8.4で opcache.jit の既定値は tracing から disable へ変更されました(同時に opcache.jit_buffer_size の既定は 0 から 64M へ変更)。つまり現在のPHPは、標準ではJITが動いていません。8.3以前も jit_buffer_size が 0 だったため既定でJITは動いておらず、「無効である」という結果は変わっていません。変わったのはスイッチの側で、jit_buffer_size にだけ値を入れてJITを有効化していた環境は、8.4に上げた時点でJITが黙って止まります。有効化を続けるなら opcache.jit の明示指定が必要です。「PHP 8からJITが入って速くなった」という説明を根拠に何もしていない環境は、JITを使っていないと考えてください。
理由は効果の出方にあります。JITが効くのは数値計算やループ主体の処理で、Webリクエストの時間を占めるDBアクセス・ファイルI/O・ネットワーク待ちは短くなりません。一方で有効化すると、JITバッファ分のメモリ消費とデバッグの難しさ(スタックトレースが追いにくい、稀にJIT固有の不具合を踏む)を引き受けることになります。
; 数値計算バッチなど、CPUバウンドな処理でだけ試す
opcache.jit=tracing
opcache.jit_buffer_size=64M
有効化するなら、対象を絞り、有効化前後のレスポンスタイムを実測してから本番に入れます。効果が測れないなら戻す、という運用でかまいません。PHPの実行モデルそのものを速くしたい場合は、JITよりもワーカー常駐型のランタイムを検討するほうが筋が良く、FrankenPHPとは何か?モダンなPHP実行エンジンの概要と誕生背景や、PHP 8.1のFibersを利用した非同期処理のメリットと活用法のアプローチが選択肢になります。
OPcacheのデメリットと、実際に起きる事故
デプロイしたのに古いコードが返る
OPcacheのデメリットとして真っ先に挙がるのがこれです。opcache.validate_timestamps=0 はファイルの更新時刻を見に行かなくなるため確かに速くなりますが、新しいコードを配置してもキャッシュが差し替わりません。この設定を選ぶなら、デプロイ手順にキャッシュの明示的な破棄を組み込むことが前提になります。
# PHP-FPMをリロードすればキャッシュは作り直される(安全側)
$ sudo systemctl reload php8.5-fpm
プロセスを落とさずクリアしたい場合は、Web経由で叩くエンドポイントを用意します。
<?php
// 認証・IP制限を必ず付ける
opcache_reset();
CLIから php -r 'opcache_reset();' を実行しても、PHP-FPMの共有メモリは消えません。前述のとおりSAPIが違えば別のキャッシュだからです。CI/CDのデプロイジョブでキャッシュを消したつもりになっている構成は、この一点で無効化されている場合があります。
シンボリックリンク方式のデプロイとrealpathキャッシュ
current → releases/20260712 のようにシンボリックリンクを張り替えるデプロイでは、キャッシュが自然に切り替わると期待してはいけません。nginxがFPMへ渡す SCRIPT_FILENAME がsymlink側のパス(/var/www/current/index.php)のままだと、PHPはrealpathキャッシュを使って実パスを解決します。realpath_cache_ttl の既定値は120秒であり、この間はリンクを張り替えても古いリリースのパスが返り続け、結果としてOPcacheも古いコードを返します。デプロイ直後だけ旧バージョンが混ざる、という再現しづらい事故の典型がこれです。
# nginx: symlinkを解決した実パスをFPMへ渡す
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
fastcgi_param DOCUMENT_ROOT $realpath_root;
$realpath_root を使えばリリースごとに異なる実パスがキーになるため、切り替えと同時に新しいコードがコンパイルされます。代償は2つあります。切り替え直後は全ファイルが未キャッシュの状態から始まるためコンパイル負荷の山が立つことと、リリースごとに新しいキーが積み上がって共有メモリと max_accelerated_files を消費することです(nginxとPHP-FPMを別ホストに分けている構成では、そもそも $realpath_root が使えません)。
なお opcache.revalidate_path(既定 0)は、include_path 上の同名ファイルの解決結果を使い回すかどうかの設定で、symlink差し替えの対策にはなりません。確実さを優先するなら、リンクを張り替えた後にPHP-FPMをリロードするのが最も単純です。opcache.validate_timestamps=0 の本番では、realpathキャッシュのTTLが切れても古いバイトコードはそのまま残るため、放置して自然に直ることは期待できません。
メモリ不足とキャッシュの全消し
共有メモリが埋まると、OPcacheは新しいスクリプトをキャッシュできなくなり、設定によっては再起動(キャッシュの全破棄)が走ります。全破棄の直後は全リクエストがコンパイルを伴うため、一時的にCPU使用率とレスポンスタイムが跳ねます。opcache_get_status() の opcache_statistics.oom_restarts(メモリ不足による再起動回数)が増えていたら、memory_consumption と max_accelerated_files の見直しが必要です。監視項目としてヒット率だけを見ていると、この兆候を取り逃します。
WordPressの「オペコードキャッシュが有効化されていません」への対処
WordPress 7.0でサイトヘルスにオペコードキャッシュの検査項目(WP_Site_Health::get_test_opcode_cache())が追加され、無効な環境では英語UIで「Opcode cache is not enabled」、日本語環境では「オペコードキャッシュが有効化されていません」といった文言が推奨項目として表示されるようになりました。この検査は opcache_get_status() を呼び、返り値の opcache_enabled を見ているだけです。つまりプラグインで解決する類の警告ではありません。サーバー側でOPcacheを有効にする以外に消す方法はありません。
環境別の対処は次のとおりです。
- VPS・専用サーバー:php.iniに
opcache.enable=1を設定してPHP-FPMをリロードする。PHP 8.5なら拡張のインストールは不要。 - レンタルサーバー:管理画面のPHP設定からOPcache(PHPアクセラレータ、APC/OPcache等の名称)を有効にする。提供されていない場合は、この警告は消せない。
- 関数が封じられている場合:OPcacheは有効でも、ホスティング側が
disable_functionsでopcache_get_statusを封じていたり、opcache.restrict_apiでAPI呼び出しを制限していると、サイトヘルスは「無効」と判定する。この場合は実質的な性能問題ではないため、警告表示を許容してよい。
なお、この警告はページキャッシュやオブジェクトキャッシュ(永続オブジェクトキャッシュの推奨項目)とは別物です。キャッシュ系プラグインを追加してもOPcacheの警告は消えません。
よくある質問(FAQ)
OPcacheの読み方は?
公式に読み仮名は定められておらず、日本語では「オーピーキャッシュ」「オプキャッシュ」の両方が使われています。opcode cache の略で、訳語の「オペコードキャッシュ」はWordPressの日本語UIなどで採用されています。
php8.5-opcache をインストールできないのはなぜ?
PHP 8.5でOPcache拡張がPHP本体に統合され、独立パッケージが提供されなくなったためです。php8.5 や php8.5-fpm を入れれば同時に利用可能な状態になります(php -m | grep -i opcache で確認できます)。8.4以前のパッケージ名を流用した移行スクリプトは -opcache の行を削除してください。
OPcacheのデメリットは?
最大のデメリットは、コード更新がキャッシュに阻まれて反映されない事故です。特に opcache.validate_timestamps=0 ではデプロイのたびにPHP-FPMのリロードか opcache_reset() が必要になります。加えて、共有メモリが不足するとキャッシュの全破棄が発生し、直後にCPU負荷が跳ねます。
JITは有効にすべき?
PHP 8.4以降、opcache.jit の既定値は disable です。DBやI/O待ちが支配的な一般的Webアプリでは効果が出にくいため、既定のまま無効で問題ありません。CPUバウンドな計算処理に限り、実測して効果を確認したうえで有効化します。
opcache_reset() をCLIで実行してもキャッシュが消えないのはなぜ?
CLIとPHP-FPMはSAPIが異なる別プロセスで、共有メモリを共有していないためです。PHP-FPMのキャッシュを破棄するには、FPMをリロードするか、Web経由で実行されるスクリプトから opcache_reset() を呼びます(そのエンドポイントには必ず認証やIP制限を付けてください)。より詳しくは、Xdebugの記事で整理しています。