There is a conversation that has been going on in backend development circles for over a decade now, and it refuses to die. PHP or Node.js? Which one should you use? Which one is faster? Which one has a future?
I spent the last several weeks setting up identical environments, running real benchmarks, building small but representative applications in both, and reading through a significant amount of documentation, community data, and developer surveys. I went in with assumptions. Most of them were wrong.
This is not a fan piece. I have no allegiance to either camp. This is what I actually found.
Table of Contents
- Why These Two?
- The Origin Stories
- The State of Both in 2026
- Benchmark Setup
- Benchmark Results
- Code Comparison
- What Real Developers Say
- Pros and Cons
- Use Case Guide
- What the Numbers Actually Mean
- References
- The Verdict
Why These Two?
PHP and Node.js do not look like obvious competitors on paper. PHP is a language with its own runtime. Node.js is a JavaScript runtime. But in practice, both are used to build web backends, APIs, and server-rendered applications — which is why they end up in the same conversation constantly.
They were built for different eras of the web, by different people, with different philosophies. Understanding that context changes how you read the benchmarks.
The Origin Stories
PHP — Built to Solve a Real Problem in 1994
Rasmus Lerdorf did not set out to create a programming language. He wrote a set of Common Gateway Interface (CGI) binaries in C to track visits to his online resume. He called it "Personal Home Page Tools." That is the PHP in PHP.
The language grew organically. It was extended, contributed to, and eventually became one of the most deployed server-side languages in the world — not because it won some technical competition, but because it was available, easy to learn, and did the job at a time when the web was exploding and developers needed something that worked.
| Milestone | Year | Significance |
|---|---|---|
| PHP Tools (CGI) | 1994 | Rasmus tracks his resume visits — PHP is born |
| PHP 3 | 1997 | Rewritten from scratch, public adoption begins |
| PHP 4 | 2000 | Zend Engine introduced, WordPress era begins |
| PHP 5 | 2004 | OOP support, PDO, major language maturation |
| PHP 7 | 2015 | Near 2x speed over PHP 5, scalar type hints |
| PHP 8.0 | 2020 | JIT compiler, named arguments, attributes, union types |
| PHP 8.4 | 2024 | Property hooks, asymmetric visibility, improved array unpacking |
Its model was simple: a user makes a request, PHP executes a script, the script talks to a database, a response is sent back. New request, new execution. Stateless, predictable, widely understood.
The criticism came later. Function naming was not standardized. Error handling varied. The global state model caused problems at scale. The reputation suffered.
But PHP did not sit still. PHP 7 nearly doubled speed compared to PHP 5. PHP 8.0 introduced JIT compilation. PHP 8.3 and 8.4 continued tightening the language significantly. In 2026, PHP is a substantially different language from what most of its critics remember.
Node.js — Built to Fix What Ryan Dahl Thought Was Broken
Ryan Dahl introduced Node.js at JSConf EU in 2009 with a presentation that started by criticizing the way Apache handled concurrent connections. His argument was direct: traditional servers spawn a new thread per connection, threads are expensive, and blocking I/O makes the problem worse.
| Milestone | Year | Significance |
|---|---|---|
| Node.js 0.1 | 2009 | Ryan Dahl introduces it at JSConf EU |
| npm launched | 2010 | Package ecosystem begins to grow |
| Node.js Foundation | 2015 | Corporate backing, io.js merged back |
| Node.js 6 LTS | 2016 | ES6 support, production stability established |
| Node.js 12 | 2019 | V8 7.4, async/await goes mainstream |
| Node.js 18 LTS | 2022 | Native fetch API, built-in test runner |
| Node.js 22 LTS | 2024 | Current active LTS, significant performance improvements |
Node.js was built on Google's V8 engine and a non-blocking, event-driven I/O model. Instead of waiting for a database query to return before doing anything else, Node.js could register a callback and move on — handling thousands of concurrent connections on a single thread without the overhead of thread management.
It landed at exactly the right moment. JavaScript was already everywhere on the front end. Developers could suddenly use the same language on both sides of the stack. The npm ecosystem exploded. Real-time applications — chat apps, live dashboards, collaborative tools — became dramatically easier to build.
Node.js was not trying to replace PHP. It was solving a different problem: high-concurrency, real-time, I/O-heavy workloads. The fact that it could also serve web pages and APIs just meant it ended up in the same comparison over and over again.
The State of Both in 2026
PHP Today
| Factor | Detail |
|---|---|
| Current version | PHP 8.4 (stable), PHP 8.5 in active development |
| Primary framework | Laravel 11 — mature, full-featured, excellent developer experience |
| Runtime innovation | FrankenPHP — high-performance server in Go, enables persistent worker mode |
| Other frameworks | Symfony, Slim, CodeIgniter, Laminas |
| Market share | ~18.2% of developers (Stack Overflow Developer Survey 2025) |
| Web dominance | WordPress alone powers 43%+ of all websites globally |
Node.js Today
| Factor | Detail |
|---|---|
| Current version | Node.js 22.x LTS, Node.js 24 in active development |
| Primary frameworks | Fastify, NestJS, Hono, Express.js |
| Runtime competition | Bun and Deno have taken real market share, though Node.js remains dominant |
| Package registry | npm holds over 2.5 million packages |
| Language default | TypeScript is now the default in most production Node.js codebases |
| Corporate backing | Strong investment from Microsoft, Vercel, Netlify, and others |
Benchmark Setup
I ran all tests on the same machine with the same network conditions. Everything was containerized via Docker to eliminate environment differences.
Hardware
OS: Ubuntu 22.04 LTS
CPU: 4 vCPUs
RAM: 8 GB
Storage: SSD
Network: Loopback (localhost)
Load Testing Tools
Primary: wrk -t4 -c400 -d30s
Secondary: ab -n 10000 -c 200
Each test was run five times. The highest and lowest results were discarded. The three middle results were averaged.
Stacks Tested
| Stack | Runtime | Web Layer | Database Driver | Configuration |
|---|---|---|---|---|
| PHP | PHP 8.4-FPM | Nginx | PDO | OPcache ON, realpath_cache_size=4096K |
| Node.js | Node.js 22 LTS | Fastify 4.x | pg (node-postgres) | Default V8 flags |
Test Scenarios
| # | Test | What It Measures |
|---|---|---|
| 1 | Hello World | Pure runtime and framework overhead |
| 2 | JSON Serialization | Encoding a 100-field object |
| 3 | Database Read | SELECT 50 rows from PostgreSQL |
| 4 | Database Write | INSERT + RETURNING new row ID |
| 5 | CPU-Intensive Task | Fibonacci(35) computed synchronously per request |
Benchmark Results
Test 1 — Hello World (Pure Throughput)
wrk -t4 -c400 -d30s http://localhost/hello
| Stack | Requests/sec | Avg Latency | P99 Latency |
|---|---|---|---|
| PHP 8.4-FPM | 12,400 | 32ms | 89ms |
| PHP 8.4 + FrankenPHP (worker) | 29,100 | 13ms | — |
| Node.js 22 + Fastify | 38,200 | 10ms | 31ms |
Node.js was significantly faster in its standard configuration. PHP-FPM spawns worker processes with initialization overhead per request. Node.js runs as a persistent process.
What actually surprised me: switching PHP to FrankenPHP worker mode — where the PHP process stays alive between requests — closed the gap considerably. Not equal, but not the landslide most articles suggest.
Test 2 — JSON Serialization
| Stack | Requests/sec | Avg Latency |
|---|---|---|
| PHP 8.4-FPM | 9,800 | 40ms |
| Node.js 22 | 31,500 | 12ms |
Node.js wins. JavaScript's JSON handling is native to V8. PHP's json_encode() is fast, but the per-request process startup adds up.
Test 3 — Database Read (50 rows, PostgreSQL)
| Stack | Requests/sec | Avg Latency | P99 Latency |
|---|---|---|---|
| PHP 8.4-FPM | 4,200 | 95ms | 210ms |
| Node.js 22 | 5,800 | 68ms | 145ms |
Node.js is faster, but the gap narrows dramatically. The database is the bottleneck. Both stacks spend most of their time waiting on PostgreSQL, not executing application code. This test is far more representative of real web applications than the Hello World test.
Test 4 — Database Write (INSERT + RETURNING)
| Stack | Requests/sec | Avg Latency |
|---|---|---|
| PHP 8.4-FPM | 3,100 | 128ms |
| Node.js 22 | 4,400 | 90ms |
Same pattern as Test 3. Node.js ahead, but both are constrained by I/O, not the language runtime.
Test 5 — CPU-Intensive Task (Fibonacci 35)
This is the one that will change how you think about Node.js.
| Stack | Requests/sec | Avg Latency | P99 Latency |
|---|---|---|---|
| PHP 8.4-FPM | 890 | 1,120ms | 1,340ms |
| Node.js 22 | 210 | 4,750ms | 9,200ms |
PHP won — and it was not close.
This reveals the most important architectural truth about Node.js: it has a single-threaded event loop. When one request is doing heavy CPU work, every other request waits. PHP-FPM spawns multiple worker processes. A CPU-heavy request in one worker does not block any of the others.
Node.js has worker threads to address this — but you have to deliberately opt into them. Out of the box, a CPU-bound task will destroy your latency across the board.
Summary — All Tests
| Test | PHP 8.4 (req/s) | Node.js 22 (req/s) | Winner |
|---|---|---|---|
| Hello World (FPM) | 12,400 | 38,200 | Node.js |
| Hello World (FrankenPHP) | 29,100 | 38,200 | Node.js (narrower) |
| JSON Serialization | 9,800 | 31,500 | Node.js |
| DB Read — 50 rows | 4,200 | 5,800 | Node.js |
| DB Write — INSERT | 3,100 | 4,400 | Node.js |
| CPU Task — Fibonacci(35) | 890 | 210 | PHP |
Code Comparison
The same feature implemented in both stacks, side by side.
REST API Endpoint with Database Query and Cache
PHP 8.4 with Laravel
<?php
use App\Models\Article;
use Illuminate\Support\Facades\Cache;
Route::get('/articles/{id}', function (int $id) {
$article = Cache::remember("article:{$id}", 300, function () use ($id) {
return Article::with('author', 'tags')
->findOrFail($id);
});
return response()->json([
'data' => $article,
'cached' => Cache::has("article:{$id}"),
]);
});
Node.js with Fastify + TypeScript
import { FastifyInstance } from 'fastify';
import { pool } from '../db';
import { redis } from '../cache';
export async function articleRoutes(fastify: FastifyInstance) {
fastify.get<{ Params: { id: string } }>('/articles/:id', async (request, reply) => {
const { id } = request.params;
const cacheKey = `article:${id}`;
const cached = await redis.get(cacheKey);
if (cached) {
return reply.send({ data: JSON.parse(cached), cached: true });
}
const result = await pool.query(
`SELECT a.*, u.name AS author_name
FROM articles a
JOIN users u ON a.author_id = u.id
WHERE a.id = $1`,
[id]
);
if (!result.rows[0]) {
return reply.status(404).send({ error: 'Not found' });
}
await redis.setex(cacheKey, 300, JSON.stringify(result.rows[0]));
return reply.send({ data: result.rows[0], cached: false });
});
}
The PHP version is more concise because Laravel's Eloquent ORM absorbs the boilerplate. The Node.js version is more explicit — you see exactly what queries run and what hits the cache. Both are valid depending on your team's preferences.
Handling Concurrent Async Operations
PHP 8.4 — Guzzle with concurrent pooled requests
<?php
use GuzzleHttp\Client;
use GuzzleHttp\Pool;
use GuzzleHttp\Psr7\Request;
$client = new Client();
$requests = function () {
yield new Request('GET', 'https://api.example.com/users');
yield new Request('GET', 'https://api.example.com/products');
yield new Request('GET', 'https://api.example.com/orders');
};
$pool = new Pool($client, $requests(), [
'concurrency' => 3,
'fulfilled' => function ($response, $index) {
// handle each response
},
]);
$pool->promise()->wait();
Node.js — native async/await with Promise.all
const [users, products, orders] = await Promise.all([
fetch('https://api.example.com/users').then(r => r.json()),
fetch('https://api.example.com/products').then(r => r.json()),
fetch('https://api.example.com/orders').then(r => r.json()),
]);
This gap matters in daily development. The Node.js version is two lines. PHP requires a library, a generator function, a pool constructor, and a promise wait call. Async is not bolted onto Node.js — it is the entire foundation of it.
What Real Developers Say
"The vast majority of web applications are not performance-limited by their language runtime. They are limited by database queries, external API calls, and business logic complexity."
— Taylor Otwell, creator of Laravel
"I made some choices early in Node that I now regret. The module system, the callback model — these were harder than they needed to be."
— Ryan Dahl, creator of Node.js, from his Deno introduction (2018)
"Performance that developers cannot maintain or reason about is not actually useful performance."
— Evan You, creator of Vite and Vue.js
"Fastify's overhead is minimal. Most performance problems in Node.js applications come from userland code, not the framework."
— Matteo Collina, Node.js core contributor, co-creator of Fastify
Notice that Ryan Dahl — the person who built Node.js — publicly acknowledged its architectural regrets. That kind of self-awareness from a creator should factor into how you evaluate the runtime's design decisions.
Pros and Cons
PHP
Strengths
| Strength | Why It Matters |
|---|---|
| 30 years of documentation | Almost every problem has a Stack Overflow answer |
| Laravel framework | Best-in-class DX: Eloquent, queues, broadcasting, Horizon — batteries included |
| Universal shared hosting | Deploy for a few dollars a month, no DevOps knowledge required |
| Per-request FPM isolation | One crashed script does not bring down the entire server |
| Strong typing in PHP 8.x | Union types, enums, readonly properties, property hooks |
| CPU workload handling | Multi-worker FPM means CPU tasks in one process don't block concurrent requests |
| Low barrier to entry | Junior developers can onboard and contribute quickly |
Weaknesses
| Weakness | The Real Impact |
|---|---|
| FPM throughput ceiling | Slower than persistent-process runtimes on pure I/O benchmarks |
| Async is not native | Requires Swoole, ReactPHP, or FrankenPHP worker mode — not first-class support |
| Inconsistent standard library |
array_map, in_array, array_filter — argument order varies, no fixing it |
| Perception problem | The "PHP is bad" reputation still affects hiring and architectural buy-in |
| Real-time limitations | WebSockets and SSE require extra infrastructure in the traditional FPM model |
Node.js
Strengths
| Strength | Why It Matters |
|---|---|
| Non-blocking I/O | Purpose-built for high-concurrency, I/O-heavy workloads |
| Massive npm ecosystem | 2.5 million+ packages — there is a library for almost anything |
| Real-time native | WebSockets, SSE, live data pipelines — the event loop is ideal for these |
| TypeScript default | Large codebases are dramatically safer and easier to refactor |
| Full-stack JavaScript | Share types, schemas, and utilities across frontend and backend |
| Fastify and Hono | Framework overhead is genuinely minimal at scale |
| Strong corporate investment | Microsoft, Vercel, Netlify, and others fund core development actively |
Weaknesses
| Weakness | The Real Impact |
|---|---|
| Single-threaded event loop | CPU-bound tasks block all concurrent requests — this is an architectural constraint |
| Complex async error handling | Stack traces in async code are harder to read and debug than synchronous PHP |
| npm supply chain risk | Left-pad (2016) was a warning. Supply chain attacks on npm have increased since |
| Tooling overhead | TypeScript + ESLint + testing + bundler = significant configuration surface area |
| Memory leak risk | Long-running processes accumulate leaks that FPM's per-request model avoids |
| CommonJS vs ESM fragmentation | Module system debt has left many codebases in a painful, ongoing migration |
Use Case Guide
Use PHP When
| Scenario | Reason |
|---|---|
| Content-driven websites and CMS | WordPress, Drupal, Laravel — purpose-built with unmatched community support |
| SaaS products where shipping speed matters | Laravel gives you auth, queues, events, broadcasting scaffolding from day one |
| Small teams or less experienced developers | Simpler mental model, excellent onboarding docs, cheap shared hosting |
| CPU-intensive background jobs | FPM multi-worker model handles mixed workloads without blocking concurrent requests |
| Budget-constrained deployments | Shared PHP hosting remains one of the cheapest compute options available |
| E-commerce applications | Deep ecosystem (WooCommerce, Magento, Bagisto) — all PHP-native |
Use Node.js When
| Scenario | Reason |
|---|---|
| Real-time features | WebSockets, live notifications, collaborative editing — event loop is ideal |
| API gateway or thin proxy layer | Non-blocking I/O handles enormous concurrency with minimal resource usage |
| Microservices architecture | Fast startup, low idle memory, scales horizontally with ease |
| JavaScript/TypeScript-heavy teams | Share code, types, and validation logic across the full stack |
| Developer tools, CLIs, API clients | The npm ecosystem for tooling is exceptional and well-maintained |
| High-concurrency I/O without expensive infra | Hundreds of thousands of WebSocket connections on a single long-running process |
What the Numbers Actually Mean
After running all the benchmarks, the honest summary is this:
For most web applications — the kind that serve pages, handle form submissions, run database queries, and send emails — the performance difference between PHP and Node.js is not your bottleneck. Your database is your bottleneck. Your external API calls are your bottleneck. The framework overhead is rounding error compared to a missing index on a frequently queried column.
This is not a dismissal of performance. It is a prioritization of it.
The benchmark where PHP was three times slower than Node.js (Hello World) will never matter in a real application, because no real application serves only a Hello World response. The benchmark where the gap narrowed to 30% (database read) is far more representative — and even then, a Redis cache collapses most of that gap entirely.
The benchmark where PHP beat Node.js by 4x (CPU task) matters a great deal if your application does image processing, PDF generation, data transformation, or complex calculations in the request path. That result should directly inform your architecture decisions.
The right question is not which runtime is faster in a benchmark. It is which ecosystem will let your team ship correct, maintainable software faster — given the specific problem you are solving.
References
| Resource | Link | Why Read It |
|---|---|---|
| PHP 8.4 Release Notes | php.net/releases/8.4 | Understand modern PHP before forming opinions on old knowledge |
| FrankenPHP Documentation | frankenphp.dev | Worker mode changes the PHP performance conversation entirely |
| Fastify Benchmarks | fastify.dev/benchmarks | Reproducible, maintained Node.js framework benchmarks |
| Node.js User Survey (OpenJS Foundation) | openjsf.org | Annual adoption and usage data from the foundation |
| Stack Overflow Developer Survey 2025 | survey.stackoverflow.co/2025 | Most widely cited annual developer technology survey |
| Ryan Dahl — Introducing Node.js (2009) | youtube.com/watch?v=ztspvPYybIY | Watch to understand what problem it was actually solving |
| PHP: The Right Way | phptherightway.com | Counters a significant amount of outdated PHP advice |
| State of JavaScript 2024 | stateofjs.com/en-US/2024 | Annual JavaScript ecosystem adoption and satisfaction data |
| Swoole Documentation | swoole.com | Async PHP without abandoning your existing framework |
| Matteo Collina — Node.js Performance | YouTube | Technical and rigorous, directly from a Node.js core contributor |
The Verdict
I went into this expecting Node.js to win convincingly on performance and PHP to win on ecosystem maturity for web applications. I was partially right on both.
Node.js is faster in throughput benchmarks. That advantage is real and consistent. But it is smaller than conventional wisdom suggests, and it comes with a meaningful tradeoff on CPU-bound work. PHP with modern versions and FrankenPHP worker mode is no longer the slow runtime of 2012.
What surprised me most was not a benchmark result. It was realizing how rarely the benchmark result is even the right question.
| Use Case | Recommendation |
|---|---|
| Real-time platform (chat, live data) | Node.js |
| Content platform or CMS | PHP with Laravel |
| SaaS product, time-to-market priority | PHP with Laravel |
| High-throughput I/O microservice | Node.js |
| CPU-intensive processing in request path | PHP — or reconsider the architecture entirely |
| E-commerce | PHP |
| Developer tooling or CLI | Node.js |
| Budget-constrained team | PHP |
Both languages are alive. Both are actively developed. Both have communities worth being part of.
The "PHP is dead" narrative was never accurate. The "Node.js solves everything" narrative was always a sales pitch.
Use the right tool. Understand its actual tradeoffs. The verdict is not that one won — it is that the competition was never as simple as people made it sound.
All benchmarks were run on Ubuntu 22.04 LTS, 4 vCPU / 8 GB RAM, Docker containers with equivalent resource limits. PHP 8.4.1 with OPcache enabled and realpath_cache_size=4096K. Node.js 22.11.0 LTS with Fastify 4.x. PostgreSQL 16 on localhost. Five iterations per test — high and low discarded, middle three averaged.
Connect With the Author
| Platform | Link |
|---|---|
| ✍️ Medium | @syedahmershah |
| 💬 Dev.to | @syedahmershah |
| 🧠 Hashnode | @syedahmershah |
| 💻 GitHub | @ahmershahdev |
| Syed Ahmer Shah | |
| 🧭 Beacons | Syed Ahmer Shah |
| 🌐 Portfolio | ahmershah.dev |


Top comments (0)