Competitor Gap 2026 10 03 - crazy-goat/php-fpm-ng GitHub Wiki
Competitor gap analysis (2026-10-03): php-fpm-ng compared with Octane, RoadRunner, FrankenPHP, Swoole and others
Baseline:
- php-fpm-ng main at 96801bc (38faff8 + #713) and async at 80ff55d.
- Issues come from
gh issue list -R crazy-goat/php-fpm-ng --state all (404 issues) and the wiki page Feature-Ideas-2026-10-03 (anti-features), both read 2026-10-03.
Competitor versions: Laravel Octane v2.20.0, RoadRunner v2025.1.15, FrankenPHP v1.12.7, Swoole 6.2.3 (6.3.0-rc1 prerelease), OpenSwoole 26.2.0, symfony-swoole/swoole-bundle v0.35.3, Hyperf 3.2.4, Workerman 5.2.2, amphp/http-server 3.4.6, ReactPHP http 1.11.1, NGINX Unit 1.35.0 (archived), TrueAsync php-8.6.0-trueasync-0.10.0.
Not verified:
- I did not open the docs for: Swoole-bundle features, RR Centrifuge, Workerman, webman and Hyperf crontab.
- FrankenPHP has no PHP-side app-metrics API: I believe this is true, but did not check the source.
- Xdebug was not checked.
1-2. Gaps: what competitors have and main does not, grouped by category
Columns: async? (yes, no or partly; "main: partly" means main already covers part of it). Issue: "none" means no issue found; "anti" means an anti-feature on the wiki. Value / Size: H/M/L.
Runtime model and framework integration
| Feature |
Who has it |
async? |
Issue |
Value |
Size |
Framework adapter for worker mode: Symfony Runtime and PSR-7 on top of the existing fpmng_worker_* boot-once loop |
FrankenPHP (Symfony 7.4+ native), RoadRunner (spiral/roadrunner-http, baldinof bundle), Octane, swoole-bundle, php-pm |
main: loop exists, no adapter. async: fiber framework results, no adapter |
#660, #663 |
H |
M |
Reset of framework state between requests: sandbox, kernel.reset, listeners, warm and flush, GC threshold |
Octane (listeners, warm, flush, garbage), swoole-bundle, RR bundle |
partly (fiber isolates statics and ini) |
none (inside #660) |
H |
M |
| Laravel Octane driver |
Octane drivers for Swoole, RoadRunner, FrankenPHP |
no |
anti until worker leaves beta |
H |
M |
| Worker executor leaves beta. The README criterion points to closed #191, so a new criterion is needed |
All competitors ship worker mode as stable |
n/a |
none |
H |
M |
| Testing fakes for the worker lifecycle |
Octane Testing/Fakes |
no |
none |
L |
S |
| Transparent IO hooks for curl, pgsql and file IO |
Swoole runtime hooks, TrueAsync |
partly: tcp/unix/TLS/sleep/select/flock; curl, libpq and file IO block |
#81, #599, #600 |
M |
L |
| Many concurrent requests per process |
Swoole, Workerman, AMPHP, TrueAsync |
main: partly (worker executor plus userland Revolt/ReactPHP, examples only, beta). async: fiber, 128 requests per child |
async milestone |
M |
L |
| Built-in concurrent tasks and task-worker pool |
Octane concurrently on Swoole, Swoole task(), webman |
main: partly through amphp async() on the worker loop |
none |
L |
M |
| Thread or process autoscaling |
FrankenPHP max_threads auto, max_idle_time |
no |
none (fastcgi has dynamic/ondemand; http-direct is pm=static only) |
L |
M |
Process management and reload
| Feature |
Who has it |
async? |
Issue |
Value |
Size |
| Zero-downtime reload. Main: partly. FastCGI children drain on SIGUSR2, selective reload, supervisor rolling restart. Missing: gateway drain, keeping the gateway listener across exec, safe reload |
Octane octane:reload, RR rr reset, FrankenPHP admin API, Swoole SIGUSR1 |
no |
#640, #641, #661 |
H |
M |
Docroot follows a symlinked current release (a bug in our gateway: realpath cached once) |
FrankenPHP resolve_root_symlink (default on), nginx $realpath_root |
no |
#638 |
M |
S |
Crash backoff and max_consecutive_failures for request workers. Main has only the upstream emergency_restart_threshold (reload after SIGSEGV/SIGBUS) |
FrankenPHP (default 6) |
no (supervisor pools have backoff) |
none |
M |
S |
| Connect and read timeout toward the FastCGI upstream |
RR pool timeouts, nginx |
no |
#716 |
M |
S |
| Run a job once from the CLI with the pool's environment |
webman, Octane artisan |
no |
#655 |
M |
S |
HTTP features
| Feature |
Who has it |
async? |
Issue |
Value |
Size |
| X-Accel-Redirect / X-Sendfile |
FrankenPHP (Caddy intercept), RR sendfile |
no |
#649 |
M |
S |
| Static Range/206, Cache-Control, precompressed sidecars, per-pattern headers. Main already serves ETag/304/sendfile |
Caddy file_server, RR static, Octane static_file_headers, Swoole |
no |
#648, #662 |
M |
M |
| Response header directive (HSTS, CORS) and error pages |
RR headers (incl. CORS), Caddy |
no |
#650 (does not cover CORS) |
M |
S |
| Host-based routing |
Caddy, NGINX Unit |
no |
#658 |
M |
S |
RFC 7239 Forwarded header |
RR proxy_ip_parser, amphp ForwardedMiddleware |
no |
none (#629 covers only X_Forwarded_* on the route hop) |
L |
S |
| Compression on the fly |
Caddy, RR gzip, amphp CompressionMiddleware, Swoole |
no |
anti (zlib.output_compression and sidecars in #648) |
M |
M |
| Rate limiter. Main has a per-client connection cap |
Hyperf, Caddy (third-party module) |
no |
anti |
L |
M |
| Streamed or very large request bodies (gateway buffers the whole body; http-direct is capped at 32 MiB) |
FrankenPHP, RR, Swoole |
no |
decided against (task 031; spooling dropped in the wiki) |
L |
L |
Protocols
| Feature |
Who has it |
async? |
Issue |
Value |
Size |
| WebSocket through the gateway. Native WS and SSE already exist on the http-direct worker (#343, #342) |
Swoole, Workerman, Octane behind nginx, TrueAsync |
no |
#659 |
M |
M |
| Cross-worker push and broadcast |
Swoole push to any fd, FrankenPHP Mercure, RR Centrifuge |
no |
#191 not planned; hub is anti |
M |
L |
| HTTP/2 and HTTP/3 |
FrankenPHP, RR, Swoole, AMPHP, TrueAsync |
no |
anti (#69, #186-188) |
M |
L |
| PROXY protocol v1/v2 |
Caddy, HAProxy setups |
no |
#651 |
L |
S |
| gRPC, raw TCP/UDP |
RR, Swoole, Workerman, TrueAsync |
no |
anti |
L |
L |
Observability
| Feature |
Who has it |
async? |
Issue |
Value |
Size |
| Readiness endpoint (503 while starting, draining, optionally all targets dead) |
RR status plugin /ready |
no |
#646 |
M (target audience is not k8s) |
S |
systemd Type=notify and watchdog |
upstream FPM |
no |
#643 |
M |
S |
Gateway access log with duration, upstream time, JSON and request id (http-direct already has access.format %d) |
upstream FPM, RR, Caddy, Octane |
no |
#642 |
H |
S |
Request-duration histograms, listen queue, max_children_reached. Main has active/idle workers |
Caddy duration histograms, FrankenPHP queue_depth/busy/ready, RR http_metrics |
partly |
#652, #644 |
M |
S |
X-Request-Id generate and propagate. traceparent is already forwarded as an ordinary header (docs only) |
RR otel, Caddy |
no |
#642 |
M |
S |
| Structured log API from PHP |
FrankenPHP frankenphp_log() (1.11), RR app-logger |
no |
none |
L |
S |
| OPcache restart and loop-lag metrics |
FrankenPHP opcache_restarts, OpenSwoole |
no |
none |
L |
S |
| Live TUI dashboard |
rr workers; Ember (third-party, for Caddy/FrankenPHP) |
no |
none |
L |
M |
Deploy and DX
| Feature |
Who has it |
async? |
Issue |
Value |
Size |
| Official multi-arch container images |
FrankenPHP, RR, Swoole |
no |
#647 |
H |
S |
| arm64 packages |
FrankenPHP, RR, Swoole |
no |
#653 |
H |
M |
| Getting-started page, migration guides, framework recipes (examples fixed in #639/#713) |
FrankenPHP, Octane, RR docs |
no |
#645 (v0.14.0) |
H |
S |
| Zero-config dev server |
FrankenPHP php-server, Octane octane:start, php -S |
no |
none |
M |
S |
| Single-file app executable |
FrankenPHP embed, webman |
no |
#425, #429-431 (v0.15.0) |
M |
M |
| File watch and auto reload in development |
FrankenPHP watch and hot reload, Octane --watch, swoole-bundle, AMPHP |
no |
anti in the C master; an external watcher recipe is allowed |
M |
S |
| Trusted local dev certificates |
FrankenPHP trust |
no |
none |
L |
S |
| macOS and Windows. Ours: macOS from source for development only, Windows none |
FrankenPHP (Windows since 1.12.0), Swoole (Windows in 6.3.0-rc1) |
no |
none |
L |
L |
| Public benchmark |
FrankenPHP, RR |
no |
#656 |
M |
M |
Security, TLS and isolation
| Feature |
Who has it |
async? |
Issue |
Value |
Size |
| Automatic HTTPS as stable (ours is beta) |
FrankenPHP/Caddy, RR ACME |
no |
#254, #657 |
M |
M |
| Slow-client limits: response trickle on the gateway, and a body idle timeout |
FrankenPHP request_body_timeout |
no |
#705 |
M |
S |
| Namespaces and cgroups per app |
NGINX Unit (archived) |
no |
anti (cgroup management) |
L |
M |
| On-demand TLS, DNS-01, OCSP |
Caddy |
no |
anti |
L |
M |
Background jobs and shared state
| Feature |
Who has it |
async? |
Issue |
Value |
Size |
| Shared-memory tables and cache |
Swoole Table, Octane cache and tables, RR KV |
no |
anti (KV); APCu covers it, a docs note is enough |
L |
M |
| Queue brokers, Temporal, locks |
RR, Hyperf |
no |
anti |
L |
L |
| Sub-minute timers. Main: partly (worker executor timers, supervisor loops) |
Octane ticks, Swoole timers, Hyperf crontab |
partly |
none (cron has minute granularity) |
L |
S |
Left out as trivial or out of scope:
- Go plugin and extension SDKs, Velox, the embeddable Go library.
- The custom
Server header.
- Upload parsing in Go.
- ZTS thread mode.
- Arbitrary HTTP methods (libevent cannot do them;
docs/http-direct.md).
- A writable control API (anti).
3. Where php-fpm-ng is ahead or unique
- Drop-in FPM: an unchanged
index.php and FPM config run as before, and classic share-nothing is the default.
- Distribution libphp 8.5 NTS packages: no Go or Caddy, no ZTS, and the distribution's PHP security updates apply.
- Per-pool OS isolation:
user/group, chroot, apparmor_hat, rlimit and clear_env.
- First-class cron pools: timezone and DST aware, jitter,
expect_within staleness, history, stop signal.
- Supervisor pools: backoff and jitter, heartbeat staleness,
max_memory/max_runtime, rolling restart.
- Selective reload of only the changed pools.
- Pool-full policy
reject|wait: comparable to FrankenPHP max_wait_time. Ours adds a cap on the queue size and the X-Fpmng-Queue-Wait header.
- Application metrics from PHP:
fpm_metric_* in shared memory, also from the CLI.
- mTLS with the client certificate in PHP (
fpm_connection_info()), and ACME as a cron payload with one renewer for every gateway.
- One worker transport for many event loops: native WebSocket, SSE and streaming with backpressure, early hints,
fpmng_respond(). Unmodified Revolt/amphp and ReactPHP run on it. This is beta.
- Mixed model in one daemon: FastCGI, gateway, http-direct classic or worker, cron and supervisor.
4. Top 10 recommendations, in priority order
- Worker executor leave-beta pass. First replace the stale "#191" criterion in README. Then the Symfony Runtime / PSR-7 bridge with state-reset hooks (#660, #663). Fits main.
- Official images and arm64 packages (#647, #653). Fits main.
- Getting-started page, migration guides and framework recipes (#645). Examples are done (#639). Fits main.
- Reload: safe reload, gateway drain, keep the listener (#640, #641, #661). FastCGI already drains; the gateway and broken configs are the gaps. Fits main.
- Readiness path and
sd_notify (#646, #643). Fits main.
- Gateway access log, request id and histograms (#642, #652, #644), plus documenting that
traceparent is already forwarded. Fits main.
- X-Sendfile and static Range, Cache-Control and sidecars (#649, #648; decision #662 first). Fits main.
- Zero-config dev server (new issue needed), plus an external file-watch recipe. Fits main.
- Laravel Octane driver, after items 1 and 8 (anti until then). Separate package.
- Public benchmark (#656). Fits main.
Next in line:
- Crash backoff for request-worker children (new issue needed). It is small and fixes a confirmed tight respawn loop.
- WebSocket through the gateway (#659).
- Upstream timeouts (#716) and slow-client limits (#705).
- Fiber IO coverage for curl and pgsql (#81). It stays on async until the io_hooks RFC lands (#599, #600).
Sources: https://raw.githubusercontent.com/php/frankenphp/main/docs/config.md, https://raw.githubusercontent.com/php/frankenphp/main/docs/metrics.md, https://docs.roadrunner.dev/docs/http/http, https://docs.roadrunner.dev/docs/http/proxy, https://gittrend.io/repo/alexandre-daubois/ember, GitHub releases (gh api) for every project listed. Local files: /Users/piotr.halas/work/crazy-goat/php-fpm-ng/README.md, /Users/piotr.halas/work/crazy-goat/php-fpm-ng/docs/http-direct.md, /Users/piotr.halas/work/crazy-goat/php-fpm-ng/docs/reload.md, /Users/piotr.halas/work/crazy-goat/php-fpm-ng/sapi/fpmng/fpm/fpm_children.c, /Users/piotr.halas/work/crazy-goat/php-fpm-ng/sapi/fpmng/fpm/fpm_conf.c, /Users/piotr.halas/work/crazy-goat/php-fpm-ng/examples/http-direct-worker/README.md, /tmp/fpmng-wiki-ro/Feature-Ideas-2026-10-03.md, /tmp/fpmng-async-ro/sapi/fpmng/fpm/fpm_pool_type_coop.c