FrankenPHP vs PHP-FPM in 2026: Benchmarks and a Verdict

FrankenPHP worker mode delivers 3-4x PHP-FPM throughput, but classic mode is a tie. Real benchmarks, the gotchas, and when switching actually pays off.

TL;DR

  • What: FrankenPHP vs PHP-FPM compared with real 2026 benchmarks across classic and worker modes.
  • Why it matters: Worker mode can replace 8 PHP-FPM servers with 3, but migration has real operational costs.
  • What to do: High-traffic stateless API: switch to worker mode. Typical CRUD app: stay on PHP-FPM.
  • Key stat: Classic mode ties PHP-FPM at ~18,400 req/s; worker mode delivers 3-4x that throughput.

FrankenPHP beats PHP-FPM only in worker mode, where keeping the app booted in memory delivers 3-4x higher throughput and cuts Laravel boot time from 40-60ms to 4-6ms per request. In classic mode both serve roughly 18,400 requests per second, a 0.4% difference. Switch for high-traffic stateless APIs; keep PHP-FPM for typical CRUD apps.

FrankenPHP is a PHP application server written in Go and built on Caddy that embeds the PHP interpreter directly in the web server process. PHP-FPM (FastCGI Process Manager) is the traditional PHP process manager that runs behind Nginx or Apache and rebuilds the entire application state on every request. The architectural difference between the two is whether your framework boots once (FrankenPHP worker mode) or on every single request (PHP-FPM).

The FrankenPHP vs PHP-FPM debate has become the most common infrastructure question I get from PHP teams in 2026, and most articles ranking for it are hype or hello-world benchmarks. I have run both stacks in production — PHP-FPM for over a decade, FrankenPHP worker mode on two client projects since late 2025 — and the honest answer is more nuanced than either camp admits. The benchmark gap is real, but it only exists in one specific mode, and that mode changes assumptions your codebase has silently relied on since the day it was written.

What is FrankenPHP and how is it different from PHP-FPM?

FrankenPHP is a single Go binary that serves HTTP and executes PHP in the same process, while PHP-FPM is a separate process pool that receives requests from Nginx over FastCGI. That handoff is the core architectural difference: with PHP-FPM, Nginx parses the request, serializes it over a socket, and a worker process boots your framework from zero. FrankenPHP, built on Caddy, eliminates the socket hop and adds HTTP/2, HTTP/3, automatic HTTPS, and Early Hints support that PHP-FPM setups need extra configuration to match.

As of early 2026, FrankenPHP sits at version 1.11.x with PHP 8.5 support and PHP Foundation backing — no longer a one-person experiment. It runs in two distinct modes, and conflating them is where most comparison articles go wrong. Classic mode behaves like PHP-FPM: fresh state per request. Worker mode keeps your booted application resident in memory and replays requests against it.

My recommendation: evaluate the two modes as separate products, because their performance profiles and risk profiles have almost nothing in common.

Is FrankenPHP faster than PHP-FPM? The real benchmarks

In classic mode, no — and anyone claiming otherwise is benchmarking incorrectly. Tideways ran the cleanest comparison I have seen: FrankenPHP classic mode and an Nginx + PHP-FPM stack both served approximately 18,400 requests per second on identical hardware, a 0.4% difference that is statistical noise. If you migrate to FrankenPHP and stay in classic mode, you are trading a battle-tested stack for operational simplicity, not speed.

Worker mode is a different story. Benchmarks across multiple independent sources in 2026 consistently show 3-4x higher throughput than PHP-FPM for framework-based applications. A Symfony 7.4 benchmark published on dev.to measured response times dropping from 45ms on PHP-FPM to 8ms on FrankenPHP worker mode. For Laravel, the framework bootstrap that costs 40-60ms per request under PHP-FPM drops to 4-6ms under Octane with FrankenPHP, because the container, service providers, and route table are built once and reused.

The reason is arithmetic, not magic. When I profiled a Laravel 12 app under PHP-FPM, roughly 70% of each request’s CPU time was framework boot — autoloading, container compilation, config parsing — and only 30% was my actual business logic. Worker mode deletes the 70%. That is also why hello-world benchmarks show absurd gaps like 15,000 vs 4,000 req/s: the smaller your real logic, the bigger the boot overhead looks proportionally.

My recommendation: ignore any benchmark that does not state which mode was tested and what framework was running.

What does worker mode actually change in your code?

Worker mode turns every request-scoped assumption in your codebase into a potential bug, because state now survives between requests. Anything registered as a singleton — a logger with request context attached, a service holding the authenticated user, a static property caching per-request data — is silently reused by the next request. This is the single largest source of surprising Octane bugs, and I hit it personally on a client project where a report service cached the requesting user’s timezone in a static property. Under PHP-FPM this was invisible for three years. Under worker mode, user B got user A’s timezone within the first hour of deployment.

Here is the pattern that breaks, and the fix:

// BROKEN in worker mode: state leaks across requests
class ReportService
{
    private static ?string $timezone = null;

    public function timezone(): string
    {
        // First request sets it; every later request reuses it
        return self::$timezone ??= auth()->user()->timezone;
    }
}

// FIXED: resolve per request, never memoize request state statically
class ReportService
{
    public function timezone(): string
    {
        return auth()->user()->timezone;
    }
}

Memory is the second discipline. PHP was never designed for long-running processes, and plenty of libraries leak a few kilobytes per request — harmless under PHP-FPM’s die-after-request model, fatal under worker mode where RSS climbs in a staircase pattern until the OOM killer fires during peak traffic. FrankenPHP’s answer is the max_requests setting, which recycles a worker thread after N requests:

{
    frankenphp {
        worker {
            file /app/public/frankenphp-worker.php
            num 8
        }
        max_requests 512
    }
}

My recommendation: set max_requests to 512 from day one and put a memory graph on your dashboard before you deploy, not after. Worker mode without RSS monitoring is flying without instruments.

What are the gotchas nobody mentions?

The biggest hidden gotcha is the Alpine Linux trap: FrankenPHP requires PHP compiled in ZTS (thread-safe) mode, and ZTS PHP on musl libc — which Alpine uses — is measurably slower than on glibc. Teams pick the Alpine Docker image for its size, then miss the published benchmark figures. Use the Debian-based images for production.

Second, not every package survives worker mode. Libraries that assume a fresh process per request — legacy Drupal modules, older PDF generators, anything fond of global state — need patching or exclusion. If your composer.lock is old enough to vote, budget real migration time.

Third, database connections behave differently. Persistent connections reused across thousands of worker requests are a throughput win, but they expose you to “MySQL server has gone away” errors after idle periods unless you configure reconnection handling — invisible under PHP-FPM, where connections rarely lived long enough to time out.

Fourth, deployment changes shape: worker mode holds code in memory, so every deploy requires a worker restart and a strategy for draining in-flight requests. FrankenPHP 1.11 added a force-kill primitive for stuck threads precisely because this bit people.

My recommendation: run worker mode in staging under production-like traffic for two weeks before cutover. Every one of these gotchas surfaces within that window.

Does FrankenPHP replace Nginx?

Yes — one frankenphp process replaces both nginx and php-fpm, which is half the appeal. Caddy underneath gives you automatic HTTPS with Let’s Encrypt renewal, HTTP/2 and HTTP/3 by default, and one config file instead of three. On a typical VPS deployment, that removes an entire service from your systemd unit list and the whole class of “502 Bad Gateway because FPM socket permissions” incidents.

You still might keep something in front. CDNs, WAFs, and load balancers do not go away, and teams running many apps per host sometimes keep a front proxy for routing. But the FastCGI layer — the socket, the pool tuning, the pm.max_children guessing game — is gone. In my VPS deployments, the config went from roughly 200 lines across nginx.conf and php-fpm.d to about 30 lines of Caddyfile.

My recommendation: even if you skip worker mode entirely, classic-mode FrankenPHP is worth evaluating purely as an operational simplification for new projects.

When should you stay on PHP-FPM?

Most PHP applications in 2026 should stay on PHP-FPM, and that is the correct engineering call, not a failure of ambition. If your app serves under 50 requests per second, worker mode’s throughput headroom solves a problem you do not have, while the singleton discipline, memory monitoring, and deployment changes are costs you pay immediately. PHP-FPM’s die-after-request model is a forgiving architecture that absorbs sloppy code and leaking libraries without complaint.

The economics flip at scale. One documented pattern from 2026 production reports: three Octane/FrankenPHP servers handling load that previously required eight PHP-FPM servers. If your monthly compute bill has four digits and your app is a stateless API with clean dependency injection, worker mode pays for its migration cost in a quarter. If you run a Drupal site with forty contrib modules, it almost certainly never will — the compatibility audit alone would cost more than the servers you would save.

My recommendation: make the decision on traffic and code cleanliness, not on hype. Under 50 req/s or heavy legacy dependencies: PHP-FPM. Stateless, container-disciplined, high-traffic: worker mode.

FrankenPHP vs PHP-FPM: production comparison, 2026
Option Performance Complexity When to Use
Nginx + PHP-FPM ~18,400 req/s (Tideways bench); 40-60ms Laravel boot per request Low — forgiving of leaks and global state; two services to manage CRUD apps, legacy code, Drupal/WordPress, teams without ops bandwidth
FrankenPHP classic mode ~18,400 req/s — statistical tie with FPM (0.4% diff) Low — single binary, auto-HTTPS, one config file New projects wanting simpler ops without code changes
FrankenPHP worker mode 3-4x FPM throughput; Laravel boot 4-6ms; Symfony 45ms to 8ms High — requires stateless code, memory monitoring, max_requests tuning High-traffic stateless APIs, real-time features, compute-bill pressure

The NexGismo verdict: FrankenPHP worker mode is the real deal for high-traffic stateless APIs — the 3-4x throughput gain is reproducible and the infrastructure savings are genuine. But for the typical Laravel CRUD app or any Drupal site, I recommend staying on PHP-FPM in 2026: classic-mode FrankenPHP buys you zero speed, and worker mode’s singleton and memory discipline costs more engineering time than most apps will ever recoup in server savings. Switch when your compute bill hurts, not when a benchmark impresses you.

  • FrankenPHP classic mode and Nginx + PHP-FPM both serve approximately 18,400 requests per second in Tideways benchmarks — a 0.4% difference that makes classic mode an operational choice, not a performance one.
  • FrankenPHP worker mode delivers 3-4x higher throughput than PHP-FPM by booting the framework once, cutting Laravel bootstrap from 40-60ms to 4-6ms per request.
  • A Symfony 7.4 application benchmarked at 45ms per request on PHP-FPM dropped to 8ms on FrankenPHP worker mode.
  • Production reports from 2026 show three FrankenPHP worker-mode servers handling load that previously required eight PHP-FPM servers.
  • FrankenPHP requires thread-safe (ZTS) PHP, which runs measurably slower on Alpine’s musl libc — Debian-based images are the correct production choice.
  • As of 2026, FrankenPHP 1.11.x ships with PHP 8.5 support and PHP Foundation backing, with configurable max_requests thread recycling to contain memory leaks in worker mode.

Frequently Asked Questions

What is FrankenPHP?

FrankenPHP is a modern PHP application server written in Go and built on Caddy. It embeds the PHP interpreter directly into the web server, replacing the Nginx plus PHP-FPM stack with a single binary that provides HTTP/2, HTTP/3, automatic HTTPS, Early Hints, and an optional worker mode that keeps your application booted in memory between requests.

Is FrankenPHP faster than PHP-FPM?

Only in worker mode. Classic mode ties PHP-FPM at roughly 18,400 requests per second in Tideways benchmarks — a 0.4% difference. Worker mode delivers 3-4x higher throughput because the framework boots once instead of on every request, dropping Laravel bootstrap time from 40-60ms to 4-6ms.

Does FrankenPHP replace Nginx?

Yes. One frankenphp process serves HTTP directly and executes PHP, replacing both nginx and php-fpm. It includes automatic HTTPS certificate management, HTTP/2, and HTTP/3 by default. Teams often keep a CDN or load balancer in front, but the FastCGI socket layer and its configuration disappear entirely.

Is FrankenPHP production ready?

Yes. As of 2026, FrankenPHP is at version 1.11.x with PHP 8.5 support and official PHP Foundation backing. Classic mode is a drop-in replacement for PHP-FPM. Worker mode is stable in production but requires leak-free, stateless application code plus memory monitoring and max_requests recycling configured.

What is worker mode in FrankenPHP?

Worker mode keeps your PHP application resident in memory between requests. The autoloader, framework container, and database connections initialize once and are reused, which eliminates per-request bootstrap cost and produces the 3-4x throughput advantage. In exchange, your code must not leak state or memory across requests.

The PHP-FPM model survived fifteen years because dying after every request forgives almost every sin a codebase can commit. FrankenPHP worker mode asks your application to finally pay down that debt in exchange for a genuine 3-4x throughput dividend — and whether that trade makes sense is a question about your code and your compute bill, not about which technology is newer. I switched two high-traffic API projects and kept every CRUD app and Drupal site where it was; six months in, both decisions still look right. If you are weighing the migration, start with the two-week staging soak test and a memory graph. And if your own FrankenPHP numbers contradict mine, I would like to see them — real data beats vendor benchmarks every time.