Copied from wiki.php.net, last synchronized Oct 13, 2026 11:49 UTC
Today, the php and php-fpm executables contain the same engine and the same extensions. Only a small part of the code (the CLI and FPM SAPIs themselves) differs. Distributions that need both ship that code twice, and when FPM starts right after a CLI command, it loads that code a second time from a different file.
This RFC proposes to link the CLI SAPI into the php-fpm executable, and to make php-fpm run as the CLI when it is invoked under the name php. One binary can then run both the CLI and FPM:
php-fpm --nodaemonize -y /etc/php-fpm.conf # FPM, as today
ln -s php-fpm php
./php script.php # CLI
The php executable is not changed. Distributions that keep shipping two executables are not affected. Those that want a single one can install the php-fpm executable as php, with php-fpm as a symlink to it, so that all the PHP code is shipped once and loaded once.
A single executable brings two separate benefits: smaller PHP distributions, and a faster FPM start when FPM follows a CLI command. Both were measured with Bref, which runs PHP on AWS Lambda, by comparing two builds of its PHP runtime (called “layer” in Lambda):
php and php-fpm executables;php, and php-fpm replaced by a symlink to it. Bref itself was not modified: it starts php-fpm as before.Setup: PHP 8.5.11, Linux x86-64, Bref 3.0.12, October 2026.
| Two executables | Single executable | |
|---|---|---|
php |
25,985,096 bytes | 25,986,576 bytes |
php-fpm |
25,985,744 bytes | symlink |
| Layer, zipped | 24.3 MB | 19.9 MB (-18%) |
| Layer, unzipped | 89.9 MB | 64.5 MB (-28%) |
The combined executable is barely larger than either separate one: the second SAPI adds about 140 KB of code and data (+0.6%), which fits in the padding the linker adds to align segments. So the second executable's 26 MB are saved almost entirely.
This matters wherever space is limited and both SAPIs are needed. For example, Lambda caps a function and its layers at 250 MB unzipped, and container images, small devices and self-contained (statically linked) PHP distributions all ship both executables today.
On every cold start, Bref's runtime for web applications runs PHP twice: the CLI executes Bref's bootstrap script, which then starts FPM to serve requests. With two executables, FPM's start has to load its own copy of the engine, even though the bootstrap has just loaded the same code from php. With a single executable, php-fpm is the same file, already loaded.
Cold start durations, median of 50 cold starts per function, 1024 MB of memory, eu-west-3 region, with the 95% confidence interval of the difference:
| Application | Two executables | Single executable | Difference |
|---|---|---|---|
| Minimal application, FPM | 280 ms | 211 ms | -70 ms (-25%) [-79, -59] |
| Minimal application, without FPM | 195 ms | 193 ms | no difference [-6, +6] |
Starting faster is critical on Lambda, but it helps any platform that starts PHP on demand to absorb load: autoscaled containers, scale-to-zero services, serverless platforms. The same pattern happens there whenever a CLI command runs right before FPM starts, for example a container entry point that warms a cache or runs migrations. How much it saves depends on how costly it is to read the executable: some platforms load container images lazily like Lambda does, while on a host where the image is already on a local disk, the gain will be smaller. These measurements only cover Lambda.
When both the CLI and FPM SAPIs are built, the CLI SAPI is also linked into the php-fpm executable. There is no new configure option:
| Configuration | Result |
|---|---|
| CLI and FPM enabled | A php executable, unchanged, and a php-fpm executable that can also run the CLI |
FPM enabled, CLI disabled (--disable-cli) |
A php-fpm executable, unchanged |
| FPM disabled (the default) | Not affected |
An option would only be useful if linking the CLI into php-fpm had a cost. It has none that we found: the CLI has no library dependencies of its own, so php-fpm only grows by the CLI code (about 150 KB in a minimal build), and its behavior under its own name is unchanged.
make install installs php and php-fpm as two separate executables, as today.
php-fpm runs as the CLI when it is invoked under the name php, optionally followed by a digit, for example through a symlink (or a hard link, or a copy). All arguments are the CLI's usual arguments. Under any other name, it is FPM, unchanged:
php-fpm -v # PHP 8.7.0 (fpm-fcgi)
php-fpm8.7 --nodaemonize # FPM
ln -s php-fpm php
php -v # PHP 8.7.0 (cli)
php -S localhost:8000 # the built-in web server
ln -s php-fpm php8.7
php8.7 script.php # CLI
The rule covers the names used by distributions for the CLI (for example php8.3 on Debian or php83 on Alpine), while leaving out other programs whose name starts with php: php-cgi, php-config, php-fpm, phpdbg, phpize. A name such as php-8.7 (from --program-suffix=-8.7) is not recognized, because it can't be told apart from those programs.
In CLI mode, the executable behaves exactly like the php executable: PHP_SAPI is cli, it reads the same php-cli.ini and accepts the same options. The whole CLI test suite (sapi/cli/tests) passes when run through a php symlink to php-fpm. There is no way to switch from one SAPI to the other in a running process.
PHP_BINARY is the real path of the executable: symlinks are resolved. Scripts that run PHP_BINARY to start another PHP process, such as Composer restarting itself or PHPUnit's process isolation, expect it to run the CLI. A distribution that ships a single executable should therefore install it as php, with php-fpm as a symlink to it:
/usr/bin/php # the php-fpm executable
/usr/sbin/php-fpm # symlink to /usr/bin/php
With this layout:
PHP_BINARY is /usr/bin/php, which runs the CLI, as today.PHP_BINARY is /usr/bin/php too, where it is /usr/sbin/php-fpm today. Running it now starts the CLI, which is more useful than starting FPM.php-fpm, so service definitions, container entry points and scripts that call php-fpm keep working. Process titles (php-fpm: master process (/etc/php-fpm.conf), php-fpm: pool www) are unchanged, and graceful reload (SIGUSR2) works: FPM re-executes its original command line, through the php-fpm symlink./proc/<pid>/exe) see php instead of php-fpm for FPM processes.The opposite layout, php as a symlink to php-fpm, works for the CLI itself, but PHP_BINARY is then php-fpm, and scripts that run it would start FPM. This is documented in UPGRADING.
None for builds that don't include both SAPIs.
When both SAPIs are built:
php-fpm invoked under a name such as php or php8.7 runs the CLI. Before, it ran FPM under any name.php-fpm executable is slightly larger (about 150 KB in a minimal build).apache_request_headers() is renamed from zif_apache_request_headers to zif_fpm_request_headers, because the CLI defines a function with the same C name. The PHP functions apache_request_headers() and getallheaders() are unchanged.Internal changes, listed in UPGRADING.INTERNALS: when the CLI is built, PHP_FPM_OBJS includes PHP_CLI_SHARED_OBJS, and PHP_FPM_WITH_CLI is defined.
PHP 8.7.
php executable is unchanged.php name, as described above. Nothing changes otherwise.None. Extensions are the same in both modes, and extensions that check the SAPI name keep seeing cli or fpm-fcgi.
Nothing changes unless distributors choose to:
php and php-fpm as separate executables, as today. The only difference is that php-fpm contains the CLI.php, with php-fpm as a symlink to it. An FPM package would then contain the symlink, configuration and service files, and depend on the package that provides php.A combined executable uses one set of build options and extensions for both modes. Distributions that build the CLI and FPM with different configure options, in separate builds, can keep doing so.
The first version of this RFC proposed the opposite: a --enable-cli-fpm configure option that linked FPM into the php executable, which then ran FPM with php --fpm or when invoked under a php-fpm name (PR #23558). It was changed following the discussion on internals:
php executable, which almost every PHP installation uses, for a feature only some need: it added a --fpm option to the CLI, and FPM's optional library dependencies (systemd, ACL, AppArmor, SELinux) to the php executable.php-fpm could run the CLI with an option, for example php-fpm --cli script.php. It is not proposed: FPM's argument parsing stays untouched (some FPM and CLI options use the same letters, such as -F), the CLI receives its arguments unchanged, and a single executable needs to be installed as php for PHP_BINARY anyway.
PHP could build the engine as a shared library (libphp.so) and make php and php-fpm small executables linked to it, as PHP does on Windows with php8.dll. Both executables would keep their names, and the shared code would be on disk once.
This RFC doesn't follow that approach:
A shared library would also avoid loading the engine twice, since both executables would map the same file. The two approaches are not exclusive.
None.
make install to install the single executable layout (php and a php-fpm symlink).Primary vote, requiring a 2/3 majority:
Vote: Link the CLI SAPI into php-fpm, so that it runs as the CLI when invoked as php, as outlined in the RFC? — Yes: 0 · No: 0 · Abstain: 0 (closed)
sapi/fpm/tests/main-cli.phpt checks that php-fpm runs as the CLI under the names php and php8, and as FPM under other names. It is skipped when PHP is built without the CLI.php symlink to php-fpm.To be completed after the vote.
php executable, with a --enable-cli-fpm option and a php --fpm option (version 0.1). See Alternatives considered.php-fpm instead of FPM into php, following the discussion. No configure option, no --fpm option, and a single vote.