弊社が検証した iCLUSTA+ の1契約(PHP 8.4.20)では、OPcache のロード自体は利用者側の操作で成功しました。ところが速度はまったく改善せず、回避策として試した file_cache 方式では、WordPress のトップページ表示がかえって約2.4倍遅くなりました。
しかも、同じ設定を隔離したディレクトリでベンチマークすると 87.8%短縮と、かなり良い結果が出ていました。この数字だけを信じて本番に導入していたら、導入後の方がどうしようもなく遅くなるという結果になるところでした。
これはサーバーの不具合ではなく、PHP をどう実行するか(プロセスを永続させるかどうか)という実行方式の違いによるものです。また、確認したのは弊社が検証した1契約のみのため、同じサービスのすべての環境で同じ結果になるとは限りません。
共用サーバーで WordPress を運用していて、「とりあえず OPcache を有効にしてみよう」「OPcache 有効にしたら効果あるのでは」と考えている方に、導入前の参考として読んでいただければと思います。
なお、以前公開したWordPressが急に遅くなった!保守会社の自社サイトで起きた障害対応を包み隠さず公開しますでも、「OPcache を有効化したが、これ単体では改善しなかった」と書きました。今回は「なぜ効かないことがあるのか」を別のサーバーであらためて検証した内容になります。
「遅いから OPcache」という定番の期待
WordPress の管理画面で「ツール」→「サイトヘルス」と進むと、環境によっては次のような指摘が出ます。
オペコードキャッシュが有効化されていません

OPcache は、PHP がソースコードを中間コード(オペコード)に変換した結果を保存し、次のリクエストでその変換を省く仕組みです。WordPress はコア・テーマ・プラグインで数千ファイル規模になるため、「有効にすれば速くなる」という期待は原理としては筋が通っています。
ただし前提があります。変換結果を置いた場所が、次のリクエストでも残っていることです。標準の OPcache は置き場所に共有メモリを使うため、PHP のプロセスがリクエストをまたいで生き続けている環境(php-fpm など)が前提になります。
共用サーバーでは、この前提が成り立たないことがあります。そして、成り立っていなくても OPcache は「有効」と表示されます。
前提確認: 「載っているか」ではなく「またいで効いているか」
そこで最初に見るべきなのは、OPcache が有効かどうかではなく、リクエストをまたいで効いているかどうかです。判定点はひとつ、hits が増えるかどうかです。
次のスクリプトを、確認したいサイトのドキュメントルートに置いてブラウザで開いてください。phpinfo() を丸ごと出すとサーバー内部のパスや環境変数まで表に出るため、判定に必要な項目だけを出しています。
<?php
// opcache-check.php — 確認が終わったら削除してください
header('Content-Type: text/plain; charset=UTF-8');
header('X-Robots-Tag: noindex, nofollow');
printf("PHP %s / SAPI %s\n", PHP_VERSION, PHP_SAPI);
printf("extension_loaded : %s\n", extension_loaded('Zend OPcache') ? 'true' : 'false');
$st = function_exists('opcache_get_status') ? @opcache_get_status(false) : false;
if ($st === false) {
echo "opcache_get_status() が false。OPcache は無効です\n";
exit;
}
printf("opcache_enabled : %s\n", $st['opcache_enabled'] ? 'true' : 'false');
printf("num_cached_scripts : %d\n", $st['opcache_statistics']['num_cached_scripts']);
printf("hits : %d\n", $st['opcache_statistics']['hits']);
printf("misses : %d\n", $st['opcache_statistics']['misses']);
printf("used_memory : %d\n", $st['memory_usage']['used_memory']);
見方はこうです。
opcache_enabledがfalse… そもそも OPcache が動いていませんtrueで、リロードするたびにhitsが増える … 期待どおりに効いていますtrueなのに、10回リロードしてもhitsが0のまま … 今回の記事の状態です

3番目は注意が必要で、サイトヘルス上は「有効」と表示されるため、数字を見なければ対応済みだと思ってしまいます。
このファイルはサーバーの状態を外部に見せるものなので、確認後は削除してください。X-Robots-Tag は、消し忘れた場合に検索エンジンにインデックスされないようにするためのものです。
実測1: ロードは通った。それでも hits は0のままだった
検証したのは iCLUSTA+ の1契約、PHP 8.4.20 の環境です。
最初の確認で分かったのは、次の状態です。
- SAPI の表記は
cgi-fcgi - 拡張は42個が読み込まれていて、すべて静的に組み込まれている(
extension=行が有効になっているものは1つもない) - Zend 拡張はゼロ。つまり OPcache は読み込まれていない
では OPcache 自体が無いのかというと、そうではありません。ここで役に立つのがコンパイル時定数です。
extension_dir が "./" になっているときに本来のディレクトリを確認する方法
このサーバーでは ini_get('extension_dir') が "./" を返していました。マスターの php.ini が上書きしていて本来の拡張ディレクトリが見えない状態のため、この値を頼りに探しても見つかりません。
そこで、ini ではなく PHP のビルド時に埋め込まれた定数を見ます。
<?php
header('Content-Type: text/plain; charset=UTF-8');
printf("PHP_EXTENSION_DIR : %s\n", PHP_EXTENSION_DIR);
printf("PHP_CONFIG_FILE_PATH : %s\n", PHP_CONFIG_FILE_PATH);
printf("ini の extension_dir : %s\n", ini_get('extension_dir'));
printf("opcache.so : %s\n", file_exists(PHP_EXTENSION_DIR . '/opcache.so') ? '実在' : '無し');
PHP_EXTENSION_DIR は ini でどう上書きされていても変わりません。今回はこの定数から /usr/local/php84/lib/php/extensions という本来のディレクトリが分かり、そこに opcache.so(約1.2MB)があることを確認できました。
ディレクトリに php.ini を置いてロードする
iCLUSTA+ では、利用者がディレクトリごとに独自の php.ini を設置できます。公式サポート情報にも「はい、設置可能です。php.iniファイルの設定を有効にしたいディレクトリごとに設置してください」と明記されています(GMOクラウド サポート ch-580)。同じページの記述例には zend_extension 行の例も載っており、php.ini から Zend 拡張を読み込むこと自体は想定内の使い方です。
WordPress のファイルが無い隔離ディレクトリを作り、次の内容の php.ini を置きました。
; CGI版PHPの直接アクセス制限。既定値は 1 で、1 のままだと
; "Security Alert! The PHP CGI cannot be accessed directly" になります
cgi.force_redirect = 0
; マスターでは "./" に上書きされているため、ビルド時の本来値に戻します
extension_dir = /path/to/php/extensions
; 絶対パス指定にして、extension_dir の解釈に依存させません
zend_extension = /path/to/php/extensions/opcache.so
opcache.enable = 1
opcache.enable_cli = 0
opcache.memory_consumption = 128
opcache.max_accelerated_files = 20000
opcache.validate_timestamps = 1
opcache.revalidate_freq = 2
extension_loaded('Zend OPcache') も opcache_enabled も true になりました。ロードは成功です。
10回叩いても、1バイトも動かなかった
ところが、同じ URL に10回続けてアクセスすると、次の4項目が10回ともまったく同じ値でした。
| 項目 | 1回目 | … | 10回目 |
|---|---|---|---|
num_cached_scripts | 1 | … | 1 |
hits | 0 | … | 0 |
misses | 1 | … | 1 |
used_memory | 9,181,072 | … | 9,181,072 |

決定的なのは hits と misses です。共有メモリの中身が次のリクエストにも残っているなら、同じスクリプトを2回目に開いた時点で hits が増えるはずですが、10回開いても hits は0、misses は1のままだったため、毎回「初めて見るファイル」として扱われていることがわかります。
used_memory が1バイトも変わらないことも、これを裏付けています。毎回まっさらな共有メモリを確保してスクリプトを1本だけコンパイルし、レスポンスを返した直後に捨てているため、何度計測しても同じ値になります。
SAPI の表記は cgi-fcgi で、名前だけ見れば FastCGI でプロセスが常駐していそうに読めます。ですが実体は、リクエストごとに新しいプロセスが起動して終了する動きでした。SAPI 名はプロセスが永続する保証にはならないというのが、今回いちばん大きな気づきでした。
実測2: file_cache の罠 — ベンチマークで87.8%短縮、実サイトで2.4倍悪化
共有メモリが使えないなら、次の手は決まっています。OPcache には変換結果をディスクに保存する opcache.file_cache があり、opcache.file_cache_only = 1 を付ければ共有メモリを使わずファイルだけで動く、プロセスが永続しない環境のために用意された機能です。先ほどの php.ini に3行足しました。
opcache.file_cache = /path/to/opcache-cache
opcache.file_cache_only = 1
opcache.file_cache_consistency_checks = 1
隔離ベンチマーク: 165.29ms → 20.21ms
まず隔離ディレクトリで計測しました。約1.2MB・関数5,000個を定義するだけの巨大な PHP ファイルを機械生成し、require にかかる時間を計測しています。実行負荷はほぼゼロのため、計測値はほぼコンパイル時間そのものです。
| 条件 | 中央値 | 範囲 |
|---|---|---|
| 共有メモリのみ | 165.29ms | 140.97〜174.47ms |
file_cache_only = 1 | 20.21ms | 16.43〜24.26ms |
87.8%短縮。 正直、この時点では「効果あり」だと思っていました。
実 WordPress: A/B/A で計測したら逆だった
次に、同じ設定を WordPress に適用してトップページの表示時間を計測しました。時間帯による負荷変動に引きずられないよう、ON → OFF → ON と戻す A/B/A の順で計測しています。
| 状態 | 最小 | 中央値 | 最大 |
|---|---|---|---|
| ON(ウォームアップ済み・500ms間隔・8回) | 3,339ms | 3,901ms | 9,884ms |
| OFF(500ms間隔・8回) | 1,365ms | 2,026ms | 3,169ms |
| ON に戻す(間隔なし・6回) | 3,157ms | 8,002ms | 10,231ms |

3行目だけリクエスト間隔を空けていないため、中央値と最大値は他の行と同条件では比較できません。負荷変動の影響を受けにくい最小値で比較すると、ON が 3,339ms と 3,157ms、OFF が 1,365ms で、約2.4倍の悪化であることがわかりました。OFF で下がり ON でまた上がったため、たまたま重い時間帯に当たったわけではないと思います。
ウォームアップ後のキャッシュは 1,777ファイル / 51.19MB。キャッシュ自体は正常に作られています。
なぜ結果が逆になったのか
原因はベンチマークの設計にありました。
file_cache_only はコンパイルを省く代わりに、キャッシュファイルの読み込みという別の作業を増やします。差し引きが得か損かは、1リクエストで何個のファイルを読むかで決まります。
- ベンチマークで使った巨大ファイルは 1本。「1本の重いコンパイル」が「1本のファイル読み込み」に置き換わるだけの、これ以上ないほど有利な条件でした
- WordPress は1リクエストで 数百の小さなファイルを読むため、
file_cache_onlyではその数百個ぶんの小さな.binを毎回openしてreadすることになります

小さいファイルのコンパイルにかかる CPU 時間は、そもそも大きくありません。そこへ数百回のファイルオープンが乗ると、I/O がボトルネックになって逆転します。file_cache_consistency_checks = 1 のチェックサム検証と validate_timestamps = 1 の stat() も、ファイル数ぶんだけ上乗せされます。
教訓は、OPcache のベンチマークは対象アプリのファイル数分布を再現しないと、速くなるか遅くなるかすら誤る、ということでした。 87.8%という数字自体は正しく計測できています。計測方法が、答えたい問いに合っていなかっただけです。
副産物: ディレクトリ php.ini は「マージ」ではなく「置き換え」
最小構成で置くと、書いていない設定が既定値に戻る
ディレクトリに置く php.ini は、マスターの php.ini に追加されるのではなく、丸ごと置き換えます。公式サポート情報にも次のように書かれています。
お客さまのWebサーバー領域に独自のphp.iniを設置された場合、デフォルトのphp.iniの設定はすべて無視され、独自に設置されたphp.iniの設定に従ってPHPが動作します (GMOクラウド サポート ch-580)
最小構成の php.ini を置いたところ、次のようになりました。
| 設定項目 | マスターの値 | 最小構成の php.ini を置いたとき |
|---|---|---|
memory_limit | 512M | 128M(PHP の既定値) |
session.save_path | サーバー指定の値 | 空 |
mbstring.language | Japanese | neutral |
| 読み込み中の拡張の数 | 42 | 42(欠落なし) |
確認に使ったのは数行です。
<?php
header('Content-Type: text/plain; charset=UTF-8');
printf("memory_limit : %s\n", ini_get('memory_limit'));
printf("mbstring.language : %s\n", ini_get('mbstring.language'));
printf("session.save_path : %s\n", ini_get('session.save_path') ?: '(empty)');
printf("拡張の数 : %d\n", count(get_loaded_extensions()));
拡張の数が変わらなかったのは、この環境では42個すべてが静的に組み込まれていたためです。extension= 行で読み込んでいる環境では、ここで拡張が読み込まれなくなります。
いちばん怖いのは session.save_path が空になることでした。これは公式サポート情報でも「初期値の /tmp という場所を指してしまい、iCLUSTAの複数あるWebサーバ間でセッションファイルが共有されずセッションがうまく機能しなくなります」と注意されています(ch-580)。PHP セッションを使うフォームなどが不安定になります。画面上は何事もなく動いて、特定の操作のときだけ失敗する壊れ方なので、置いた直後には気づきにくいものです。
対策は単純で、マスターの php.ini を全文コピーし、末尾に必要な行を追記した形で置くことです。php.ini は後に書いた行が勝つため、末尾への追記が上書きになり、ロールバックも置いた php.ini を削除するだけで済みます。
なお .user.ini は php.ini と併用でき、upload_max_filesize と max_execution_time の指定も維持されました。.user.ini は部分的な上書きとして読まれ、設置したディレクトリの下位にも適用されるため、変更したい項目がその範囲に収まるなら、そちらのほうが安全だと思います。
適用されるのは「実行されたスクリプトのあるディレクトリ」だけ
もうひとつ、置き場所です。CGI として動く PHP は、php.ini を実行されたスクリプトと同じディレクトリでしか探しません。親ディレクトリを遡ることはなく、今回の検証でも同様でした。なお、同じ系統のパネルを使っている お名前.com の共用サーバーGS では、ヘルプに「※設置したディレクトリのみ有効になります」と明記されています(お名前.com ヘルプ 9640)。同系統のサービスでは同様の挙動になる可能性があります。
ここで引っかかったのが、WordPress を /wp/ のようなサブディレクトリに置いている構成です。この構成ではドキュメントルート直下に「wp/wp-blog-header.php を読み込むだけの index.php」を置くのが定番ですが、そうすると実行されるスクリプトはドキュメントルートの index.php であり、WordPress 本体があるディレクトリではありません。
つまり、/wp/php.ini を置いてもフロントの表示には適用されず、適用されるのはドキュメントルート直下の php.ini です。弊社も最初はここを取り違えて、しばらく「設定したのに何も変わらない」と悩みました。確認用スクリプトをフロントと同じディレクトリに置いて php_ini_loaded_file() を出せば、どの ini が読まれているか分かります。
なお、ディレクトリの php.ini を読むのは CGI として実行されたときだけのため、CLI で動くサーバー cron には影響しませんでした。裏を返せば cron 側には設定が適用されないということでもあります。

まとめ: この環境では、PHP層のキャッシュより先に見るべきものがあった
今回分かったことを整理します。
- OPcache のロードは利用者側でできた。しかし共有メモリはリクエストごとに捨てられ、
hitsは0のままだった。判定材料は SAPI 名ではなく、hitsとused_memoryの推移 file_cache_onlyは、WordPress では逆効果だった。隔離ベンチマークの87.8%短縮に対し、実サイトでは最小値比で約2.4倍の悪化。原因は数百の小ファイルを毎回開く I/O- ディレクトリ
php.iniはマスターを置き換える。全文コピー+末尾追記でなければ、書いていない設定が既定値に戻る php.iniが適用されるのは実行されたスクリプトのあるディレクトリだけ。サブディレクトリ設置の WordPress では、置き場所はドキュメントルート直下
この契約に対する弊社の判断は「OPcache は提案しない」でした。プロセスが永続しない以上、PHP 層でオペコードをキャッシュしても得られるものが小さいためです。この環境で速度を改善するなら、レンダリング結果を返すサーバー機能のページキャッシュや、生成済み HTML を配信する仕組みのほうが見込みがあると思います。ホスティング側に相談する場合も、論点は「OPcache の有効化」ではなく「PHP の実行方式」になります。
検証の進め方についても2つ。ひとつは 導入する前に計測すること。ベンチマークだけを見ていれば「87.8%短縮」という報告書が書けてしまうため、数字が正しくても問いに合っていないことがあります。
もうひとつは、テスト環境で行い、原状復帰まで含めて計画すること。今回は環境調査から計測まで合計で約2時間45分、うち40分がベンチマーク設計と php.ini の置き場所の取り違えによる手戻りでした。本番には触らず、確認用スクリプトも php.ini も検証後にすべて削除しています。キャッシュ置き場をドキュメントルート内に作らざるを得なかった場面では、コンパイル済みの PHP コードが Web から読めないよう .htaccess で読み取りを拒否し、外部から実際に 403 が返ることを確認してから進めました。
「有効になっているのに効いていない」は、数字を見なければ気づけません。サイトヘルスの表示だけで判断せず、hits が増えるかを10回確認してみてください。
共用サーバーの WordPress で表示速度にお困りの方は、弊社の WordPress 保守管理サポートからお気軽にご相談ください。今回のような導入前の計測から対応しています。






コメントを残す