PHP 8.5 Application Server Showdown: FrankenPHP vs RoadRunner vs PHP-FPM Benchmark

In this PHP 8.5 application server benchmark, we put three runtimes to the test: traditional PHP‑FPM, RoadRunner, and FrankenPHP. Traditional PHP‑FPM has been the reliable workhorse for two decades. However, modern application servers promise higher concurrency, lower latency, and better resource efficiency. How much faster are they in a real‑world Laravel application?

We compared all three under identical hardware and PHP configurations. The results are clear: RoadRunner delivered 41.6% higher throughput and 29% lower P99 latency than PHP‑FPM. FrankenPHP performed on par with FPM in this specific test, after we applied our default tuning. Below, we share the full methodology, raw data, and configuration files so you can reproduce every result yourself.


After our initial three-way comparison, readers asked us to add Swoole to the lineup. We answered that call in our four-way application server showdown, where Swoole’s coroutine model revealed important performance trade-offs.

Test Environment & Methodology

All tests ran on a single Alibaba Cloud ECS instance to eliminate network variance. The software stack mirrors a typical production environment managed by the Baota panel. This approach is a significant departure from the request‑per‑process model we used in our first benchmark .

Server Specifications

  • OS: Ubuntu 24.04 LTS
  • Web Server: Nginx 1.26 (for PHP‑FPM only)
  • PHP Versions: 8.5.2 (FPM & RoadRunner) / 8.5.5 (FrankenPHP built‑in)
  • Database: MySQL 8.4
  • Control Panel: Baota (for convenient PHP‑FPM management)

Uniform PHP Configuration

To guarantee a fair comparison, all three environments shared the exact same php.ini settings. We disabled JIT for this test to isolate the impact of the runtime architecture. You can see the profound impact JIT can have in our detailed JIT configuration impact analysis.

Furthermore, all environments used the optimal OPcache settings identified in our OPcache optimization guide . These settings align with the official PHP OPcache recommendations .

DirectiveValue
memory_limit256M
opcache.enableOn
opcache.validate_timestampsOff
opcache.memory_consumption512
opcache.jitOff
Worker / Process Count20 (FPM static pool / RR workers / FrankenPHP fixed threads)

Test Script

We created a simple route /bench inside a Laravel 11 application. This route simulates a realistic mixed workload for our PHP 8.5 application server benchmark:

  • 1,000 sqrt() calculations (light CPU pressure)
  • One SELECT 1 database query (verifies connectivity and adds micro‑I/O)

Load Testing Tool & Parameters

  • Tool: ApacheBench (ab) 2.3
  • Concurrency: 200 (-c 200)
  • Duration: 60 seconds per run (-t 60)
  • Strategy: Each environment ran 3 consecutive rounds. The first round served as a warm‑up and was discarded. The final result is the average of rounds 2 and 3.

Benchmark Results

Bar chart comparing RPS of PHP-FPM (167), FrankenPHP (167), and RoadRunner (237)

The table below shows the average of the two valid measurement rounds for each environment. Higher RPS (Requests Per Second) is better; lower P99 latency is better.

EnvironmentAvg RPS (Warm)Avg P99 (ms)Throughput vs FPM
PHP‑FPM (Baseline)167.351337
RoadRunner237.01944▲ +41.6%
FrankenPHP166.721381▼ -0.4%

Key Takeaways

RoadRunner significantly outperforms traditional FPM in both throughput and tail latency under this mixed workload. The 41.6% RPS improvement and 29% P99 reduction translate directly to better user experience and higher scalability.

In contrast, FrankenPHP matched FPM performance in this specific setup. Despite our efforts to align configurations (including fixing the worker count to 20 and loading the optimized php.ini), the test tool ab uses HTTP/1.0 short‑lived connections. This may not fully exercise FrankenPHP’s HTTP/2 and multiplexing strengths. As a result, in production environments with HTTP/2 traffic, FrankenPHP is expected to perform considerably better.


Analysis: Why RoadRunner Wins

RoadRunner’s advantage stems from its memory‑resident architecture and event‑driven design.

First, there is no process forking overhead. Traditional PHP‑FPM spawns a new process (or reuses one from a pool) for each request. This incurs OS‑level overhead. RoadRunner keeps a pool of long‑lived PHP workers that handle thousands of requests without respawning.

Second, the framework boots only once. In FPM, Laravel initializes on every request. RoadRunner boots the framework once per worker and reuses that state. This drastically reduces per‑request CPU time.

Finally, the Goridge protocol is highly efficient. Communication between the Go server and PHP workers uses a binary protocol that minimizes serialization costs.

While RoadRunner shines, JIT often provides the biggest boost in compute‑heavy scenarios. Our Laravel JIT benchmark results showed an 82% improvement on a CPU‑intensive route. This is a separate optimization that you can layer on top of an application server.

In contrast, the gains for I/O‑bound applications are more muted. This aligns with our findings in the WordPress PHP performance benchmark , where JIT had a negligible impact. For those workloads, the architectural efficiency of the runtime itself matters more than JIT.

Adopting a modern application server like RoadRunner is one of the most impactful upgrades you can make. It is a more significant leap than the incremental engine improvements we observed in our PHP 7.4 to 8.5 performance evolution benchmark .


Resource Consumption Observations

While the benchmark focused on RPS and latency, we also monitored CPU and memory usage via htop.

  • PHP‑FPM: Maintained exactly 20 php‑fpm processes. Each consumed approximately 30‑50 MB of RAM after warm‑up. Total memory footprint was the highest of the three.
  • RoadRunner: Showed one rr master process and 20 lightweight PHP workers. Memory usage per worker was similar to FPM. However, the overall footprint was slightly lower due to shared master process overhead.
  • FrankenPHP: Ran as a single multi‑threaded process. Memory usage was the most compact of all three environments. This makes it exceptionally well‑suited for memory‑constrained containers.

Recommendations for Production

Based on our findings, here is our guidance for choosing a PHP 8.5 runtime.

Stick with PHP‑FPM if: You run traditional web applications, value maximum stability and compatibility, or have a well‑established FPM tuning workflow. FPM is still perfectly capable for moderate traffic levels.

Adopt RoadRunner if: You operate high‑traffic APIs, Laravel Octane projects, or microservices. The performance uplift is substantial. Moreover, Laravel’s first‑party support makes integration smooth.

Experiment with FrankenPHP if: You are drawn to its single‑binary deployment, modern Caddy features (auto HTTPS, HTTP/3), or you run containerized workloads where memory density matters. Be prepared to invest time in tuning. For instance, you should fix worker counts, adjust the Caddyfile, and test with HTTP/2‑capable load generators.

Of course, if raw CPU speed is your ultimate goal, you might even consider how PHP stacks up against alternatives. We explored this in our PHP vs Node.js vs Go vs Python showdown.


Full Reproducibility

At PHPBenchLab, we believe benchmarks should be transparent and repeatable. Below are the exact commands and configurations we used. You can run the same tests on your own Ubuntu 24.04 server with Baota panel.

Test Endpoints (Publicly Accessible)

  • PHP‑FPM: https://phpbenchlab.com/bench
  • RoadRunner: https://phpbenchlab.com:8081/bench
  • FrankenPHP: https://phpbenchlab.com:8082/bench

RoadRunner Configuration (.rr.yaml excerpt)

text

version: '3'
server:
  command: "/www/server/php/85/bin/php worker.php"
http:
  address: 0.0.0.0:8081
  pool:
    num_workers: 20

FrankenPHP Configuration (Caddyfile excerpt)

text

:8082 {
    bind 0.0.0.0
    root * public/
    php_server {
        env PHP_INI_SCAN_DIR /www/server/php/85/etc
        worker 20
    }
}

Benchmark Command (after warm‑up)

bash

ab -c 200 -t 60 https://phpbenchlab.com/bench

We encourage you to replicate these tests in your own environment. Different hardware, PHP extensions, or workloads may yield different results. Always benchmark with your own application code.


Frequently Asked Questions

1. Why did RoadRunner outperform PHP-FPM by 41%?

RoadRunner avoids the overhead of spawning processes and booting Laravel on every request. It keeps PHP workers alive in memory and reuses the framework state, significantly reducing CPU time and latency. Additionally, its event‑driven Go server handles concurrent connections more efficiently than the traditional FPM pool model.

2. Why didn’t FrankenPHP show a performance gain in this test?

In this specific setup, the load testing tool ab uses HTTP/1.0 short‑lived connections, which does not leverage FrankenPHP’s HTTP/2 and multiplexing strengths. Moreover, FrankenPHP’s dynamic worker scaling may introduce slight cold‑start latency during sudden bursts. In production environments with modern load tools and sustained traffic, FrankenPHP typically performs on par with or better than RoadRunner.

3. Is JIT enabled in this benchmark?

No. We deliberately disabled JIT (opcache.jit=off) across all three environments. This isolates the performance impact of the runtime architecture itself, rather than the JIT compiler. For a detailed look at JIT’s impact, see our JIT configuration impact article.

4. Can I use these configurations in a shared hosting environment?

RoadRunner and FrankenPHP require the ability to run long‑lived processes and bind to custom ports, which is typically not allowed on shared hosting plans. They are best suited for VPS, dedicated servers, or cloud instances where you have full control over the environment. PHP‑FPM remains the standard choice for shared hosting.

5. Which application server is best for Laravel Octane?

RoadRunner is the officially recommended and most mature option for Laravel Octane. It provides first‑party integration, extensive documentation, and a large community. FrankenPHP is also supported experimentally in Octane and is rapidly improving, making it an exciting alternative for those willing to experiment.

6. How do I fix the “Target class [config] does not exist” error with RoadRunner?

This error usually occurs when Laravel’s configuration cache is stale or corrupted. Run the following commands to rebuild the cache before starting RoadRunner:

php artisan optimize:clear

php artisan config:cache

php artisan route:cache

Then restart the RoadRunner server. For more details, refer to the troubleshooting section in our OPcache optimization guide .


What’s Next?

This test focused on raw HTTP throughput with a modest CPU+DB workload. In future articles, we plan to explore:

  • FrankenPHP vs RoadRunner under HTTP/2 load using wrk or bombardier.
  • JIT impact across these three runtimes (JIT was off in this test).
  • Memory efficiency and cold‑start times in serverless/container contexts.

Have a specific scenario you’d like us to benchmark as part of our ongoing PHP 8.5 application server benchmark series? Reach out via our contact page.

Found this article useful? Share it with your team or bookmark PHPBenchLab.com for more data‑driven PHP performance insights.

Leave a Comment