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

  1. 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.
  2. Official images and arm64 packages (#647, #653). Fits main.
  3. Getting-started page, migration guides and framework recipes (#645). Examples are done (#639). Fits main.
  4. Reload: safe reload, gateway drain, keep the listener (#640, #641, #661). FastCGI already drains; the gateway and broken configs are the gaps. Fits main.
  5. Readiness path and sd_notify (#646, #643). Fits main.
  6. Gateway access log, request id and histograms (#642, #652, #644), plus documenting that traceparent is already forwarded. Fits main.
  7. X-Sendfile and static Range, Cache-Control and sidecars (#649, #648; decision #662 first). Fits main.
  8. Zero-config dev server (new issue needed), plus an external file-watch recipe. Fits main.
  9. Laravel Octane driver, after items 1 and 8 (anti until then). Separate package.
  10. 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