Nginx + PHP-FPMは、PHPアプリの本番環境で事実上の標準となっている構成です。Nginxが静的配信・リバースプロキシ・TLS終端を担い、PHP-FPMがPHPの実行に集中する責務分離により、Apache+mod_phpよりもメモリ効率・並列性・セキュリティで優位に立ちます。LaravelのCloud環境/Forge/Ploi、WordPress商用ホスティング、Symfony/CodeIgniterなど2026年時点のPHP本番サーバーはほぼ全てこの構成です。
多くの入門記事は「動くcompose.ymlを1つ載せる」までで終わっており、実案件で必要になるFastCGIプロトコル・PHP-FPMプロセス管理(pm.max_children/dynamic/ondemand)・OPcache完全設定(JIT含む)・Composer統合・Xdebug連携・アップロードサイズ4箇所の統一・フレームワーク別rewriteルール・セキュリティヘッダーまでまとまった情報がほとんどありません。
この記事では、Nginx + PHP-FPMアーキテクチャの仕組みの本質から、本番相当の完全版compose.yml・nginx.conf・Dockerfile(Composer統合済み)、PHP-FPMプロセス管理チューニング、OPcache+JIT、Xdebug VS Code連携、Laravel/Symfony/WordPress/CodeIgniterのフレームワーク別rewrite、セキュリティ強化、TCP vs Unix socket選定、トラブル対処まで、2026年の現場で即使える完全ガイドとしてまとめます。一般的なNginx活用は【Docker】Nginxローカル開発環境構築完全ガイド、WordPress特化は【Docker】WordPress環境構築完全ガイド、MySQL操作は【Docker】MySQL CLI完全ガイドで補完できます。
この記事で学べること
- Apache+mod_php vs Nginx+PHP-FPMの比較と選定理由
- FastCGIプロトコルの仕組みとNginx/PHP-FPM通信の裏側
- 本番相当の完全版compose.yml+Dockerfile(Composer統合済み)
- nginx.conf実戦設定:フレームワーク別rewrite(Laravel/Symfony/WordPress/CodeIgniter)
- PHP-FPMのプロセス管理(
pm.max_children計算式/static/dynamic/ondemand) - OPcache + JITの完全設定(開発/本番別)
- Xdebug 3の設定とVS Code連携(
pathMappings含む) - Composerのmulti-stage統合(開発/本番/CI)
- アップロードサイズ4箇所の統一(nginx/php.ini/fpm.conf/client_max_body_size)
- セキュリティ(
expose_php=Off/session.cookie_secure/CSP) - TCP vs Unix socketのfastcgi_pass選定(パフォーマンス比較)
- 典型トラブル(502/白画面/ダウンロードダイアログ/タイムアウト)の原因と対処
- Nginx + PHP-FPMアーキテクチャ:なぜ本番標準なのか
- 30秒クイックスタート:最小構成
- 本番相当の完全版:Dockerfile(Composer統合)+ compose.yml
- nginx.conf完全版+フレームワーク別rewrite
- PHP-FPMプロセス管理:pm.max_children計算式
- OPcache + JITの完全設定
- Xdebug 3 + VS Code ステップ実行デバッグ
- Composer統合パターン:multi-stage/同居/専用コンテナ
- アップロードサイズ4箇所の統一:最大の罠
- セキュリティ強化:最低限入れるべき設定
- fastcgi_pass:TCP vs Unix socket
- 典型トラブルと対処
- よくある質問
- 関連記事
- まとめ
Nginx + PHP-FPMアーキテクチャ:なぜ本番標準なのか
PHPの実行環境はApache+mod_phpで長らく使われてきましたが、2010年代後半からNginx+PHP-FPMが本番標準になりました。アーキテクチャの違いが性能・拡張性・セキュリティに直結します。
FastCGIプロトコルの仕組み
ブラウザ ↓ HTTP Nginx(静的ファイルならここで配信完了) ↓ FastCGIプロトコル(TCPまたはUnix socket) PHP-FPMプロセスプール ↓ PHP実行 アプリケーション(Laravel等) ↓ DB/Redis/外部API等 結果をFastCGI経由でNginxに返す ↓ HTTPレスポンス ブラウザ
FastCGIとCGIの違い
CGIは毎リクエストでプロセス起動→終了(超遅い)。FastCGIはプロセスをプールとして常駐させ、リクエストが来るたびプール内の待機プロセスに割り当てます。これによりプロセス起動コストが消え、PHPの実行性能が劇的に向上。PHP-FPMはFastCGIプロトコルの具体的な実装です。
30秒クイックスタート:最小構成
project/
├─ compose.yml
├─ docker/
│ ├─ nginx/
│ │ └─ default.conf
│ └─ php/
│ ├─ Dockerfile
│ ├─ php.ini
│ └─ www.conf
└─ app/
└─ public/
└─ index.php
services:
nginx:
image: nginx:1.27-alpine
ports: ["8080:80"]
volumes:
- ./app:/var/www/html
- ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
depends_on:
php:
condition: service_healthy
php:
build: ./docker/php
volumes:
- ./app:/var/www/html
healthcheck:
test: ["CMD", "php-fpm", "-t"]
interval: 10s
retries: 5
FROM php:8.3-fpm-alpine
RUN apk add --no-cache \
icu-dev libpng-dev libjpeg-turbo-dev libzip-dev oniguruma-dev \
&& docker-php-ext-configure intl \
&& docker-php-ext-install -j$(nproc) intl opcache pdo pdo_mysql mbstring zip gd
COPY php.ini /usr/local/etc/php/conf.d/zzz-custom.ini
COPY www.conf /usr/local/etc/php-fpm.d/zzz-www.conf
WORKDIR /var/www/html
server {
listen 80;
server_name localhost;
root /var/www/html/public;
index index.php;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass php:9000;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_read_timeout 60s;
}
}
<?php phpinfo();
docker compose up -d --build # http://localhost:8080 で phpinfo() が表示されれば成功
2026年の推奨構成:nginx:1.27-alpine+php:8.3-fpm-alpine。alpineは軽量(約80MB)でマイナー固定しておけば自動でセキュリティパッチが当たります。PHP 7系はEOL済み、8.0/8.1もサポート終了に近いので8.3以上が2026年標準。
本番相当の完全版:Dockerfile(Composer統合)+ compose.yml
Dockerfile:multi-stageでComposer統合
# ステージ1: Composer依存解決(CI/本番で変更頻度低い)
FROM composer:2 AS composer
WORKDIR /app
COPY composer.json composer.lock ./
RUN composer install \
--no-dev --no-interaction --no-progress --no-scripts \
--prefer-dist --optimize-autoloader
# ステージ2: PHP-FPM本体
FROM php:8.3-fpm-alpine
# 必要拡張を一括インストール
RUN apk add --no-cache \
icu-dev libpng-dev libjpeg-turbo-dev libzip-dev oniguruma-dev \
postgresql-dev linux-headers \
&& docker-php-ext-configure intl \
&& docker-php-ext-install -j$(nproc) \
intl opcache pdo pdo_mysql pdo_pgsql mbstring zip gd bcmath pcntl sockets \
&& pecl install redis \
&& docker-php-ext-enable redis \
&& apk del --no-cache icu-dev libpng-dev libjpeg-turbo-dev libzip-dev postgresql-dev linux-headers
# Composerのバイナリをコピー
COPY --from=composer /usr/bin/composer /usr/bin/composer
# 設定ファイル
COPY php.ini /usr/local/etc/php/conf.d/zzz-custom.ini
COPY www.conf /usr/local/etc/php-fpm.d/zzz-www.conf
WORKDIR /var/www/html
# 非rootユーザーで起動
USER www-data
EXPOSE 9000
compose.yml(本番相当)
services:
nginx:
image: nginx:1.27-alpine
ports: ["8080:80"]
volumes:
- ./app:/var/www/html
- ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
- ./docker/nginx/snippets:/etc/nginx/snippets:ro
- nginx-cache:/var/cache/nginx
depends_on:
php:
condition: service_healthy
healthcheck:
test: ["CMD-SHELL", "wget -qO- http://localhost/healthz || exit 1"]
interval: 10s
retries: 5
networks: [app-net]
php:
build:
context: ./docker/php
target: dev # multi-stageのdev段階
environment:
PHP_IDE_CONFIG: "serverName=docker"
TZ: "Asia/Tokyo"
volumes:
- ./app:/var/www/html
healthcheck:
test: ["CMD", "php-fpm", "-t"]
interval: 10s
retries: 5
networks: [app-net]
db:
image: mariadb:11
environment:
MARIADB_DATABASE: app
MARIADB_USER: app
MARIADB_PASSWORD: app
MARIADB_ROOT_PASSWORD: root
volumes:
- db_data:/var/lib/mysql
healthcheck:
test: ["CMD", "healthcheck.sh", "--connect"]
interval: 5s
retries: 10
networks: [app-net]
redis:
image: redis:7-alpine
networks: [app-net]
volumes:
db_data:
nginx-cache:
networks:
app-net:
nginx.conf完全版+フレームワーク別rewrite
実戦的なdefault.conf
server {
listen 80;
server_name localhost;
root /var/www/html/public;
index index.php;
charset utf-8;
client_max_body_size 20M;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
# ヘルスチェック(depends_onのhealthcheck用)
location = /healthz {
access_log off;
return 200 "ok";
add_header Content-Type text/plain;
}
# 静的ファイルは長期キャッシュ
location ~* \.(?:css|js|woff2?|ttf|otf|ico|png|jpg|jpeg|gif|webp|svg)$ {
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
try_files $uri =404;
}
# フロントコントローラ(Laravel等)
location / {
try_files $uri $uri/ /index.php?$query_string;
}
# PHP実行
location ~ \.php$ {
try_files $uri =404; # 存在しないphpは404
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass php:9000;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_read_timeout 60s;
fastcgi_buffers 16 16k;
fastcgi_buffer_size 32k;
}
# .htaccess等を外部から見せない
location ~ /\.(?!well-known).* {
deny all;
}
}
フレームワーク別rewriteルール
フレームワークによってlocation /内のtry_filesが微妙に違います。以下に主要な4つを一覧で提示します。
# Laravel / Symfony(/index.phpへフロントコントローラ)
location / {
try_files $uri $uri/ /index.php?$query_string;
}
# WordPress(index.phpへ、?にパスを渡さない)
location / {
try_files $uri $uri/ /index.php?$args;
}
# CodeIgniter 4(publicディレクトリから)
location / {
try_files $uri $uri/ /index.php?$uri&$args;
}
# Yii2(statselbypublic)
location / {
try_files $uri $uri/ /index.php$is_args$args;
}
snippets パターンで共通化
# 複数serverブロックで同じPHP設定を使う
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass php:9000;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_read_timeout 60s;
# default.conf内で使う
# location ~ \.php$ {
# include /etc/nginx/snippets/fastcgi-php.conf;
# }
PHP-FPMプロセス管理:pm.max_children計算式
FPMの設定で最も重要なのがpm(プロセスマネージャ)。多すぎるとOOM、少なすぎると503エラー。用途に応じた適切な値を理解しましょう。
[www] user = www-data group = www-data listen = 9000 listen.allowed_clients = 127.0.0.1 ; プロセスマネージャ pm = dynamic pm.max_children = 10 ; 最大プロセス数 pm.start_servers = 3 ; 起動時のプロセス数 pm.min_spare_servers = 2 ; アイドル最小 pm.max_spare_servers = 5 ; アイドル最大 pm.max_requests = 500 ; メモリリーク予防でN回で再起動 ; ステータスページ(監視用) pm.status_path = /fpm-status ; ping(healthcheck用) ping.path = /fpm-ping ; ログ access.log = /proc/self/fd/2 catch_workers_output = yes decorate_workers_output = no ; タイムアウト request_terminate_timeout = 60s request_slowlog_timeout = 10s slowlog = /proc/self/fd/2
pm.max_childrenの計算式
pm.max_children = (利用可能RAM - 予約分) / 1プロセスの平均メモリ
例: コンテナメモリ1GB、1プロセス50MB使用、OSとopcache等で200MB予約
(1024 - 200) / 50 = 16プロセス → 切り下げて pm.max_children = 14
# プロセスの実メモリを確認
docker compose exec php ps -o pid,rss,cmd ax
pm モード3種の使い分け
開発中はondemandでメモリ消費最小化、本番はdynamicで予熱+自動調整、大量同時接続の本番はstaticで安定化、が実戦的な使い分け。
OPcache + JITの完全設定
OPcacheはPHPコンパイル結果をメモリキャッシュする機能で、有効化するだけで30〜50%高速化。PHP 8のJITと組み合わせれば数値計算系はさらに2〜3倍。
開発用設定
; === OPcache 開発用 === opcache.enable = 1 opcache.enable_cli = 0 opcache.validate_timestamps = 1 ; ファイル更新チェック opcache.revalidate_freq = 0 ; 即時反映(開発向き) opcache.memory_consumption = 128 opcache.interned_strings_buffer = 16 opcache.max_accelerated_files = 10000 opcache.fast_shutdown = 1 ; === 開発で役立つ設定 === display_errors = On display_startup_errors = On error_reporting = E_ALL log_errors = On error_log = /proc/self/fd/2 ; Dockerログへ
本番用設定(JIT含む)
; === OPcache 本番用 === opcache.enable = 1 opcache.enable_cli = 1 opcache.validate_timestamps = 0 ; ファイルチェック無効(超速) opcache.memory_consumption = 256 opcache.interned_strings_buffer = 32 opcache.max_accelerated_files = 20000 opcache.fast_shutdown = 1 opcache.save_comments = 1 ; attribute等で必要 opcache.preload = /var/www/html/preload.php opcache.preload_user = www-data ; === JIT(PHP 8+) === opcache.jit_buffer_size = 128M opcache.jit = tracing ; tracing が一般的 ; === セキュリティ === expose_php = Off display_errors = Off log_errors = On error_log = /proc/self/fd/2
opcache.validate_timestamps = 0は本番専用。開発でこれを設定するとファイルを変更しても反映されず困惑します。本番でデプロイ後はdocker compose exec php php -r "opcache_reset();"で明示リセットするか、FPMプロセスリロード(docker compose exec php kill -USR2 1)でOPcacheを更新します。
JITのトレードオフ
JITは数値計算・画像処理・機械学習等CPUバウンドな処理で劇的な効果を発揮しますが、一般的なWebアプリ(DB I/Oが律速)では数%程度の改善。それでもJITによる副作用(バグ)は過去に何度か報告されているため、ミッションクリティカルなアプリはjit = disableで安全側に寄せる選択もあります。
Xdebug 3 + VS Code ステップ実行デバッグ
Xdebug対応Dockerfile
# 既存の拡張インストールに続けて追加 RUN pecl install xdebug \ && docker-php-ext-enable xdebug COPY xdebug.ini /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini
zend_extension=xdebug ; Xdebug 3 の新形式 xdebug.mode = debug,develop xdebug.start_with_request = yes xdebug.client_host = host.docker.internal xdebug.client_port = 9003 xdebug.idekey = VSCODE xdebug.log = /tmp/xdebug.log ; 開発では discover_client_host をOFFで固定 xdebug.discover_client_host = 0
services:
php:
build: ./docker/php
extra_hosts:
- "host.docker.internal:host-gateway" # Linux対応
# Xdebug用ポートは不要(phpからホストへ接続する方向)
{
"version": "0.2.0",
"configurations": [
{
"name": "Listen for Xdebug (Docker)",
"type": "php",
"request": "launch",
"port": 9003,
"pathMappings": {
"/var/www/html": "${workspaceFolder}/app"
},
"xdebugSettings": {
"max_children": 128,
"max_data": 1024,
"max_depth": 5
}
}
]
}
Xdebug 3 のポート変更点
Xdebug 2 の既定ポートは9000でしたが、Xdebug 3から9003に変更されました。PHP-FPMの9000番と競合回避が目的。Xdebug 2の古い記事を参考にすると設定が動かない原因になりやすい。
Xdebugを常時ONにすると動作が重い(2〜3倍遅くなる)ため、本番や通常テストではxdebug.mode = offに切り替え。compose.override.ymlで開発時だけ有効化するのが理想です。
Composer統合パターン:multi-stage/同居/専用コンテナ
パターン①:multi-stage(本番推奨)
# 前述のmulti-stage Dockerfile参照 # composerステージでインストール → php-fpmステージにvendor/をCOPY COPY --from=composer --chown=www-data:www-data /app/vendor /var/www/html/vendor
パターン②:同居(開発で使いやすい)
# phpコンテナ内でcomposerも使えるように
RUN curl -sS https://getcomposer.org/installer | php -- \
--install-dir=/usr/local/bin --filename=composer
# 実行例
docker compose exec php composer install
docker compose exec php composer require monolog/monolog
docker compose exec php composer dump-autoload
パターン③:専用コンテナ(profiles)
services:
php:
# ... 本体
composer:
image: composer:2
volumes:
- ./app:/app
working_dir: /app
profiles: ["tools"] # 通常起動しない
command: install
# 依存インストール docker compose run --rm composer install # パッケージ追加 docker compose run --rm composer require laravel/sanctum
2026年の推奨:開発はパターン②(phpコンテナに同居)、本番はパターン①(multi-stage)。パターン③は「ホストにPHP入れたくない/専用CIジョブ」で有用。Laravel Sail等の標準もphpコンテナに同居させる方式です。
アップロードサイズ4箇所の統一:最大の罠
「アップロードできない」原因は4箇所のどれかだけ小さいことがほとんど。全てを同じ値に揃えないと最小値でブロックされます。
# ① nginx.conf(最優先で弾かれる)
http {
client_max_body_size 128M;
}
# または server{} / location{} 個別指定も可
# ② php.ini
upload_max_filesize = 128M
post_max_size = 130M ; upload_max_filesize より大きく
memory_limit = 256M ; post_max_size以上が望ましい
# ③ php-fpm www.conf
; request_terminate_timeout が短いと大ファイルで中断
request_terminate_timeout = 300
# ④ フレームワーク側(Laravel例)
# Laravelの form validation でも max:131072 (KB) 設定
# WordPress wp-admin → 設定 → メディア でサーバー上限表示
post_max_sizeはupload_max_filesizeより大きく設定すること。両方同じだとフォームデータ全体でギリギリオーバーすることが。さらにmemory_limitはpost_max_sizeより大きくするのが基本。
セキュリティ強化:最低限入れるべき設定
php.ini のセキュリティ設定
; PHPバージョンを隠す(Server: PHP/8.3.xを消す) expose_php = Off ; ディレクトリ制限(アップロード先/キャッシュだけ許可) open_basedir = "/var/www/html:/tmp:/usr/share/php" ; 危険な関数を無効化 disable_functions = exec,passthru,shell_exec,system,proc_open,popen,curl_multi_exec,parse_ini_file,show_source ; セッションのセキュア設定 session.cookie_httponly = 1 session.cookie_secure = 1 ; HTTPS必須 session.cookie_samesite = Lax session.use_strict_mode = 1 session.sid_length = 48 session.sid_bits_per_character = 6 ; ファイルアップロードの制限 file_uploads = On upload_tmp_dir = /var/www/html/tmp ; エラー情報を外部に漏らさない display_errors = Off display_startup_errors = Off log_errors = On
nginx側のセキュリティヘッダー
# 共通セキュリティヘッダー add_header X-Content-Type-Options "nosniff" always; add_header X-Frame-Options "SAMEORIGIN" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; add_header Permissions-Policy "geolocation=(), camera=(), microphone=()" always; # PHPのServer: PHP/... を消す(expose_php=Offとの二重対策) fastcgi_hide_header X-Powered-By; more_clear_headers Server; # headers-more モジュール必要 # HTTPS時 add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always;
サーバー署名(Server: nginx, X-Powered-By: PHP/…)は本番では必ず隠す。バージョン情報が漏れると既知の脆弱性を狙った攻撃の的になります。expose_php=Off+fastcgi_hide_header X-Powered-By+server_tokens off;(nginx)の3点セットで対応。
fastcgi_pass:TCP vs Unix socket
Nginx→PHP-FPMの通信には2方式あり、性能と運用性にトレードオフがあります。
# php-fpm.conf listen = /var/run/php-fpm.sock listen.owner = www-data listen.group = www-data listen.mode = 0660 # nginx.conf fastcgi_pass unix:/var/run/php-fpm.sock; # compose.ymlでsocketディレクトリを両コンテナで共有 volumes: - php-socket:/var/run
Dockerでの現実的な選択
Dockerではnginx・php-fpmを別コンテナにするのが定石で、異なるコンテナ間でUnix socketを共有するのは手間の割に効果が小さい。TCP(fastcgi_pass php:9000;)が圧倒的に楽でシンプル。Unix socketが本当に必要なのはベアメタル本番の極限チューニングくらいです。
典型トラブルと対処
① 502 Bad Gateway
原因:①PHPコンテナが未起動/クラッシュ、②fastcgi_passのホスト名・ポートミスマッチ、③pm.max_childrenを超える同時接続、④PHP-FPMがsocketモードでnginxがTCPを指している(逆も)。対処:docker compose logs phpとdocker compose psでphpの状態確認。depends_on + healthcheckで起動順序を保証。
② 白画面(White Screen of Death)
# 一時的に display_errors を ON docker compose exec php sh -c "echo 'display_errors=On' > /usr/local/etc/php/conf.d/temp-errors.ini" docker compose restart php # PHP-FPMのエラーログを追跡 docker compose logs -f php # プロセス中で直接エラーを吐き出す docker compose exec php tail -f /proc/self/fd/2
③ PHPファイルがダウンロードされる
location ~ \.php$ブロックが一致していないため、Nginxが静的ファイルとして返す→ブラウザがダウンロード扱い。nginx.confの正規表現ミス(例:行末の$忘れ)をチェック。docker compose exec nginx nginx -tで構文チェック。
④ 504 Gateway Timeout
# ① nginx側 fastcgi_read_timeout 300s; # ② php.ini max_execution_time = 300 max_input_time = 300 # ③ php-fpm www.conf request_terminate_timeout = 300 # ④ フレームワーク側のタイムアウト設定
⑤ アップロードできない
本記事「アップロードサイズ4箇所の統一」を参照。もう1つの原因はopen_basedirの制限——アップロード先ディレクトリがopen_basedirの範囲外だと失敗します。
⑥ OPcacheがリロードされない
# opcache.validate_timestamps=0 の本番で docker compose exec php php -r "opcache_reset();" # または FPMプロセスをreload(graceful restart) docker compose exec php kill -USR2 1 # コンテナを再起動してもOK docker compose restart php
よくある質問
(利用可能RAM - OSやOPcacheの予約200MB程度) / 1プロセスの平均メモリ使用量で算出。1プロセスの実メモリはdocker compose exec php ps -o pid,rss axで確認。例:1GB RAMでプロセス50MB使用なら(1024-200)/50 ≈ 16→切り下げて14。詳細は本記事「PHP-FPMプロセス管理」セクション参照。validate_timestampsは開発/本番で何を変える?validate_timestamps = 1+revalidate_freq = 0(ファイル更新を即反映)。本番:validate_timestamps = 0(チェック無しで最速)。ただし本番デプロイ時はopcache_reset()かkill -USR2 1でFPMをgracefulリロードしてコードを反映させます。jit = disableで安全側に。新規開発から順次有効化するのが現実的。9000→9003に変わり、remote_*設定はclient_*に改名されました。Xdebug 3ではxdebug.mode = debug,developのようにモード指定が必須です。本記事「Xdebug 3」セクションのxdebug.iniをそのまま使えば動きます。docker compose exec php composer installで楽)、本番はmulti-stage Dockerfileでビルド時にインストール(vendor/を焼き込み)。CIならcomposer:2イメージを専用ジョブで走らせる手もあります。目的別に使い分けましょう。client_max_body_size/php.ini upload_max_filesize・post_max_size・memory_limit/php-fpm request_terminate_timeout)を全て同じ値に揃えてください。post_max_size > upload_max_filesize、memory_limit >= post_max_sizeの関係も重要。本記事「アップロードサイズ4箇所の統一」参照。fastcgi_pass php:9000;)推奨です。ベアメタル本番で最後の数%を絞り出す時以外は不要。expose_php = Off+fastcgi_hide_header X-Powered-Byでバージョン隠蔽、②server_tokens off;でNginxバージョンも隠す、③disable_functionsで危険関数無効化、④セッションcookieをhttponly=1; secure=1; samesite=Lax、⑤セキュリティヘッダー(CSP・X-Frame-Options・HSTS)。本記事「セキュリティ強化」セクション参照。関連記事
- 【Docker】Nginxローカル開発環境構築完全ガイド — Nginx 6用途別設定(SPA/リバプロ/WebSocket等)
- 【Docker】WordPress環境構築完全ガイド — WP特化のNginx+PHP-FPM運用
- 【Docker】MySQL CLI完全ガイド — PHP-FPMと連携するDB操作
- 【Docker】Composeで複数コンテナを連携させる方法 — サービス間通信の基礎
- 【Docker】volumeの使い方とデータ永続化の基本 — ソース・ログのマウント戦略
- 【Docker】bind mountとvolumeの違いと使い分け — 設定ファイル配置の判断
- 【保存版】Docker Desktopが「Docker Desktop stopped」で起動できない時の完全解決ガイド — Docker起動トラブル
- 【GitHub】Actions完全ガイド — PHPアプリのCI/CDビルド
まとめ
- Nginx + PHP-FPMは静的配信+PHP実行を責務分離した本番標準構成
- FastCGIプロトコルでNginxとPHP-FPM間をTCP/Unix socketで通信
- 推奨イメージ:
nginx:1.27-alpine+php:8.3-fpm-alpine - Dockerfileはmulti-stageで
composer:2を統合(本番向け) - nginx.confはフレームワーク別rewrite(Laravel/Symfony/WordPress/CodeIgniter)を使い分け
- PHP-FPMは
pm = dynamicが既定、pm.max_childrenをRAMから算出 - OPcache設定:開発は
validate_timestamps=1、本番は=0+opcache_resetデプロイフック - JITはCPUバウンドで効果絶大、Web I/O主体なら微増(
jit = tracing推奨) - Xdebug 3はポート9003、
client_host=host.docker.internalでVS Code連携 - アップロードサイズは4箇所(nginx/upload_max/post_max/memory_limit)を揃える
- セキュリティ:
expose_php=Off/セッションsecure/disable_functions/セキュリティヘッダー - DockerではTCP fastcgi_passがシンプルで推奨(Unix socketの恩恵は微小)
- 502/白画面/ダウンロード扱い/504/OPcacheリロードは典型トラブルで対処法が定石化
Nginx + PHP-FPMは本番PHPサーバーの事実上の標準です。本記事のcompose.yml/Dockerfile/nginx.conf/php.ini/www.confを土台にすれば、Laravel・Symfony・WordPress・CodeIgniteいずれのフレームワークでも短時間で本番相当のローカル環境が構築できます。さらにMySQL/Redis/キューを追加し、GitHub Actionsでイメージ自動ビルドに乗せれば、個人開発からチーム開発・本番運用まで一気通貫で設計できます。

