What actually happens when you push a PHP application to millions of requests and how far can it really go?
I’ve been writing PHP for eight years now. Long enough to have shipped code on PHP 5.6 with mysql_query() still lingering in legacy files, and long enough to now write PHP 8.5 . So I want to answer a question I still get asked alot:
Can PHP actually scale? Can it handle millions of requests? Thousands of concurrent users, hitting a database, at once, without falling over?
The honest answer is yes, but it is not a zero effort. So this piece is going to go deep: where PHP came from, what actually happens on the server when a PHP request lands, and what it genuinely takes to push a PHP application to serious scale.
A quick history of PHP
PHP started in 1994 as a personal set of CGI scripts Rasmus Lerdorf wrote to track visits to his own résumé page. He called it “Personal Home Page Tools.” That’s the entire ambition it was born with.
- PHP/FI (1995) added a form interpreter and database integration; the first hint that this was becoming a real web tool, not just a hack.
- PHP 3 (1998) was the first version that actually looked like a language, with a real parser rewritten by Andi Gutmans and Zeev Suraski.
- PHP 4 (2000) introduced the Zend Engine: the first real virtual machine under the language, and the point where PHP stopped being a scripting curiosity and started being infrastructure. This is the version WordPress, and a huge share of the early 2000s web, was built on.
- PHP 5 (2004) brought proper object-oriented programming; interfaces, abstract classes, exceptions. This is the version that made “PHP framework” a coherent phrase, and Symfony (2005) and later Laravel built entire ecosystems on top of it.
- PHP 7 (2015) is the version that should have killed the “PHP is slow” narrative on its own. A rewritten Zend Engine roughly doubled performance and cut memory usage nearly in half over PHP 5.6, for free, with no code changes required.
- PHP 8 (2020) added a JIT compiler, union types, named arguments, and match expressions. The subsequent 8.1–8.5 releases added enums, readonly properties, fibers (real, first-class coroutines: cooperative concurrency without an extension), and property hooks. This is not the PHP most critics are picturing when they dunk on it.
And the ecosystem around the language moved just as much as the language itself. Composer (2012) gave PHP a real dependency manager and ended the copy-paste-library era almost overnight. The PSR standards gave the ecosystem shared interfaces instead of every framework reinventing HTTP messages and autoloading. Laravel turned developer experience into PHP’s dominant framework philosophy, the way Rails once did for Ruby. And most recently, tools like Laravel Octane and FrankenPHP; a modern PHP application server written in Go, built on the Caddy web server, with first-class Laravel and Symfony worker-mode integrations have started attacking the exact architectural assumption that made PHP “slow” in the first place: that every request has to boot the framework from scratch. More on that shortly.
As of today, PHP still powers roughly 7 in 10 websites on the public web where the server-side language is known, according to W3Techs’ ongoing survey and the majority of those sites now run on PHP 8. “PHP is dead” has been said every year since roughly 2008. It is still, by a wide margin, the most deployed server-side language on the internet. Both things being true at once is the actual, more interesting story.
What actually happens under the hood
Here’s where the “doesn’t scale” reputation actually comes from, and it’s rooted in something real, not just old prejudice.
The dominant way PHP has been deployed for most of its life is PHP-FPM (FastCGI Process Manager) sitting behind Nginx or Apache. When a request comes in:
- The web server accepts the TCP connection and hands the request to a free FPM worker process over the FastCGI protocol.
- That worker process boots the application (or resumes from OPcache, PHP’s bytecode cache, if it’s warm), runs the request, and produces a response.
- Almost all request-scoped state is then thrown away. The worker doesn’t remember the previous request. It didn’t keep a database connection open. It didn’t keep anything in memory beyond what OPcache has already cached at the bytecode level.
- The worker goes back into the pool, idle, waiting for the next request which might belong to a completely different user, and which will re-do all of the above from a clean slate.
This is called a shared-nothing architecture, and it’s a genuine, deliberate design philosophy, not an accident. It has real upsides: no request can leak state into another, a single misbehaving request can’t corrupt a long-lived process, and a bad deploy is a full worker recycle away from being clean. It’s also exactly why classic PHP hosting could be so forgiving and so cheap for two decades: the failure modes were contained by design.
The downside is the one that actually drives the “doesn’t scale” reputation: because nothing persists between requests, every worker that touches a database opens its own fresh connection, on every request, and tears it down when it’s done. Multiply that by the number of worker processes per server, multiplied again by the number of servers behind your load balancer during a traffic spike, and you get a connection count that can outgrow what your database was ever configured to hold long before the database is anywhere near CPU- or disk-bound. This is the actual mechanism behind most real-world “PHP fell over” incidents. It’s an architecture problem sitting one layer below the language, and it’s fixable which is the whole point of the next section.
So, can it scale? Yes, but not for free
Here’s the thing I want to be completely honest about: PHP scaling to millions of users is not a “flip a config flag” story. Anyone who tells you it’s zero-effort is selling something. What’s true is that the effort required is well-understood, well-trodden by huge production systems, and doesn’t require abandoning the language. Broadly, it comes down to the same handful of levers, applied deliberately:
Make the request path itself fast. OPcache (bytecode caching) and, since PHP 8, the JIT compiler remove most of the “PHP has to re-parse and re-interpret everything on every request”; cost that gave the language its slow reputation in the first place. Framework-level caching of config, routes, and compiled views removes the reflection and filesystem overhead frameworks otherwise pay on every boot.
Stop rebuilding the world on every request. This is where worker-mode servers like Laravel Octane and FrankenPHP change the equation: instead of the shared-nothing, boot-from-scratch-every-time model, the application boots once and stays resident in memory across many requests, closer to how a long-running Node or Go service behaves. You trade away some of shared-nothing’s safety net for a large drop in per-request overhead and it’s notable that one of the most credible efforts here, FrankenPHP, is itself written in Go, embedding the PHP runtime rather than replacing it. That’s not a PHP-versus-Go story; it’s Go and PHP solving the problem together.
Stop opening a database connection per request, per worker, per server. This is the single most common actual cause of “PHP doesn’t scale” incidents, and it’s solved the same way any shared-nothing, connection-hungry stack solves it: put a pooling layer a dedicated proxy like ProxySQL, or a purpose-built service between the application fleet and the database, so the number of real database connections stays bounded no matter how many application workers or hosts you add.
Push work out of the request/response cycle entirely. Queues (a first-class, well-supported pattern in every serious PHP framework) let you accept a request, acknowledge it, and do the expensive part asynchronously. This alone removes a huge amount of pressure from the synchronous path that most scaling conversations focus on.
Scale the data layer on its own terms. Read replicas for read-heavy traffic, caching layers (Redis/Memcached) in front of hot queries, and sharding for write-heavy workloads that would otherwise serialize on a single primary. None of this is PHP-specific; it’s the same playbook any language uses once the database, not the application runtime, becomes the bottleneck.
Push static and semi-static work to the edge. CDNs, full-page caching, and edge logic remove a large fraction of requests from ever reaching a PHP worker at all; often the highest-leverage lever of all, because the cheapest request is the one your application never sees.
None of these are exotic. Every one of them is a documented, production-proven pattern, and none of them require PHP to stop being PHP. What they require is treating scale as an architecture problem (which it is, in every language) rather than assuming the runtime itself is the ceiling.
What’s next
That’s the theory, and I think it holds up. But theory is cheap, and I’d rather prove the point than just argue it. So in the next piece, I’m going to actually design a realistic, production-shaped PHP application and push it against the throughput numbers people usually assume you need a “modern” stack (Go, Node, Rust) to hit. I want to show, with real architecture and real reasoning, that a well-built PHP system can go toe-to-toe with the stacks it’s constantly compared unfavorably to.
I’ve spent eight years watching this language get underestimated. Let’s go build the receipts.
Комментарии (0)
Пока нет комментариев — будьте первым.