FrankenPHPは、Go製WebサーバーのCaddyにPHPを組み込んだアプリケーションサーバーです。Nginx+PHP-FPMの2段構成を1つのバイナリにまとめ、HTTPSの自動化やHTTP/3、アプリを常駐させるワーカーモードまで扱います。2025年5月からはPHP Foundationの公式プロジェクトです。
本記事は、2026年10月16日公開の最新版v1.13.1を前提に、PHP-FPMとの実行モデルの違い、導入と移行の手順、v1.13で変わった設定、Laravel・Symfony・WordPressでの動かし方、導入前に確認すべき制約を整理します。挙動の説明には、macOS版バイナリで取得した実際の出力を載せています。
まとめ:FrankenPHPで変わる点と導入前の確認事項
- FrankenPHPはCaddyとPHPを1つにまとめたアプリサーバーで、2025年5月15日からPHP Foundationが公式にサポートしています。
- 設定なしで起動するとクラシックモードになり、既存のPHPファイルをPHP-FPMと同じくリクエストごとに実行します。プロセスではなくスレッドでPHPを動かす点が根本的な違いです。
- ワーカーモードはアプリを1回だけ起動してメモリに常駐させます。速くなる代わりに、変数に加えて date_default_timezone_set() や ini_set() で変えた設定も次のリクエストへ持ち越されます。
- v1.13.0から、ワーカーを設定したときの num_threads はワーカー以外のスレッド数を意味します。max_threads が足りない設定は起動時にエラーで止まります。5件の脆弱性修正を含むため、上げるなら回帰を直したv1.13.1にします。
- LaravelはOctane経由、Symfonyは7.4以降ならネイティブでワーカーモードに対応します。WordPressの公式手順はクラシックモードでの起動です。
- imap・newrelic・pcovはスレッド非対応で使えません。New Relicで監視している案件と共有レンタルサーバーの案件は対象外です。
FrankenPHPとは:Caddyに組み込んだGo製のPHPアプリサーバー
FrankenPHPはKévin Dunglas氏が開発したPHPのアプリケーションサーバーです。PHPインタプリタをGoのプログラムから呼び出し、Caddyのモジュールとして動かします。HTTPの受付からPHPの実行までを1プロセスで完結させるため、Webサーバーと別にPHP-FPMを立てる必要がありません。
2025年5月にPHP Foundationの公式プロジェクトへ移管
PHP Foundationは2025年5月15日のブログ記事「FrankenPHP Is Now Officially Supported by The PHP Foundation」で、FrankenPHPを公式にサポートすると発表しました。ソースコードはPHPプロジェクトのGitHub組織へ移り、現在のリポジトリは github.com/php/frankenphp です。発表では、ガバナンスは変えず、Kévin Dunglas・Robert Landers・Alexander Stecherの3名が引き続き開発を担うと明記されています。
個人プロジェクトのまま消えるリスクを理由に採用を見送っていたなら、この移管が判断材料になります。ただしリリースはPHP本体とは別の、独自の版番号で出ています。
スタンドアロンバイナリに含まれるPHP 8.5とCaddy
公式が配布するバイナリには、PHP本体とCaddyが組み込まれています。macOS(x86_64)版のv1.13.1をダウンロードして確認すると、次の組み合わせでした。ファイルサイズは191,142,496バイト(約182MiB)です。
$ ./frankenphp-mac-x86_64 version
FrankenPHP v1.13.1 PHP 8.5.11 Caddy v2.11.7 ...
サーバーにPHPを別途入れなくても、このファイル1つでPHPアプリを配信できます。Linux向けのmusl版は完全静的リンクで、依存パッケージなしで動きます。GNU版はglibcに依存し、Alpineなどのmusl環境ではそのまま動きません。アプリのソースコードまでバイナリへ埋め込み、1ファイルで配布する機能もあります。
PHP-FPMとの違い:プロセス方式とスレッド方式
PHP-FPMはリクエストをPHPのワーカープロセスへ振り分け、プロセス単位で実行を分けます。FrankenPHPは1つのプロセスの中にPHPのスレッドを複数立て、そこでリクエストを処理します。そのため、スレッドセーフ版(ZTS)でビルドしたPHPが必要です。
| 項目 | Nginx+PHP-FPM | FrankenPHP(クラシック) | FrankenPHP(ワーカー) |
|---|---|---|---|
| 構成 | Nginxと常駐FPMの2つ | バイナリ1つ | バイナリ1つ |
| PHPの実行単位 | プロセス | スレッド | スレッド |
| アプリの起動 | リクエストごと | リクエストごと | ワーカー起動時の1回 |
| リクエスト間の状態 | 残らない | 残らない | 残る |
| HTTPS自動化・HTTP/3 | 別途設定 | 対応 | 対応 |
| 既存コードの改修 | 不要 | 不要 | 状態の後始末が必要 |
クラシックモードによるPHP-FPMの置き換え
何も設定せずに起動すると、FrankenPHPはクラシックモードで動きます。公式ドキュメントのクラシックモードの解説は、このモードをPHP-FPMやApacheのmod_phpの置き換えと位置づけています。PHPファイルはリクエストのたびに最初から実行されるため、既存コードは書き換え不要です。
リクエストのたびにstatic変数を1増やし、2回目でタイムゾーンを変えるスクリプトで確かめると、カウンタは毎回1に戻り、タイムゾーンもUTCのままでした。プロセスIDは3回とも同じです。PHP-FPMでも同じワーカープロセスが複数のリクエストを処理するので、PIDが同じという結果だけではスレッド方式かどうかは判別できません。
$ frankenphp php-server --root app --listen 127.0.0.1:18083
$ curl http://127.0.0.1:18083/index.php # 1回目
$ curl 'http://127.0.0.1:18083/index.php?tz=1' # 2回目でAsia/Tokyoに変更
$ curl http://127.0.0.1:18083/index.php # 3回目
classic hits=1 pid=3010 sapi=frankenphp tz=UTC
classic hits=1 pid=3010 sapi=frankenphp tz=UTC
classic hits=1 pid=3010 sapi=frankenphp tz=UTC
ワーカーモードで持ち越される変数とエンジン設定
ワーカーモードでは、各ワーカーが起動時にアプリを1回読み込み、frankenphp_handle_request() でリクエストを待ち続けます。フレームワークの起動処理を毎回やり直さないぶん速くなりますが、スクリプト内の変数はリクエストをまたいで残ります。
公式サンプルと同じ形のワーカースクリプトで、リクエスト数・キャッシュ配列の要素数・タイムゾーンを出力させました。ワーカーは1つで、スクリプト内で環境変数 MAX_REQUESTS=3 を読み、3リクエスト後に処理ループを抜けて再起動させる設定です。MAX_REQUESTSは、この読み取り処理を書いたスクリプトでだけ効きます。
<?php
// app/worker.php
$hits = 0;
$cache = [];
$handler = static function () use (&$hits, &$cache) {
$hits++;
$cache[] = str_repeat('x', 1024);
echo "worker hits=", $hits, " cache=", count($cache), " tz=", date_default_timezone_get(), "\n";
if (isset($_GET['tz'])) {
date_default_timezone_set('Asia/Tokyo');
}
};
$max = (int)($_SERVER['MAX_REQUESTS'] ?? 0);
for ($i = 0; !$max || $i < $max; ++$i) {
$keep = \frankenphp_handle_request($handler);
gc_collect_cycles();
if (!$keep) break;
}
$ MAX_REQUESTS=3 frankenphp php-server --root app \
--listen 127.0.0.1:18083 --worker app/worker.php,1
$ curl http://127.0.0.1:18083/worker.php # 1回目
$ curl 'http://127.0.0.1:18083/worker.php?tz=1' # 2回目でAsia/Tokyoに変更
$ curl http://127.0.0.1:18083/worker.php # 3〜5回目
worker hits=1 cache=1 tz=UTC
worker hits=2 cache=2 tz=UTC
worker hits=3 cache=3 tz=Asia/Tokyo
worker hits=1 cache=1 tz=UTC
worker hits=2 cache=2 tz=UTC
2回目のリクエストで変えたタイムゾーンが、3回目のリクエストにそのまま残りました。4回目でワーカーが再起動され、カウンタもタイムゾーンも初期値に戻っています。公式ドキュメントのワーカーモードの解説は、ini_set()・stream_context_set_default()・date_default_timezone_set()・chdir()・set_error_handler() などで変えた設定が後続のリクエストに残ると明記し、リクエストやユーザーごとの値をそこに入れないよう求めています。ユーザーごとにタイムゾーンを切り替えるアプリは、ワーカーモードでは変更前の値を保存し、例外が起きても通る finally ブロックで元に戻す処理が要ります。
配列に追記し続けるコードも、再起動されるまでメモリを解放しません。PHP本体の脆弱性でも、実行方式によって危険度が変わります。2026年10月に公表されたPHP2Shell(CVE-2026-109618・CVE-2026-109619)の影響と、mod_php・PHP-FPMなど実行方式ごとの見方は別記事で整理しています。ライブラリ側のリークに備えて、リクエスト数での再起動は残しておくのが安全です。同じサーバーでも index.php へのリクエストはクラシックモードで処理されました。
Swoole・RoadRunnerとの位置づけ
PHPを常駐させる手段には、FrankenPHPのほかにSwooleとRoadRunnerがあります。SwooleはpeclでインストールするPHP拡張で、コルーチンによる非同期処理まで扱えます。RoadRunnerはGo製のサーバーがPHPのワーカープロセスを外から管理する方式です。FrankenPHPはPHP拡張を足さずに導入でき、常駐させないクラシックモードから始めてワーカーモードへ段階的に移れます。非同期処理を書きたいなら、PHPの非同期処理とFibers・ReactPHP・AMPHP・Swooleの違いで整理しているSwooleの方が向いています。
インストールと起動の手順
インストールスクリプト・Homebrew・Dockerイメージの選び方
LinuxとmacOSではインストールスクリプト、WindowsではPowerShellのスクリプトが用意されています。Homebrew(macOS・Linux)とDockerイメージでも入手できます。
# Linux / macOS
curl https://frankenphp.dev/install.sh | sh
# Windows(PowerShell)
irm https://frankenphp.dev/install.ps1 | iex
# Homebrew
brew install dunglas/frankenphp/frankenphp
# Docker(カレントディレクトリを公開ディレクトリとして配信)
docker run -v .:/app/public -p 80:80 -p 443:443 -p 443:443/udp dunglas/frankenphp
Dockerイメージのタグは dunglas/frankenphp:<FrankenPHPの版>-php<PHPの版>-<OS> の形で、OSは trixie・bookworm・alpine から選びます。本番ではDebian系を選んでください。公式のパフォーマンス解説は、ZTS版のPHPがmusl libcで遅くなりやすく、muslでだけ起きる不具合もあるとして、本番ではglibc版を推奨しています。拡張の追加はDockerfile内で install-php-extensions コマンドを使います。
php-serverによる既存PHPアプリの配信
いちばん手早いのは、公開ディレクトリで php-server サブコマンドを実行する方法です。設定ファイルは要りません。
# カレントディレクトリを配信
frankenphp php-server
# ドキュメントルートと待ち受けアドレスを指定
frankenphp php-server --root public --listen 127.0.0.1:8080
# ドメインを指定するとHTTPSで待ち受け、証明書を自動取得
frankenphp php-server --domain example.com
# ワーカーモードで起動し、PHPファイルの変更で再起動
frankenphp php-server --worker public/index.php --watch
php-serverは既定ではHTTPで待ち受け、--domain を指定すると待ち受けがHTTPSのポートに変わります。HTTP/2とHTTP/3はTLSが前提で、--listen でHTTPのまま起動したログには「HTTP/2 skipped because it requires TLS」と「HTTP/3 skipped because it requires TLS」が出ました。HTTP/3を試すなら --domain を付けてHTTPSで起動してください。PHPスクリプトをコマンドラインで動かすときは frankenphp php-cli script.php を使います。なお、v1.13.1のmacOS版(PHP 8.5.11)で php-cli -v を実行すると、PHP 8.6未満では使えない機能だというメッセージが返りました。同梱PHPの版は frankenphp version で確認します。
Caddyfileのスレッド数とワーカー設定
本番では frankenphp run でCaddyfileを読み込んで起動します。スレッド数やワーカーは、グローバルオプションの frankenphp ブロックで指定します。
{
frankenphp {
num_threads 8
max_threads auto
max_wait_time 10s
worker {
file /app/public/index.php
num 4
watch /app/src
}
}
}
example.com {
root /app/public
encode zstd br gzip
php_server
}
v1.13.0以降、この設定で起動するスレッドは、ワーカー以外の8本にワーカーの4本を足した12本です。num_threads を省略した場合は、論理CPU数の2倍からワーカーの分を差し引いた残り(最低1本)が通常スレッドになり、ワーカーが多すぎない限り合計はCPU数の2倍です。論理CPUが16個のMacでワーカー1つの php-server を起動したログは、total_threads が32、worker_threads が1でした。公式は既定値のまま使わず、max_threads×memory_limit が利用可能なメモリを下回るよう調整するよう勧めています。
max_wait_time は、空きスレッドを待つリクエストをタイムアウトさせる設定です。既定では無効で、スレッドが埋まるとリクエストは無期限に待たされます。FrankenPHPの前段にNginxやロードバランサーを置く場合は、公式の本番デプロイ手順のとおり、Caddyfileで trusted_proxies を、フレームワーク側でも信頼するプロキシを設定します。どちらかが欠けるとX-Forwarded-Forなどが無視され、HTTPSの判定やクライアントIPが誤ります。
開発時のホットリロードの有効化
v1.11.0(2025年12月19日公開)で、ファイルの変更を検知してブラウザを再読み込みさせるホットリロードが入りました。Mercureを有効にし、php_server に hot_reload を書きます。ワーカーモードと組み合わせる場合は、worker の watch でワーカーを再起動させ、hot_reload でブラウザを更新させる分担です。
localhost
mercure {
anonymous
}
php_server {
hot_reload
}
既定の監視対象は、カレントディレクトリ配下のphp・twig・yaml・js・cssなどの拡張子です。ページ側には、$_SERVER['FRANKENPHP_HOT_RELOAD'] が設定されているときだけ読み込むJavaScriptを足します。公式のホットリロード解説は、内部情報を露出し動作も遅くなるため本番で有効にしないよう警告しています。開発用のCaddyfileと本番用を分けておくのが確実です。
Nginx+PHP-FPMから移行する手順
公式の移行ガイド(Migrating from Nginx/PHP-FPM)は、既存の設定を次のように置き換えるよう示しています。php.iniはそのまま使えます。
| Nginx+PHP-FPMの設定 | FrankenPHPでの置き換え先 |
|---|---|
| Nginxの server ブロック | Caddyfileのサイトブロック |
| php-fpm.conf のプール設定 | グローバルオプション frankenphp |
| php_value / php_admin_value | php_ini ディレクティブ |
| pm = static と pm.max_children | num_threads |
| pm = dynamic | max_threads auto |
| Webサーバー側のリクエスト拒否 | Caddyのルートとマッチャー |
見落としやすいのが最後の行です。Nginxなどが既定で弾いていたリクエストは、FrankenPHPではPHPアプリまで届くことがあります。移行ガイドはTRACEメソッドを例に、php_server の前で明示的に拒否するよう勧めています。
example.com {
@trace method TRACE
respond @trace 405
root /var/www/app/public
php_server
}
移行はクラシックモードで置き換えて動作を確かめ、そのあと必要ならワーカーモードへ進む順番にします。Nginxの役割とTLS終端までFrankenPHPへ移す構成なら、Nginx、php-fpmのサービス、FastCGIの設定、自前で管理していたTLS証明書が不要になります。前段にNginxやロードバランサーを残す場合は、インストール手順の章で触れた trusted_proxies の設定が必要です。
v1.13へ上げる前に確認する変更点
2026年10月4日公開のv1.13.0は、Caddyを2.11.4から2.11.7へ上げ、5件の脆弱性を修正した大きな版です。v1.13.0のリリースノートはアップグレードを強く勧めつつ、先に移行上の注意を読むよう求めています。10月16日公開のv1.13.1は、Nextcloudなどが起動時に REQUEST_URI を読むとワーカーが起動できない不具合や、php-cli の $argv がずれてSymfony Consoleが動かない不具合など、1.13.0の回帰を直しています。上げるなら1.13.1にしてください。
num_threadsの数え方とmax_threads不足の起動エラー
v1.13.0から、ワーカーを宣言した設定の num_threads は「ワーカーが処理しないリクエスト用のスレッド数」になり、ワーカーのスレッドはその上に足されます。v1.12系と同じ値のままだと、起動するスレッドが増えます。num_threads を3、ワーカーを2にした設定の起動ログは次のとおりでした。
{"logger":"frankenphp","msg":"FrankenPHP started","php_version":"8.5.11",
"total_threads":5,"worker_threads":2,"max_threads":5,"max_requests":0}
同じ設定に max_threads 4 を足すと、起動が終了コード1で止まりました。エラーには3つの数が並ぶので、どこを直すかはすぐ分かります。
frankenphp: invalid configuration: max_threads (4) must be greater than or equal to
num_threads (3) plus the worker threads (2)
PHP-FPMの pm.max_children に合わせて max_threads を絞っている環境は、v1.13へ上げる前に num_threads・ワーカー数・max_threads の3つを計算し直します。
設定の再読み込みが失敗時に旧設定を残す挙動
v1.13.0では、設定を再読み込みする前に中身を検証するようになりました。存在しないワーカーファイルを指定したCaddyfileで frankenphp reload を実行すると、管理APIがHTTP 400を返して再読み込みは拒否され、その後のリクエストは旧設定のまま200で応答しました。リリースノートによると、以前は不正な設定が稼働中の設定を置き換え、サーバーが500を返し続けることがありました。
$ frankenphp reload --config Caddyfile.missing --address localhost:2099
... caddy responded with error: HTTP 400: ... worker filename is invalid
"./public/missing.php": lstat public/missing.php: no such file or directory
$ curl -s -o /dev/null -w "%{http_code}\n" localhost:18091/index.php
200
Caddy 2.11.7のヘッダー制限と5件の脆弱性修正
Caddy 2.11.7では、リクエストヘッダーが既定で16KiBまでになり、読み書きが1分止まった接続は切断されます。名前に「.」を含むヘッダーも、「_」を含むヘッダーと同じく捨てられるようになりました。PHPは Foo.Bar を Foo-Bar と同じ HTTP_FOO_BAR に変換するため、ヘッダーのなりすましに使えたからです。Foo.Bar と Foo-Dash を付けたリクエストを送ると、PHP側に届いたのは Foo-Dash だけでした。
$ curl -H 'Foo.Bar: dot' -H 'Foo-Dash: dash' localhost:8091/index.php
pid=2318 tz=UTC foo_bar=(none) foo_dash=dash
大きなCookieやJWTをヘッダーで受けるアプリは max_header_size で上限を上げ、どうしてもドット入りのヘッダーが必要なら expected_dot_headers で個別に残します。修正された脆弱性のうち重要度Highは、WindowsでのSCRIPT_FILENAMEの解決でドキュメントルートの外へ出られる問題と、このドット入りヘッダーによるなりすましの2件です。ほかに、/uploads/a.php.txt/b.php のようなパスで uploads/a.php が実行される問題、putenv() の値がリクエストやスレッドをまたいで漏れる問題も直っています。Mercureを使っている場合は、publisher_jwt や subscriber_jwt を protocol_version_compatibility なしで書いた設定がエラーになる点も確認してください。
フレームワーク別の動かし方
Laravel:Octane経由のワーカーモード起動
LaravelをクラシックモードでFrankenPHPに載せる公式手順では、プロジェクト直下のCaddyfileで root を public/ に向け、php_server に try_files {path} index.php を指定して frankenphp run で起動します。ワーカーモードはLaravel Octaneを経由するのが公式の手順です。PHPを別途入れずスタンドアロンバイナリだけで動かす場合は、PATH="$PWD:$PATH" frankenphp php-cli artisan octane:frankenphp の形で起動します。
composer require laravel/octane
php artisan octane:install --server=frankenphp
php artisan octane:frankenphp --workers=4 --max-requests=500
octane:frankenphp の --workers の既定値はauto、--max-requests は500です。--https を付けるとHTTPS・HTTP/2・HTTP/3と証明書の自動更新が有効になります。Octaneではサービスコンテナへの注入やstaticプロパティへの追記がリクエストをまたいで残るため、既存アプリを載せる前に点検が必要です。点検箇所とワーカー数の決め方はLaravel Octaneのワーカー常駐の仕組みと状態リークで詳しく扱っています。
composer.json のスクリプトにある @php artisan package:discover が失敗することもあります。ComposerがFrankenPHPのバイナリの呼び方を知らず、-d オプションも渡すためです。PHPを別途入れていない環境では、-d を取り除いて frankenphp php-cli を呼ぶラッパースクリプトを作り、PHP_BINARY 環境変数でそのパスを指定します。
Symfony:7.4以降はネイティブ対応、それ以前はRuntimeパッケージ
Symfony 7.4以降はFrankenPHPのワーカーモードにネイティブ対応しています。7.3以前では runtime/frankenphp-symfony パッケージを入れ、APP_RUNTIME 環境変数でFrankenPHP用のRuntimeを指定します。
composer require runtime/frankenphp-symfony
docker run \
-e FRANKENPHP_CONFIG="worker ./public/index.php" \
-e APP_RUNTIME=Runtime\\FrankenPhpSymfony\\Runtime \
-v $PWD:/app -p 80:80 -p 443:443 -p 443:443/udp \
dunglas/frankenphp
リクエスト固有の状態を持つサービスは ResetInterface を実装してリセットさせます。v1.13.1の静的ビルドには、symfony/var-exporter 8.1以降のキャッシュ生成を速くする deepclone 拡張が標準で入り、macOS版の拡張一覧にも含まれていました。
WordPress:展開先ディレクトリからのphp-server起動
公式のWordPress手順は、ダウンロードしたZIPを展開したディレクトリで frankenphp php-server を実行し、/wp-admin/ からインストールを進めるだけです。本番向けには、ドメイン・php_server・圧縮・ログの4行を書いたCaddyfileを frankenphp run で読み込む構成が示されています。
example.com
php_server
encode zstd br gzip
log
WordPressの手順にワーカーモードは載っておらず、載っているのはクラシックモードと、テーマの functions.php にスクリプトを足すホットリロードです。プラグインが書き換えるグローバル変数を次のリクエストへ持ち越すと不具合の原因になるため、WordPressではクラシックモードでHTTPS自動化やHTTP/3、Zstandard圧縮の恩恵を受ける使い方が現実的です。WordPress本体の最新版はWordPress 7.0「Armstrong」の新機能・変更点と安全なアップデート手順で扱っています。
導入前に確認する制約と既知の問題
スレッド非対応の拡張(imap・newrelic・pcov)とimagick
公式ドキュメントの既知の問題(Known issues)では、imap・newrelic・pcovの3つがスレッドセーフでないため非対応です。imapには webklex/php-imap などの代替があり、pcovはFrankenPHPの外でCLIからテストを走らせてカバレッジを取るよう案内されています。New Relicの代替は示されていません。APMにNew Relicを使っている案件は、この時点で見送る理由になります。
imagickはImageMagickのOpenMPスレッドがFrankenPHPのスレッドと衝突してクラッシュすることがあり、ImageMagick側のスレッド数を1に制限して回避します。静的バイナリと公式のapt・apk・rpmパッケージはOpenMPを無効化済みで、影響を受けるのはDockerイメージとHomebrewでの導入です。DatadogのプロファイリングとBlackfireも、それぞれ不安定・ベータ扱いです。
公式バイナリに無い拡張を足す方法は、バイナリの種類で変わります。muslの完全静的バイナリは拡張を後から読み込めず、ビルド時に組み込むしかありません。glibc版とmacOS版は読み込めますが、拡張もZTS版でビルドする必要があります。パッケージマネージャーの多くはZTS版の拡張を配布していないため、XdebugのPHPデバッグの使い方・インストール手順のような拡張も自前でビルドすることになります。拡張が多い案件ではDockerイメージの install-php-extensions を使う方が手間がかかりません。
Windowsのネイティブ対応とv1.12.0以降の導入
2026年2月24日公開のv1.11.3までは、リリースに含まれるバイナリはLinux版とmacOS版だけでした。2026年3月6日公開のv1.12.0でWindowsにネイティブ対応し、ワーカーモードとホットリロードもWindowsで使えます。Windows向けのアーカイブには、PHP公式のWindows用バイナリが同梱されています。Windows固有のHigh脆弱性がv1.13.0で直っているため、v1.12系のまま運用しないでください。
WinSWでWindowsサービスとして登録した場合、Windowsのサービス管理からは設定を再読み込みできません。設定ファイルのあるディレクトリで .\frankenphp.exe reload --config Caddyfile を実行すると、サービスを再起動せずに設定を反映できます。
AlpineイメージとGLOB_BRACE
完全静的バイナリとAlpineベースのイメージはmusl libcを使っているため、glob() の GLOB_BRACE フラグが使えません。設定ファイルの読み込みにGLOB_BRACEを使うライブラリで問題が出たら、GNU版の静的バイナリかDebianベースのイメージに切り替えます。
FrankenPHPを採用する案件と見送る案件の線引き
採用を勧めるのは、Dockerでアプリを配布し、Nginxの設定とPHP-FPMのチューニングを二重に抱えているLaravel・Symfonyの案件です。まずクラシックモードで置き換え、構成が1つに減ることとHTTPSの自動化を確かめてからワーカーモードへ進むと、状態の持ち越しによる不具合を切り分けやすくなります。既存のPHPシステムをこうした構成へ作り直す場合は、フルスクラッチでのシステム開発として設計から相談を受けています。
見送るべきなのは次の案件です。
- New Relicでアプリを監視しており、APMを変える予定が無い
- imap拡張に依存したメール処理があり、代替ライブラリへ移す工数を取れない
- 共有レンタルサーバーで運用しており、常駐プロセスやバイナリを置けない
- 速度の不満がDBクエリや外部APIの待ち時間から来ている
最後の条件は見落とされがちです。ワーカーモードで短くなるのはフレームワークの起動時間で、遅いSQLは遅いままです。導入前にリクエストの処理時間の内訳を計測し、起動処理が大きな割合を占めていることを確かめてください。起動時間の短縮だけが目的なら、OPcacheの設定と確認方法の見直しとプリロードで足りる場合もあります。
運用に入ったら、公式のメトリクス解説に沿ってCaddyのメトリクスを有効にし、通常リクエストの待ち行列を frankenphp_queue_depth、ワーカーの待ち行列と処理状況を frankenphp_worker_queue_depth と frankenphp_busy_workers で見ます。frankenphp_busy_threads には待機中の常駐ワーカーが占有するスレッドも含まれます。frankenphp_worker_crashes が増えていればワーカースクリプトが異常終了しています。v1.13.0で増えた試験的な frankenphp_opcache_restarts は、ZTSでは安全でないOPcacheの共有メモリ再起動を数えるもので、0のままが正常です。Prometheusでの収集はPrometheusで監視を構築する手順で扱っています。
よくある質問
FrankenPHPとPHP-FPMの違いは何ですか?
PHP-FPMはプロセス単位でPHPを実行し、Nginxなどと組み合わせて使います。FrankenPHPはCaddyと一体になったバイナリで、1つのプロセス内のスレッドでPHPを実行します。HTTPSの自動化やHTTP/3に対応し、アプリを常駐させるワーカーモードも使えます。
FrankenPHPの最新バージョンはいくつですか?
2026年10月18日時点の最新版は、10月16日公開のv1.13.1です。同梱のPHPは8.5.11、Caddyは2.11.7でした。v1.13.0で5件の脆弱性が修正されているため、v1.12系以前を使っている場合はv1.13.1へ上げてください。最新の版はGitHubのリリースページで確認できます。
WordPressはFrankenPHPで動きますか?
動きます。WordPressを展開したディレクトリで frankenphp php-server を実行すると、そのままインストール画面に進めます。公式ドキュメントのWordPress手順はクラシックモードでの起動で、ワーカーモードの手順は載っていません。
FrankenPHPはWindowsで使えますか?
使えます。2026年3月6日公開のv1.12.0からWindowsにネイティブ対応し、PowerShellのインストールスクリプトかリリースページのZIPで導入できます。ワーカーモードとホットリロードもWindowsで動作します。
FrankenPHPの前にNginxを置くことはできますか?
置けます。その場合はCaddyfileのグローバルオプションで trusted_proxies を設定し、LaravelやSymfony側でも信頼するプロキシを設定してください。片方だけではX-Forwarded-Forなどが無視され、クライアントIPやHTTPSの判定が誤ります。