· php.live plus 2024 · Ostrava · given in Czech

Modern PHP web servers

Moderní PHP webservery. Apache, Nginx, Caddy, NGINX Unit and FrankenPHP. They all serve PHP, but they do it in very different ways, and that difference matters more than any benchmark.

Update, 2026Two things changed since this talk. NGINX Unit was archived in October 2025 and no longer gets features or security fixes, so don’t pick it for new projects. FrankenPHP became officially supported by the PHP Foundation in May 2025, which answers the “bus factor 1” worry below.

First, how PHP gets served

PHP is an interpreted language. The interpreter, php-src, is written in C, and a web server can’t run PHP by itself. It needs a SAPI, a Server Application Programming Interface, which is the bridge between the web server and the interpreter.

There are two ways to do it:

Diagram: the user sends an HTTP request to Nginx, which passes it over the FastCGI protocol to the PHP-FPM process manager and its PHP workers.
The classic setup: Nginx as the web server, PHP-FPM as the SAPI, FastCGI between them.

This matters for containers. An external SAPI means two processes, usually two containers, that you have to wire together. An embedded one is a single process and a single container.

What I expect from a web server

Apache (since 1995)

Twenty-nine years old at the time of the talk and still everywhere. It runs PHP either embedded through mod_php, which fits in a single container, or through PHP-FPM. It’s written in C, it’s stable, and it has the legendary .htaccess. On the other side: performance isn’t great, and configuring it is painful.

Nginx (since 2004)

The industry standard. Written in C, with great performance and an excellent reverse proxy. It only talks to PHP through PHP-FPM, which makes it less container friendly: you always run two processes. It’s also complex to configure well.

Caddy (since 2015)

Written in Go, and the easiest one to start with. Its defaults are good enough for production as they are:

The catch: it uses PHP-FPM like Nginx, and it’s slower than Nginx.

NGINX Unit (since 2017)

Unit is a different idea: an application server that runs many languages (PHP, Python, Node, Go, Java, Perl, Ruby, WebAssembly) and embeds PHP directly. You configure it entirely through a REST API with an OpenAPI spec, and routing goes through listeners, routes and applications or upstreams.

The good parts were great performance and stability with a small footprint, zero-downtime reloads, powerful routing with NJS scripting, easy TLS, and security. The weak parts were no native Prometheus support, a slower release cycle than I hoped, and some missing advanced features.

FrankenPHP (since 2022)

FrankenPHP is by Kévin Dunglas, the author of Mercure, Vulcain and API Platform. It’s a combination of three things:

You get everything Caddy offers (HTTP/2 and HTTP/3, automatic HTTPS, zero-downtime reloads), plus:

My worries in 2024 were stability and a bus factor of one.

The benchmarks

Numbers from a benchmark of PHP runtimes, in a Docker container with 1 CPU and 1 GB of RAM, measured with k6 at a concurrency of 1,000:

Bar chart of average requests per second at concurrency 1000. Apache with mod_php 211.6, Apache with PHP-FPM 223.6, Nginx with PHP-FPM 435.62, Nginx Unit 1,383.01, Roadrunner 768.83, Nginx with Roadrunner 672.94, FrankenPHP 414.36, FrankenPHP in worker mode 5,472.41, Swoole 3,706.58.
Average requests per second. Higher is better.

FrankenPHP in worker mode handled about 5,470 requests per second, 25× more than Apache and more than 12× Nginx with PHP-FPM. Its average response time was 180 ms, against almost 3 seconds for Apache. Without worker mode, FrankenPHP was roughly on par with Nginx and PHP-FPM. Unit was the best of the classic setups at about 1,380 requests per second.

A separate stress test of Unit, Nginx with PHP-FPM, and Laravel Octane on Swoole found that Unit was the only one that didn’t drop a single request, with low resource usage and very fast cold starts.

Which one to pick

The demo code from the talk is on GitHub: github.com/janmikes/phplive-2024.

All 43 slides
  1. Slide 1: Title: Modern PHP Web Servers, Jan Mikeš, 17.5.2024 #PHPLive2024; omnicado.com, myspeedpuzzling.com, peon.dev
  2. Slide 2: Speaker's projects: omnicado.com, MySpeedPuzzling.com, and peon.dev with short descriptions
  3. Slide 3: Logos of the compared web servers: Apache, Nginx, Caddy, Nginx Unit, FrankenPHP
  4. Slide 4: Reminder: PHP is interpreted; web servers talk to it via a SAPI, embedded (mod_php) or external (PHP-FPM over FastCGI)
  5. Slide 5: php-src README for the embed SAPI with a basic C example
  6. Slide 6: Diagram: user request goes to Nginx, which talks FastCGI to PHP-FPM workers
  7. Slide 7: PHP-FPM pool config www.conf: user and group www-data, listen 127.0.0.1:9000
  8. Slide 8: Webserver expectations: stability, performance, easy config, PHP 8.3, open source, docs, community, cloud, observability
  9. Slide 9: Retro western movie poster titled "Apache"
  10. Slide 10: Apache: since 1995, PHP-FPM or mod_php, stable, written in C, slower, .htaccess, painful to configure
  11. Slide 11: Apache VirtualHost config proxying .php files to PHP-FPM at 127.0.0.1:9000
  12. Slide 12: Vintage gin label reading "Nginx, served here, a perfect match"
  13. Slide 13: Nginx: since 2004, PHP-FPM, industry standard, written in C, great performance and reverse proxy, complex
  14. Slide 14: Nginx server block config with try_files and fastcgi_pass to 127.0.0.1:9000
  15. Slide 15: White van branded with the Caddy logo
  16. Slide 16: Caddy: since 2015, automatic SSL, zero-downtime reloads, HTTP/2 and 3, written in Go, slower than Nginx
  17. Slide 17: Minimal Caddyfile: root, php_fastcgi 127.0.0.1:9001, file_server
  18. Slide 18: Nginx logo next to a green thread spool with a U, a pun on Nginx Unit
  19. Slide 19: Nginx Unit: since 2017, written in C, multi-language, configured via REST API, layered routing
  20. Slide 20: Unit pros and cons: great performance, zero downtime, NJS scripting; no native Prometheus, slower releases
  21. Slide 21: Unit config.json: listener on *:80 passing to a PHP application
  22. Slide 22: curl PUT uploading config.json to the Unit control socket
  23. Slide 23: FrankenPHP logo with its Frankenstein elephant mascot
  24. Slide 24: Kévin Dunglas, creator of Vulcain, Mercure, API Platform, and Symfony components, with thumbs-up hands
  25. Slide 25: FrankenPHP stack: Caddy web server, FrankenPHP SAPI embedded in Caddy, optional static-php-cli binaries
  26. Slide 26: Meme: smiling man holding out an apple toward the camera
  27. Slide 27: FrankenPHP: Caddy features, embed SAPI in Go, 103 early hints, worker mode, Mercure hub, single binary, bus factor 1
  28. Slide 28: Caddyfile enabling FrankenPHP with php_server for example.com
  29. Slide 29: docker run command for the dunglas/frankenphp image on ports 80 and 443
  30. Slide 30: docker-compose.yml running dunglas/frankenphp with HTTP, HTTPS, and HTTP/3 ports
  31. Slide 31: Dockerfile: FROM dunglas/frankenphp, COPY . /app/public
  32. Slide 32: Dockerfile adding PHP extensions pdo_mysql, gd, intl with install-php-extensions
  33. Slide 33: Mind-blown meme: man with hands at his temples
  34. Slide 34: Running a self-contained app binary: php-server --domain example.com and php-cli bin/console
  35. Slide 35: Futurama Fry meme: "Should I choose this one or this one?"
  36. Slide 36: Cartoon finger about to press a blue button among many red buttons
  37. Slide 37: Bar chart, requests per second at concurrency 1000: FrankenPHP worker mode 5,472, Swoole 3,707, Apache+mod_php 212
  38. Slide 38: Bar chart, response time at concurrency 1000: FrankenPHP worker mode 180 ms, Swoole 266 ms, Apache+PHP-FPM 2,950 ms
  39. Slide 39: Stress test: Unit vs Nginx+FPM vs Octane; Unit dropped no requests, fast with low resource usage
  40. Slide 40: Nginx vs Apache and Nginx vs Caddy: performance, ease of use, ecosystem, and who each suits
  41. Slide 41: Verdict: Unit is very fast and reliable, go for it; FrankenPHP is supercool, go for it
  42. Slide 42: Links: github.com/janmikes/phplive-2024, caddyserver.com, unit.nginx.org, frankenphp.dev, static-php.dev
  43. Slide 43: Homer Simpson asking "Questions?" and Thank you! @honza_mikes, janmikes.cz

Related talks