Your website feels fast when you test it alone. Then a campaign goes live, 1,000 people arrive at once, and pages start taking seconds to load, or don't load at all. Most of the time the code isn't the problem. The problem is how PHP is being served.
We wanted a clear answer to a question our clients ask us often: which PHP runtime handles heavy real-time traffic best? So we load-tested three popular setups with 1,000 concurrent users on the same server and the same real application.
The short version
- Apache + PHP-FPM gave the best balance of CPU, memory and throughput. CPU peaked at around 500%.
- FrankenPHP was very fast on light load, but CPU climbed to around 1500% at 1,000 users, which made capacity hard to plan.
- Caddy + PHP-FPM was stable and the easiest to configure, with slightly higher latency than Apache.
- Choosing the runtime is half the job. Tuning PHP-FPM, OPcache, Redis sessions and MySQL makes the real difference.
The three stacks we tested
- FrankenPHP: a modern PHP application server built on Caddy, now officially supported by The PHP Foundation.
- Caddy + PHP-FPM: Caddy as the web server, with automatic HTTPS, and PHP-FPM handling PHP.
- Apache + PHP-FPM: the traditional, battle-tested pairing, with Apache passing requests to PHP-FPM.
Test setup
- Server: 16 CPU cores, 40 GB RAM
- Database: MySQL 8 with a tuned
innodb_buffer_pool_sizeand caching parameters - Load tools: ApacheBench (
ab) andwrk, simulating 1,000 concurrent users - Application: a real-world PHP project with sessions and database queries, not a "hello world" script
That last point matters. Synthetic benchmarks that return a static string flatter every runtime. Real applications open sessions, query databases and build pages, and that's where the differences show up.
Results at a glance
| Stack | What we saw at 1k users | Stability | Best for |
|---|---|---|---|
| Apache + PHP-FPM | CPU peaked around 500% | Predictable | Business-critical apps, CRMs, high-traffic sites |
| Caddy + PHP-FPM | Slightly higher latency than Apache | Stable | Simple deployments that need automatic HTTPS |
| FrankenPHP | CPU peaked around 1500% | Inconsistent under heavy load | Modern apps built for worker mode |
FrankenPHP: fast, but hungry under pressure
On light load, FrankenPHP was impressively quick. It's easy to run, and it includes modern features like HTTP/2, HTTP/3 and early hints.
At 1,000 concurrent users the picture changed. CPU usage climbed to around 1500%, which means almost all 16 cores were busy. Requests were still served quickly, but resource usage became unpredictable. When the CPU is that close to its limit, a small extra spike leaves no headroom.
A note on worker mode: FrankenPHP's biggest advantage is worker mode, which keeps your application loaded in memory between requests. It can give large gains, but only for applications written to support it, because state must not leak from one request to the next. If you're evaluating FrankenPHP, test worker mode with your own application before deciding.
Caddy + PHP-FPM: the easiest to run
Caddy is a pleasure to configure. HTTPS certificates are issued and renewed automatically, the config file is short and readable, and it spread the load well. Paired with PHP-FPM it was stable throughout the test.
In our runs, though, latency was slightly higher than with Apache in front of the same PHP-FPM pool, and PHP worker handling was a little less efficient.
Apache + PHP-FPM: the most predictable
The traditional stack gave the best balance of CPU usage, memory consumption and request throughput. At 1,000 concurrent users, CPU stayed at around 500%, about a third of FrankenPHP's peak, which left plenty of room for traffic spikes.
Its only real downside was a slightly higher memory footprint than Caddy. For a business application, we'll take that trade-off for predictability every time.
Why CPU usage matters more than raw speed
A benchmark winner on paper isn't always the winner in production. What business owners care about is simple: does the site stay fast when it matters most? Think of a product launch, a newsletter send, or Monday morning when the whole sales team logs into the CRM.
A stack with steady, moderate CPU usage gives you:
- Headroom for unexpected spikes, without downtime
- Predictable scaling, so you know when to add resources before users notice
- Room for everything else on the server, such as background jobs, cron tasks and the database
How we tune PHP-FPM for 1,000+ users
Choosing Apache + PHP-FPM is the starting point. These are the settings our team adjusts on every high-traffic project. The values below are example starting points; the right numbers depend on your application.
1. Size the worker pool to your memory
Measure how much memory an average PHP-FPM worker uses in your application, then set pm.max_children to the memory available for PHP divided by that figure. If it's too low, requests queue. If it's too high, the server starts swapping.
pm = static ; or dynamic for servers with varying load
pm.max_children = 200 ; example: RAM available for PHP / avg worker size
pm.max_requests = 1000 ; recycle workers to contain memory leaks
2. Turn on OPcache properly
opcache.enable=1
opcache.memory_consumption=256
opcache.max_accelerated_files=20000
opcache.validate_timestamps=0 ; reload PHP-FPM on every deploy
3. Move sessions to Redis
File-based sessions lock and slow down under concurrency. Redis keeps them in memory:
session.save_handler = redis
session.save_path = "tcp://127.0.0.1:6379"
4. Use Apache's event MPM
Run Apache with mpm_event and mod_proxy_fcgi, not the old prefork + mod_php combination. That way Apache handles connections and PHP-FPM handles PHP, and each can be tuned on its own.
5. Don't forget the database
Many "PHP is slow" problems are really database problems. Size innodb_buffer_pool_size so your working data fits in memory, add indexes for your slowest queries, and check the slow query log before you blame PHP.
Which stack should you choose?
- Apache + PHP-FPM: if your site or app is business-critical, has many logged-in users, or runs a CRM like vTiger. Predictability wins.
- Caddy + PHP-FPM: if you want a simple, modern setup with automatic HTTPS and your traffic is moderate.
- FrankenPHP: if you're building a new application designed for worker mode and can test it thoroughly before launch.
What this means for your website, CRM or web app
Most slow PHP sites we're asked to look at don't need new hardware. They need the right stack and sensible tuning. When a client brings a slow website, a busy vTiger CRM or a custom web app to WebifyGO, we start by measuring: worker usage, slow queries, session handling and cache hit rates. Then we fix what's actually holding it back.
Performance and security go hand in hand. The same PHP-FPM setup that won this benchmark also closes the door on risky configurations like PHP-CGI. We cover that in our guide to two critical PHP vulnerabilities and how to fix them.
If your site slows down whenever traffic picks up, the WebifyGO team can tell you why, and fix it.
Is your PHP site slowing down under load?
Talk to our team. We'll review your server stack and show you exactly where the performance is going.
Book a free consultation