はじめに
読了目安 25分
Redisを入れた。
CloudFrontも設定した。
OPcacheも有効にした。
それでも、なぜか遅い☹️
——その理由は単純です。
あなたは「キャッシュを積んでいる」だけで、設計をしていないのです。
WordPress高速化はツールの問題ではありません。
- どのレイヤで
- どの負荷を
- どう抑制するか
この3つを定量的に(=数値で)理解しているかどうかになります。
本記事では、AWS上でWordPressを本番運用する前提で、レイヤごとに必要な設計について解説していきます。
この記事がどなたかの参考になれば幸いです😼
対象読者
- 「キャッシュを入れたのに効果が出ない」と感じているエンジニア
- 設計根拠をもって説明できるようになりたいエンジニア
- AWS上でWordPressを運用しているインフラエンジニア
前提環境
- CloudFront → ALB → EC2(Nginx or Apache + PHP-FPM)
- ElastiCache(Redis)
- Aurora MySQL または RDS MySQL
1. 全体アーキテクチャ
レイヤ別責務の整理
各レイヤが 「何を」 キャッシュし、 「何を」 守るかを明確にしておくことが設計の出発点です。
| レイヤ | キャッシュ対象 | 守る対象 | キャッシュにない(MISS)時の影響 |
|---|---|---|---|
| CloudFront | HTML・静的アセット(CSS/JS/画像) | ALB / EC2 | リクエストがそのままサーバーに届く |
| Page Cache(Nginx/Apache) | WordPressが生成した動的HTML | PHP-FPM | PHPが毎回ページを生成する |
| OPcache | コンパイル済みPHPバイトコード(PHPを機械語に変換したもの) | CPU | PHPファイルを毎回解釈し直す |
| Redis | DBクエリ結果・セッション情報 | Aurora / RDS | 全クエリが毎回DBに届く |
キャッシュは「速くするもの」ではなく、下位レイヤを守る盾です。
この意識がないと、全レイヤにキャッシュを入れても効果が薄い構成になってしまいます。
2. オリジン到達率
本記事で最も重要な式です。
すべての設計判断は、この オリジン到達率 で判断することになります。
P_origin = (1 - H_cf) × (1 - H_page)
つまり「CloudFrontでもPage Cacheでも捕まらず、PHPまで到達してしまうリクエストの割合」です。この値が小さいほど、サーバーの負荷は 低く なります。
- H_cf:CloudFrontヒット率(CloudFrontが代わりに返せた割合)
- H_page:ページキャッシュヒット率(Nginx/Apacheが保存済みHTMLを返せた割合)
なぜこの式が重要なのか
性能もコストも、この値が 肝 になるからです。
CloudFrontとPage Cacheはかけ算の関係です。
つまり、片方だけ高くても効果が 限定的 で、両方高めると効果が加速的に大きくなるという性質があります。
具体例)数値で見る影響度
総リクエスト 1,000 RPS(毎秒1,000回のアクセス)のサイトで、PHPが実行される回数がどう変わるか比較してみましょう。
| シナリオ | H_cf | H_page | P_origin | PHPまで届く数 (RPS) |
|---|---|---|---|---|
| 何もしない | 0% | 0% | 100% | 1,000 |
| CloudFrontだけ | 85% | 0% | 15% | 150 |
| Pageだけ | 0% | 90% | 10% | 100 |
| 両方 | 85% | 90% | 1.5% | 15 |
「CloudFrontだけ」「Pageだけ」では100以上残るが、両方合わせるとたった15RPSになります。これが CloudFrontとPage Cacheのかけ算効果 です。
3. Aurora DBLoad統合モデル
セクション2のP_originが 「どれだけPHPまで到達するか」 を決めるとすれば、ここでは 「DBがどれだけ忙しくなるか」 を数値で測ります。
DBLoad とは、「DBが同時に処理しているクエリの重さ」を表す指標です。この値がDBのvCPU数(論理CPUコア数)を超えると、DBが処理しきれず応答が遅くなります。
DBLoad ≒ RPS_alb(CloudFront後のオリジン到達数)
× (1 - PageCacheHit率)
× Q_per_request
× (1 - Redis削減率)
× T_query
つまり「サーバーに届いたリクエスト数 × ページキャッシュで防げなかった割合 × 1ページあたりのDBクエリ回数 × Redisで防げなかった割合 × 1回のクエリにかかる時間」になります。
| 変数 | 意味 | 典型値 |
|---|---|---|
| RPS_alb | ALB到達RPS(CloudFrontでヒットせずにサーバーに届いたリクエスト数/秒) | 150 |
| PageCacheHit率 | Nginx/Apacheが保存済みHTMLを返せた割合 | 0.9 |
| Q_per_request | 1ページ表示あたりのDBへのクエリ回数 | 30 |
| Redis削減率 | Redisが代わりに答えてくれた割合 | 0.8 |
| T_query | DBへのクエリ1回の平均所要時間(秒) | 0.005 |
計算例)キャッシュなし vs あり
キャッシュなし(Page=0%, Redis=0%)
DBLoad = 150 × 1.0 × 30 × 1.0 × 0.005 = 22.5
Aurora db.r6g.xlarge(4 vCPU)でも完全に飽和してしまいます。
DBLoadがvCPU数(この場合は4)を超えると、 「CPUが足りず処理待ちが発生している」 状態です。
キャッシュあり(Page=90%, Redis=80%)
DBLoad = 150 × 0.1 × 30 × 0.2 × 0.005 = 0.45
vCPU 1未満。つまりDBはほぼ休んでいる状態です。ページキャッシュとRedisのかけ算効果が実感できるはずです。
解釈
- ページキャッシュ × Redis → かけ算で効くため、両方高めると効果が劇的に大きくなる
- クエリ最適化 → T_query削減(遅いクエリの改善は、全リクエストに波及する)
- Q_per_request削減 → 使っていないプラグインを無効化するだけで、1ページあたりのDBクエリ数自体が減る
4. カンタンCloudWatch 5分診断
私も最初は「どこから見ればいいか分からない」となっていましたが、答えは簡単で、エッジからオリジンへ、上から順に見ることを意識すれば良いです!
| 順番 | レイヤ | メトリクス(指標名) | 正常基準 | 赤信号 | 対処 |
|---|---|---|---|---|---|
| 1 | CloudFront | CacheHitRate(キャッシュで返せた割合) | > 80% | < 50% | Cache Policy・ビヘイビア(URLパターン別のルール)確認 |
| 2 | ALB | TargetResponseTime(サーバー応答時間) | < 200ms | > 1s | PHPかDBがボトルネック |
| 3 | EC2 | CPUUtilization(CPU使用率) | < 70% | > 85% | PHPワーカーが多すぎる or OPcacheが無効 |
| 4 | Redis | CurrConnections / Evictions(接続数 / キー削除数) | Evictions = 0 | Evictionsが続く | メモリ(maxmemory)拡張 |
| 5 | Aurora | DBLoad / DatabaseConnections(DB負荷 / 接続数) | DBLoad < vCPU数 | DBLoad > vCPU×2 | 遅いクエリ特定・Read Replica追加 |
診断フロー
レイヤ順に見るだけで犯人は特定できます。自分の「勘」ではなく正確な 「手順」 で原因の切り分けを行いましょう!
:::message
SLAによって基準は変動します。
:::
5. PHP-FPM ワーカー設計
PHP-FPMとは、PHPのリクエストを処理するプロセスマネージャーです。「ワーカー」はその中の作業員のようなもので、ワーカーが多いほど同時にたくさんのリクエストを処理できます。
「とりあえず pm.max_children = 50」という設定をよく見かけますが、この値に根拠はありますか?
FPMワーカー数は 「メモリから決める」 が鉄則です!
① メモリ基準で決める
まず、実測します。
# PHP-FPMプロセスごとのメモリ使用量(RSS = 実際に使っているメモリ)を確認
ps -ylC php-fpm --sort:rss
例)
- 1プロセスのピークRSS(最大メモリ使用量)60MB
- EC2利用可能メモリ(OS + Nginx分を除く)6,000MB
6,000 ÷ 60 ≒ 100ワーカー
Nginx・OS・モニタリングエージェント用に最低15–20%の余裕を残してください。
OOM Killer(メモリ不足時にLinuxがプロセスを強制終了する仕組み)に殺されるのは、この余裕計算をサボったケースが多いです(泣)
② 理論最大RPS
RPS ≒ W × (1 / T_php)
| 変数 | 意味 | 例 |
|---|---|---|
| W | FPMワーカー数(同時に働ける作業員の数) | 100 |
| T_php | 1リクエストのPHP処理時間(秒) | 0.2 |
RPS = 100 × (1 / 0.2) = 500 RPS
つまり、Page Cacheで捕まらなかったリクエストが毎秒500件を超えると、ワーカー(作業員)が足りなくなり、リクエストが行列待ちになります。行列待ちが発生すると、ユーザーにはページの表示が極端に遅くなる(数秒〜十数秒)か、最悪の場合 502 Bad Gateway や 504 Gateway Timeout としてエラーが返ります。
③ 推奨 FPM 設定(www.conf)
[www]
pm = static
pm.max_children = 100
pm.max_requests = 500
; スローリクエスト検知:10秒以上かかるリクエストをログ
request_slowlog_timeout = 10s
slowlog = /var/log/php-fpm/slow.log
なぜ pm = static(固定モード)なのか dynamic(動的モード)や ondemand(オンデマンド)は、リクエストの増減に応じてワーカーを生成・破棄します。その分オーバーヘッドがかかります。アクセスパターンが安定している本番環境では、最初から全ワーカーを起動しておく static が最も応答が安定します。
pm.max_requests = 500 はPHPのメモリリーク(長時間動作するとメモリが少しずつ漏れる問題)対策。500リクエストごとにワーカーを作り直します。
6. Redis maxmemory設計
Redisは、DBへのクエリ結果をメモリ上に保存しておく「高速なメモ帳」のようなものです。maxmemoryとは、そのメモ帳に使えるメモリの上限です。
Redisを入れただけで満足していませんか?maxmemoryの設計なき運用は、「ブレーキのない車で高速道路を走る」のと同じ危険な状態です😨
基本式
maxmemory ≒ used_memory_peak × 1.5〜2
確認方法
redis-cli INFO memory | grep used_memory_peak_human
例:used_memory_peak_human: 180M → maxmemory は 270MB〜360MB を設定。
Eviction(エビクション)の正しい読み方
Evictionとは、メモリが一杯になったときRedisが古いデータを自動的に削除することです。
十分な maxmemory を確保した上で Evictions が恒常的に > 0 なら、メモリ設計を見直しましょう!
推奨ポリシー
allkeys-lru
allkeys-lru は「メモリが一杯になったら、最も長く使われていないデータから消す」というルールです。WordPressのオブジェクトキャッシュは、削除されてもDBから再取得できるので、一時的な Eviction は問題ありません。
監視すべきCloudWatchメトリクス
| メトリクス | 意味 | アラーム目安 |
|---|---|---|
| Evictions | メモリ不足で削除されたデータ数 | 持続的に > 0 |
| CurrConnections | Redisへの現在の接続数 | PHPワーカー数の1.5倍超 |
| DatabaseMemoryUsagePercentage | Redisのメモリ使用率 | > 80% |
| CacheHits / CacheMisses | Redisが代わりに答えられた回数 / 答えられなかった回数 | ヒット率 < 80%(※) |
ElastiCacheは CacheHitRate という単一メトリクスを提供していません。CloudWatch数式 m1/(m1+m2)*100(m1=CacheHits, m2=CacheMisses)でヒット率を算出してください。
7. コスト相関モデル($ / RPS)
高速化の投資対効果を説明できますか? 「この施策でいくら安くなるか」を数式で語れるようにしましょう。
C_total ≒ C_fixed(固定費) + C_variable(変動費) × RPS × P_origin
「毎月のインフラ費用 = 固定的にかかる分 + アクセスがサーバーに届くほど増える分」です。 P_origin を下げれば、変動費が減ります。
固定費 vs 変動費の性格
| コンポーネント | コスト特性 | 削減効果 |
|---|---|---|
| ElastiCache Redis | 固定費(サーバーを立てている限り課金) | DBへのクエリが減り、Auroraのサイズを下げられる |
| Page Cache | ゼロコスト(Nginxの標準機能) | P_originを劇的に下げ、変動費を潰す |
| CloudFront | 変動費(データ転送量に応じて課金) | サーバーへの転送が減り、ALB/EC2費用が下がる |
| Aurora | 変動費(インスタンスサイズまたはACUに応じて課金) | キャッシュ層で守るほど大きなインスタンスが不要に |
試算例)Page Cache導入のインパクト
P_originを 15% → 1.5% に改善できた場合
- AuroraのDBLoadが1/10に → インスタンスサイズを 1ステップダウン可能
- EC2のCPU使用率が大幅低下 → インスタンス数削減可能
- 追加コスト 0円(Nginxの設定のみ)
ページキャッシュは、最も投資対効果(ROI)が高い高速化施策です!
8. TTL階層設計
TTL(Time To Live)とは、 キャッシュの有効期限 です。この時間が過ぎるとキャッシュは破棄され、次のリクエストで新しいデータが取得されます。
キャッシュTTLを全レイヤで同じ値にすると、全キャッシュが 同時に期限切れ になり「雪崩」が起きます。
雪崩とは何か
たとえば全レイヤのTTLを300秒に統一したとします。300秒後、CloudFront、Page Cache、Redisのキャッシュが一斉に消えます。その瞬間にアクセスが来ると、全リクエストがキャッシュなしの状態でPHP → DB まで到達します。普段はキャッシュが守ってくれていた負荷が一気にサーバーに押し寄せ、セクション5で計算した理論最大RPSを瞬間的に超えてしまい、サイトがダウンする可能性があります。
🔑 TTL設計の最重要原則
エッジ寄り(配信レイヤ)はページ鮮度を保つため短めに、オリジン寄り(オブジェクトキャッシュ)はDB負荷軽減のため長めに設定する
この原則を無視して全レイヤ同一TTLにすると、上述の「雪崩」の原因になります。以下の設計例を参考にしてください。
| レイヤ | TTL | 設計意図 |
|---|---|---|
| 静的アセット(CSS/JS/画像) | 1年 | 変更時はファイル名にハッシュ付与で破棄 |
| CloudFront HTML | 600秒 | ページ鮮度とエッジ配信のバランス |
| Page Cache | 300秒 | 更新反映を優先しつつFPM負荷軽減 |
| Redis(オブジェクト) | 3600秒 | DBクエリ結果の再利用でDB負荷削減 |
キャッシュ雪崩の対策
上記の雪崩を防ぐには、 「全キャッシュが同時に切れない」 仕組みを入れます。
-
TTLにジッター(ランダムなバラつき)を加える
たとえばTTL 300秒なら、実際には280〜320秒のように少しずつズラす。こうすることで、全て同時にキャッシュ切れになるのを防ぐ -
stale-while-revalidate
CloudFrontで「キャッシュの期限が切れても、まず古いキャッシュをユーザーに返しつつ、裏側で新しいデータを取りに行く」設定。ユーザーには常に即座に応答を返せる -
ロック機構
Nginxのfastcgi_cache_lock onを使うと、同じURLへの同時リクエストのうち「1人だけがPHPに取りに行き、残りはその結果を待つ」方式になる。100人が同時にアクセスしても、PHPが動くのは 1回だけ
キャッシュ破棄戦略
記事更新時にキャッシュを破棄する設計も必須です!
| レイヤ | 破棄方法 |
|---|---|
| CloudFront |
CreateInvalidation API(/article/123*のようにパス指定) |
| Page Cache | Nginx: rm /var/cache/nginx/... またはWordPressプラグイン経由 |
| Redis |
wp cache flush またはRedis Object CacheのPurge |
WordPressの save_post フック(記事保存時に自動実行される処理)でこれらを連動させると、更新と同時に全レイヤのキャッシュをまとめて破棄できます。
9. セキュリティ × キャッシュ統合
キャッシュ設計とセキュリティは表裏一体です。キャッシュを誤ると、他人のダッシュボードが見えたり、ログインCookieが漏れるなどのセキュリティ事故が発生する可能性があります。
キャッシュ除外必須リスト
| 対象 | 理由 | 設定箇所 |
|---|---|---|
/wp-admin/* |
管理画面はユーザー固有 | CloudFront Behavior / Nginx |
/wp-login.php |
認証ページ | CloudFront Behavior / Nginx |
| POSTリクエスト | 状態変更 | Nginx $request_method
|
| Cookie付きページ | ユーザー固有コンテンツ | Nginx $http_cookie
|
| Set-Cookieレスポンス | セッション情報の漏洩 | PHP側のレスポンスヘッダー制御 |
REST API (/wp-json/*) |
動的データ(毎回内容が変わる) | CloudFront Behavior |
原則
- 個人化ページは絶対にキャッシュしない — 他ユーザーの情報が返るリスク
- Cookieは最小化 — 不要なプラグインのCookieがCache MISSを引き起こす
- WAFはCloudFront直下 — オリジン到達前に不正リクエストを排除
- Cache-ControlヘッダーをPHP側から明示する — 「このページはキャッシュしてよいか」をサーバー側から明確に指示する
事故事例)他人のダッシュボードが見えた
原因:wordpress_logged_in_* CookieをCloudFrontが無視し、ログイン状態のページがエッジキャッシュされた。
対策:CloudFrontのCache PolicyでCookieをキャッシュキーに含める(Cookieが違えば別のキャッシュとして扱う)か、Cookie付きリクエストはキャッシュをバイパス(素通り)する。
10. 意思決定チャート
「何から手をつければいいか分からない」という方へ。
サイト特性に応じた対策の優先順位があるので、参考にしてください。
各戦略の特徴
| 戦略 | 最適なケース | 即効性 | コスト | P_originへの影響 |
|---|---|---|---|---|
| ① Page Cache + CF | ブログ・コーポレートサイト | ★★★ | 無料〜低 | 劇的に改善 |
| ② Redis + DB最適化 | 会員制・ECサイト | ★★ | 中 | DBLoad削減 |
| ③ Read Replica | 大量読み取り | ★★ | 高 | 直接影響なし(DB側の負荷分散) |
| ④ OPcache + FPM | 全構成共通 | ★★★ | 無料 | 間接的に改善 |
迷ったら、まず④(OPcache + FPM)と①(Page Cache)から。コストゼロで最大の効果が得られます。
11. セルフ診断 あなたの環境は「積んだだけ」になっていないか?
以下の10項目をYes/Noで回答してみてください。
「何を確認すればいいか」と「Noだと何が起きるか」も書いてあるので、こちらもご参考に!
キャッシュ効果を把握できているか?
| # | 診断項目 | 確認方法 | Noだとどうなる? | 詳細 |
|---|---|---|---|---|
| 1 | CloudFrontのキャッシュヒット率を把握している | CloudFrontコンソール → 「キャッシュ統計」で CacheHitRate を確認 | どれだけCFが仕事をしているか分からず、改善の指標がない | 2. オリジン到達率 |
| 2 | P_origin(PHPまで到達するリクエストの割合)を数値で説明できる |
P_origin = (1 - H_cf) × (1 - H_page) を自環境の値で計算してみる |
全体の負荷構造が見えず、どこを改善すべきか判断できない | 2. オリジン到達率 |
サーバー設定に根拠があるか?
| # | 診断項目 | 確認方法 | Noだとどうなる? | 詳細 |
|---|---|---|---|---|
| 3 | FPMワーカー数をメモリから算出している |
ps -ylC php-fpm --sort:rss でプロセスあたりRSSを確認し、利用可能メモリから逆算 |
メモリ不足でOOM Killerに殺される or 少なすぎて行列待ち発生 | 5. PHP-FPM ワーカー設計 |
| 4 | 理論最大RPSを数式で説明できる |
RPS = ワーカー数 × (1 / PHP処理時間) を自環境の値で計算 |
「何RPSまで耐えられるか」が分からず、障害時に原因の切り分けができない | 5. PHP-FPM ワーカー設計 |
キャッシュ層は健全か?
| # | 診断項目 | 確認方法 | Noだとどうなる? | 詳細 |
|---|---|---|---|---|
| 5 | Redis Evictionsが恒常的に0である | CloudWatch → ElastiCache → Evictions メトリクスを確認 | メモリ不足でキャッシュが頻繁に消え、DBに本来不要な負荷がかかり続ける | 6. Redis maxmemory設計 |
| 6 | Aurora DBLoadがvCPU数以下である | CloudWatch → RDS → Performance Insights → DBLoadがvCPU本数のラインを超えていないか | DBが処理しきれずクエリが詰まり、サイト全体がじわじわ遅くなる | 3. Aurora DBLoad統合モデル |
| 7 | TTLをレイヤ別に階層設計している | CloudFront・Page Cache・Redisで異なるTTLを意図的に設定しているか確認 | 全キャッシュが同時に切れて雪崩が起き、瞬間的にサイトダウンするリスク | 8. TTL階層設計 |
安全性と運用は整っているか?
| # | 診断項目 | 確認方法 | Noだとどうなる? | 詳細 |
|---|---|---|---|---|
| 8 | キャッシュ除外ルールを明示的に設定している | wp-admin、wp-login.php、POSTリクエスト、Cookie付きページが除外されているか確認 | 管理画面やログイン情報が他人に見える重大なセキュリティ事故 | 9. セキュリティ × キャッシュ統合 |
| 9 | 施策前後のメトリクスを比較記録している | CloudWatchダッシュボードで変更前後の値(CacheHitRate, DBLoad等)をスクリーンショットや記録で残している | 「効果があったかどうか」を証明できず、次の改善にも繋がらない | 4. カンタンCloudWatch 5分診断 |
| 10 | 障害時のレイヤ別診断手順がある | 「遅い」と報告されたとき、CF → ALB → EC2 → DB の順に何を見るか決まっている | 障害時に「どこが原因か分からない」とパニックになり、復旧が遅れる | 4. カンタンCloudWatch 5分診断 |
判定
| Yesの数 | レベル | 次のアクション |
|---|---|---|
| 9〜10個 | 設計できている | 本記事はレビュー用として活用してください! |
| 6〜8個 | 土台はある | Noだった項目の「詳細」列のセクションを重点的に読み、1つずつ問題を潰していきましょう |
| 3〜5個 | 積んでいるだけ | まず2. オリジン到達率 → 5. PHP-FPM ワーカー設計 → 8. TTL階層設計の順で読み直してください |
| 0〜2個 | これからの人 | 本記事をブックマークして、1. 全体アーキテクチャから順番に設計していきましょう |
12. 実践設定付録
以下の設定は、本記事の設計思想を反映した本番運用向けテンプレートです。各設定に「なぜこの値か」の根拠を併記しています。必ず自身の環境のメモリ・トラフィックに合わせて調整してください!
:::message
本番運用を行う際は、それぞれ十分に検証を行った上で実施してください。
:::
📋 付録内ナビゲーション
- OPcache(php.ini)
- wp-config.php(Redis Object Cache)
- Nginx FastCGI Cache(本番構成)
- Apache mod_cache(本番構成)
- CloudFront Behavior 設計(本番構成)
OPcache(php.ini)
; ==============================================
; OPcache: コンパイル済みPHPバイトコードをキャッシュし、CPUを守る
; 対象: PHP 8.1以降
; ==============================================
opcache.enable=1
opcache.enable_cli=0
; CLI(コマンドライン)では不要。WP-CronをCLIで動かす場合もFPM経由なら影響なし
opcache.memory_consumption=256
; WordPressコア + 主要プラグイン20〜30個で約120〜180MB消費。余裕を持って256MB確保
; 確認: php -r "echo opcache_get_status()['memory_usage']['used_memory'] / 1024 / 1024 . 'MB';"
opcache.max_accelerated_files=20000
; PHPは内部で定義済みの素数セット{223,463,...,130987}から直近上位の値に丸める(20000→32531)
; WordPress + プラグイン + テーマで通常10,000〜15,000ファイル。倍程度を確保
opcache.validate_timestamps=0
; ★本番環境の鉄則:ファイル変更検知を無効化
; デプロイのたびに opcache_reset() または PHP-FPM reload で反映する運用とする
; ※ステージング環境では =1, revalidate_freq=2 が便利
opcache.revalidate_freq=0
; validate_timestamps=0 のため実質無視されるが、意図を明示するため記載
opcache.interned_strings_buffer=32
; WordPress は翻訳ファイル(.mo/.po)で大量の文字列を使う。16MBでは不足するケースがあるため32MB推奨
; 確認: php -r "echo opcache_get_status()['interned_strings_usage']['used_memory'] / 1024 / 1024 . 'MB';"
opcache.save_comments=1
; WordPress・WooCommerce・多くのプラグインがPHPDocアノテーションに依存。0にすると動作しなくなる
opcache.huge_code_pages=1
; Linux HugePages(2MBページ)を利用し、TLBミスを削減してパフォーマンス向上
; 前提: OS側で HugePages を有効化済みであること
; 確認: grep HugePages /proc/meminfo
; ※HugePages未設定の環境では =0 にするか、この行をコメントアウト
; --- PHP 8.0以降: JITコンパイル ---
opcache.jit=tracing
; tracing JIT(最も高速)。PHP 8.0〜8.3では数値形式 1255 も使えるが、
; PHP 8.4以降は文字列形式(tracing / function)のみサポート。将来互換性のため文字列で指定
; WordPress単体では効果が限定的だが、画像処理や重いループ処理があるサイトでは5〜15%の改善が見込める
opcache.jit_buffer_size=64M
; JIT用コードバッファ。WordPressでは64MBで十分(memory_consumptionとは別枠)
デプロイ時の反映手順
# 方法1: PHP-FPMリロード(graceful:処理中のリクエストを待ってから再読み込み)
systemctl reload php-fpm
# 方法2: PHP側からリセット(デプロイスクリプトに組み込む場合)
curl -s http://localhost/opcache-reset.php
# ※ opcache-reset.php は IP制限 + トークン認証をかけること
本番でやってはいけないこと:
systemctl restart php-fpmはリロードと違い、処理中のリクエストを強制切断します。デプロイ時は必ずreload(graceful restart)を使ってください。
wp-config.php(Redis Object Cache)
<?php
// ==============================================
// Redis Object Cache: DBクエリ結果をキャッシュし、Auroraを守る
// プラグイン: Redis Object Cache(Till Kruss)前提
// ==============================================
define('WP_CACHE', true);
// --- 接続設定 ---
define('WP_REDIS_HOST', 'your-redis-endpoint.cache.amazonaws.com');
define('WP_REDIS_PORT', 6379);
// TLS有効化(ElastiCache の「転送中の暗号化」を有効にしている場合は必須)
define('WP_REDIS_SCHEME', 'tls');
// DB番号で論理分離(同じRedisインスタンスを複数環境で共有する場合に重要)
// 例: 本番=0, ステージング=1, 開発=2
define('WP_REDIS_DATABASE', 0);
// キープレフィックスで名前空間を分離(Blue-Greenデプロイ時にも役立つ)
define('WP_REDIS_PREFIX', 'wp_prod:');
// --- タイムアウト設定 ---
// Redisダウン時にWordPressが長時間ハングしないための安全策
define('WP_REDIS_TIMEOUT', 1); // 接続タイムアウト(秒)
define('WP_REDIS_READ_TIMEOUT', 1); // 読み取りタイムアウト(秒)
// --- 耐障害性設定 ---
// Redis障害時、エラーを出さずDBへフォールバックする(サイトは遅くなるが止まらない)
define('WP_REDIS_GRACEFUL', true);
// フラッシュ時に自分のプレフィックスのキーだけを消す(他環境に影響しない)
define('WP_REDIS_SELECTIVE_FLUSH', true);
// --- キャッシュTTL ---
// オブジェクトキャッシュのデフォルト有効期限(秒)
// §8 TTL階層設計に合わせ、Redis層は長めに設定
define('WP_REDIS_MAXTTL', 3600);
// --- phpredis拡張 推奨 ---
// phpredis(C拡張)はPredis(PHPライブラリ)より約2〜3倍高速
// Amazon Linux 2023: sudo dnf install php-redis
// 確認: php -m | grep redis
define('WP_REDIS_CLIENT', 'phpredis');
// --- 管理画面バナー非表示(任意) ---
define('WP_REDIS_DISABLE_BANNERS', true);
導入後の確認手順
# 1. phpredis拡張が入っているか確認
php -m | grep redis
# 2. WP-CLIでRedis接続テスト
wp redis status
# 3. キャッシュヒット率の確認(運用開始後)
wp redis report
注意 ElastiCacheの「転送中の暗号化」を有効にした場合、
WP_REDIS_SCHEMEをtlsにしないと接続できません。逆に暗号化を無効にしている場合はtlsを外してください。
Nginx FastCGI Cache(本番構成)
# ==============================================
# Page Cache: PHPを守る盾。サーバーまで届くリクエストを劇的に減らす
# Nginx 1.25+ 推奨
# ==============================================
# --- キャッシュストレージ定義(httpブロック内に記述) ---
fastcgi_cache_path /var/cache/nginx/wordpress
levels=1:2
keys_zone=WORDPRESS:200m # メタデータ用メモリ。200MBで約160万エントリ管理可能
inactive=600s # 600秒アクセスがなければ自動削除
max_size=1g # ディスク使用量上限
use_temp_path=off; # 一時ファイルを経由せず直接書き込み(I/O削減)
fastcgi_cache_key "$scheme$request_method$host$request_uri";
# --- キャッシュスキップ判定(httpブロック内に記述) ---
# ログイン中ユーザー・WooCommerce利用者はキャッシュを通さない
map $http_cookie $skip_cache {
default 0;
~*wordpress_logged_in_ 1; # ログイン済み
~*wp-settings- 1; # 管理画面カスタマイズ中
~*wordpress_no_cache 1; # キャッシュ無効化Cookie
~*woocommerce_cart_hash 1; # WooCommerceカート保持中
~*woocommerce_items_in_cart 1; # WooCommerceカートに商品あり
~*wp_woocommerce_session_ 1; # WooCommerceセッション
}
map $request_method $skip_cache_method {
default 0;
POST 1; # POSTは状態変更なのでキャッシュしない
}
# --- Upstreamの定義 ---
upstream php-fpm {
server unix:/run/php-fpm/www.sock; # UNIXソケット推奨(TCP localhostより低レイテンシ)
keepalive 32; # FPMへの接続を使い回す。ワーカー数の1/3程度
}
server {
listen 80;
server_name example.com;
root /var/www/html;
index index.php;
# --- セキュリティヘッダー ---
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# --- Gzip(ALBの背後でもNginx側で圧縮する場合) ---
gzip on;
gzip_comp_level 5; # 5が圧縮率と速度のバランス最良
gzip_min_length 256;
gzip_types text/css application/javascript application/json
text/xml application/xml image/svg+xml;
gzip_vary on;
# --- 静的アセット(§8 TTL階層設計:1年) ---
location ~* \.(css|js|jpg|jpeg|png|gif|ico|svg|woff2?|ttf|eot|webp|avif)$ {
expires 365d;
add_header Cache-Control "public, immutable";
access_log off; # 静的ファイルのアクセスログを無効化(I/O節約)
}
# --- WordPress管理画面・ログイン(キャッシュ完全除外) ---
location ~* ^/(wp-admin/|wp-login\.php) {
add_header Cache-Control "no-store, no-cache, must-revalidate" always;
fastcgi_pass php-fpm;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
# 管理画面はタイムアウトを長めに(プラグイン更新・エクスポート等)
fastcgi_read_timeout 300s;
}
# --- WordPress REST API(動的データのため原則キャッシュしない) ---
location ~* ^/wp-json/ {
add_header Cache-Control "no-store" always;
fastcgi_pass php-fpm;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
# --- メインのPHP処理(FastCGI Cache適用) ---
location ~ \.php$ {
# セキュリティ:存在しないPHPファイルへのアクセスを拒否
try_files $uri =404;
fastcgi_pass php-fpm;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
# --- FastCGI バッファ設定 ---
fastcgi_buffers 16 16k; # バッファ数×サイズ。大きなページ用
fastcgi_buffer_size 32k; # レスポンスヘッダー用バッファ
fastcgi_busy_buffers_size 48k;
# --- キャッシュ設定 ---
fastcgi_cache WORDPRESS;
fastcgi_cache_valid 200 300s; # §8 TTL階層設計:Page Cache = 300秒
fastcgi_cache_valid 301 302 60s;
fastcgi_cache_valid 404 10s;
fastcgi_cache_bypass $skip_cache;
fastcgi_cache_bypass $skip_cache_method;
fastcgi_no_cache $skip_cache;
fastcgi_no_cache $skip_cache_method;
# キャッシュに2回以上アクセスされたURLだけキャッシュする(一度きりのクロールを除外)
fastcgi_cache_min_uses 2;
# --- 雪崩対策(§8 参照) ---
# 同じURLへの同時リクエストを「1人だけ取りに行く」方式に集約
fastcgi_cache_lock on;
fastcgi_cache_lock_timeout 5s;
fastcgi_cache_lock_age 3s; # ロック取得者が3秒以内に返さなければ次のリクエストも通す
# --- stale-while-revalidate(§8 参照) ---
# PHP-FPMがエラーやタイムアウトになった場合、古いキャッシュを返す(503防止)
fastcgi_cache_use_stale error timeout updating
http_500 http_502 http_503 http_504;
# バックグラウンドで新しいキャッシュを取得(ユーザーは待たない)
fastcgi_cache_background_update on;
# PHPが Set-Cookie を返してもキャッシュを保存する(匿名ページ用)
# ※ログインユーザーは $skip_cache で除外済みなので安全
fastcgi_ignore_headers Cache-Control Expires Set-Cookie;
# キャッシュHIT/MISS/BYPASSをレスポンスヘッダーに出力
add_header X-Cache-Status $upstream_cache_status always;
}
# --- WordPress パーマリンク ---
location / {
try_files $uri $uri/ /index.php?$args;
}
# --- セキュリティ:wp-config.php等へのアクセスを拒否 ---
location ~* /(?:wp-config\.php|readme\.html|license\.txt|xmlrpc\.php)$ {
deny all;
}
# --- セキュリティ:隠しファイルへのアクセスを拒否 ---
location ~ /\. {
deny all;
}
}
運用コマンド
# キャッシュの手動クリア
rm -rf /var/cache/nginx/wordpress/*
# キャッシュHIT率の確認(アクセスログから集計)
awk '{print $NF}' /var/log/nginx/access.log | sort | uniq -c | sort -rn
# ※ログフォーマットに $upstream_cache_status を追加しておくこと
Apache mod_cache(本番構成)
Apache環境ではNginxの fastcgi_cache 相当の機能を mod_cache + mod_cache_disk で実現します。
# ==============================================
# Page Cache: Apache版
# 前提: mod_cache, mod_cache_disk, mod_headers, mod_rewrite が有効
# ==============================================
# --- キャッシュストレージ設定 ---
CacheRoot /var/cache/apache/wordpress
CacheDirLevels 2
CacheDirLength 1
CacheEnable disk /
CacheDefaultExpire 300
CacheMaxExpire 600
CacheLastModifiedFactor 0.1
# --- 雪崩対策:同じURLへの同時リクエストを集約 ---
CacheLock on
CacheLockPath /tmp/mod_cache-lock
CacheLockMaxAge 5
# --- stale-while-revalidate:エラー時に古いキャッシュを返す ---
CacheStaleOnError on
CacheStoreExpired on
# --- デバッグ用:キャッシュHIT/MISSをレスポンスヘッダーに出力 ---
CacheHeader on
# --- キャッシュ除外:管理画面・ログイン・REST API ---
<LocationMatch "^/(wp-admin/|wp-login\.php|wp-json/)">
Header set Cache-Control "no-store, no-cache, must-revalidate"
CacheDisable on
</LocationMatch>
# --- キャッシュ除外:POSTリクエスト ---
<If "%{REQUEST_METHOD} == 'POST'">
CacheDisable on
</If>
# --- キャッシュ除外:ログインCookieを持つユーザー ---
# mod_rewriteで環境変数を設定し、CacheDisableで参照
RewriteEngine On
RewriteCond %{HTTP_COOKIE} wordpress_logged_in_ [OR]
RewriteCond %{HTTP_COOKIE} woocommerce_cart_hash [OR]
RewriteCond %{HTTP_COOKIE} woocommerce_items_in_cart
RewriteRule .* - [E=NO_CACHE:1]
<If "reqenv('NO_CACHE') == '1'">
CacheDisable on
Header set Cache-Control "no-store, no-cache, must-revalidate"
</If>
# --- セキュリティヘッダー ---
Header always set X-Frame-Options "SAMEORIGIN"
Header always set X-Content-Type-Options "nosniff"
# --- セキュリティ:wp-config.php等へのアクセスを拒否 ---
<FilesMatch "^(wp-config\.php|readme\.html|license\.txt|xmlrpc\.php)$">
Require all denied
</FilesMatch>
# --- 静的アセット:1年キャッシュ ---
<FilesMatch "\.(css|js|jpg|jpeg|png|gif|ico|svg|woff2?|ttf|eot|webp|avif)$">
Header set Cache-Control "public, max-age=31536000, immutable"
</FilesMatch>
Nginx vs Apache
本記事のアーキテクチャでは Nginx FastCGI Cache を推奨します。Apache mod_cache_disk はディスクI/Oに依存するのに対し、Nginx FastCGI Cacheはイベント駆動モデルにより少ないリソースで高い同時接続を処理できます(※キャッシュ本体はディスク保存ですが、メタデータを keys_zone でメモリ管理するため高速にルックアップできます)。Apache環境では、リバースプロキシとして NginxをApacheの前段に置く構成 (Nginx → Apache → PHP-FPM)も検討してください。
CloudFront Behavior 設計(本番構成)
CloudFrontではURLパターンごとにBehavior(振る舞い)を分けるのが本番運用の基本です。「1つのBehaviorで全URLを処理」は設計ミスの温床になります。
Behavior一覧(優先順位順)
| 優先順 | パスパターン | 用途 | Cache Policy | TTL |
|---|---|---|---|---|
| 1 | /wp-admin/* |
管理画面 | CachingDisabled | - |
| 2 | /wp-login.php |
ログイン | CachingDisabled | - |
| 3 | /wp-json/* |
REST API | CachingDisabled | - |
| 4 |
/wp-content/uploads/* 等(※) |
静的アセット | CachingOptimized(AWS Managed) | Max 86400秒 |
| 5 |
*(デフォルト) |
動的HTML | カスタムポリシー(下記) | Default 600秒 |
CloudFrontのBehaviorは 1つにつき1つのパスパターン しか設定できません。*.css のような拡張子ワイルドカードを使う場合は拡張子ごとにBehaviorを作成する必要があります。実運用では /wp-content/* や /wp-includes/* のようにパスプレフィックスで一括する方が管理しやすくなります。
動的HTML用カスタム Cache Policy
| 設定項目 | 値 | 理由 |
|---|---|---|
| Min TTL | 0秒 | オリジンの Cache-Control: no-store を尊重するため |
| Default TTL | 600秒 | 8. TTL階層設計に準拠。オリジンがCache-Controlを返さない場合のフォールバック |
| Max TTL | 600秒 | Default TTL と同じにし、意図しない長期キャッシュを防止 |
| Cookie | なし(None) | キャッシュキーにCookieを含めない。ログインユーザーの /wp-admin /wp-login.php はBehavior1、2でバイパス済み。注意: ログインユーザーがフロントエンド(一般ページ)を閲覧する場合はBehavior5(デフォルト)にマッチし、匿名キャッシュが返される。会員制サイト・ECサイトでは Cookie を wordpress_logged_in_* でキャッシュキーに含めるか、Origin Request Policy経由でオリジン側で Cache-Control: no-store を返す設計が必要 |
| Query String | 全て含める(All) | WordPressのページネーション(?paged=2)やUTMパラメータの区別に必要 |
| Header | なし(None) | Hostはオリジン側が処理。Accept-Encoding等はCloudFrontが自動圧縮 |
Origin Request Policy(オリジンに転送する情報)
Cache PolicyとOrigin Request Policyは別ものです。Cache Policyはキャッシュキーを決め、Origin Request Policyはオリジンに何を転送するかを決めます。
| 設定項目 | 値 | 理由 |
|---|---|---|
| Cookie |
wordpress_logged_in_*, wp-settings-*, wordpress_sec_*
|
ログイン判定に必要なCookieだけをオリジンに転送 |
| Header |
Host, CloudFront-Forwarded-Proto
|
バーチャルホスト判定とHTTPS判定に必要 |
| Query String | 全て | WordPressが使用するクエリパラメータをすべて転送 |
Response Headers Policy(レスポンスヘッダー制御)
CloudFront側でセキュリティヘッダーを一元管理すると、Nginx/Apache側の設定漏れを防げます。
| ヘッダー | 値 | 効果 |
|---|---|---|
| Strict-Transport-Security | max-age=31536000; includeSubDomains |
HTTPS強制(HSTS) |
| X-Content-Type-Options | nosniff |
MIMEスニッフィング防止 |
| X-Frame-Options | SAMEORIGIN |
クリックジャッキング防止 |
| Content-Security-Policy | サイトに応じて設定 | XSS・データ注入防止(XSS対策の本命) |
補足
X-XSS-Protection ヘッダーはChrome 78(2019年)でXSS Auditorが削除されて以降、主要ブラウザでは機能しません。OWASPも現在は設定を推奨していないため、本記事では掲載していません。XSS対策は Content-Security-Policy で行ってください。
静的アセット用のBehaviorには AWS Managed Policy の CachingOptimized(ID: 658327ea-f89d-4fab-a63d-7e88639e58f6)がそのまま使えます。自分でポリシーを作る必要はありません。
13. 要点まとめ
| # | ポイント | 根拠 |
|---|---|---|
| 1 | 肝は P_origin | 性能もコストも「PHPまで到達する割合」に帰着する(2. オリジン到達率) |
| 2 | ページキャッシュは構造変換 | ゼロコストでサーバー負荷を1/10以下に(7. コスト相関モデル) |
| 3 | DBLoadは数式で語れる | 「なんとなく重い」を具体的な数値に(3. Aurora DBLoad統合モデル) |
| 4 | PHPワーカーはメモリから設計 | 根拠なき設定はメモリ不足の元(5. PHP-FPM ワーカー設計) |
| 5 | 測定しない高速化はただの願望 | 前後比較なき施策は再現できない(4. CloudWatch 5分診断・11. セルフ診断) |
結論
WordPress高速化とは、キャッシュを入れることではありません。
再現可能で、測定可能で、事故らない設計をすること。
が根本的な解決へ導きます。
本記事で提示した数式・診断フロー・設定テンプレートを使えば、「なぜこの設定なのか」を各ステークホルダーに説明できるはずです。
キャッシュは積むな、設計せよ!!😼
本記事の内容は2026年2月時点の情報に基づいています。AWSサービスの仕様は変更される可能性があるため、公式ドキュメントも併せてご確認ください。
誤植、誤りなどありましたらご指摘ください。