This website uses cookies

Our website, platform and/or any sub domains use cookies to understand how you use our services, and to improve both your experience and our marketing relevance.

Cloudways Velocity is here. Deploy Node.js apps on managed, predictable infrastructure. Start Hosting Now →

Node.js vs PHP Hosting: Architecture, Benchmarks & Real-World Tests

Updated on October 28, 2026

12 Min Read
Two glowing server racks, left labeled Node.js in green and right labeled PHP in purple, connected by a network cable with a central signal icon.

Choosing between Node.js and PHP isn’t just about which language is easier to work with. The two handle requests differently, and those differences carry over into how the applications run on a server.

PHP has been used to power websites for decades, including platforms such as WordPress, Drupal, and Magento. Node.js took a different approach by bringing JavaScript to the server and using an event-driven model that works particularly well for applications handling lots of connections at once.

Rather than relying on the usual list of pros and cons, I want to see how the two behave in a more practical test. In this guide, I’ll first look at how PHP and Node.js handle requests and memory, then set up comparable database queries on Cloudways and run a load test with 1,000 concurrent requests.

What Are PHP and Node.js? The Core Differences

The biggest differences show up in how each runtime handles requests and keeps track of memory.

PHP: Request-by-Request Execution

PHP applications typically work around a short request-response cycle. When a request reaches a PHP application, the web server passes it to PHP-FPM, which uses one of its available workers to run the PHP code.

The basic flow looks like this:

Incoming Request → PHP-FPM Worker → Run Script → Query DB → Send Response → Request Ends

The PHP process doesn’t normally keep application state around between requests. Once the request is finished, that request’s memory is cleared.

There are some practical benefits to this approach. If a PHP script runs into a fatal error, the problem is generally limited to that request rather than taking down the entire application. The trade-off is that the application has to go through its startup and database connection work again for subsequent requests, depending on how the application and database connections are configured.

Node.js: A Long-Running Event Loop

Node.js works differently. A Node.js application normally starts once and stays running, with incoming requests handled through its event loop.

The basic flow looks like this:

Incoming Request → Event Loop → Non-Blocking I/O → Callback → Send Response
                         ↑                              │
                         └──────── Keeps Running ───────┘

JavaScript itself runs on a main thread, while operations such as database queries, file access, and network requests can be handled without blocking that thread.

Because the Node.js process stays alive, things such as database connection pools can remain available between requests. The application doesn’t have to recreate everything from scratch every time someone visits a page or calls an API endpoint.

That model works particularly well when an application needs to handle a large number of connections. There is a trade-off, though. A piece of code that blocks the event loop can hold up other requests, and an unhandled error that brings down the Node.js process can affect every connection being handled by that process.

Web Servers and Hosting Architecture

The two stacks also have different requirements when it comes to the web server sitting in front of the application.

The typical setups look something like this:

PHP:
User → NGINX / Apache → PHP-FPM → PHP Application

Node.js:
User → NGINX → Node.js Process → HTTP Server

The PHP Hosting Stack

A PHP application doesn’t normally listen directly for requests on ports 80 or 443. NGINX or Apache sits in front of it and passes PHP requests to PHP-FPM.

The main pieces are:

  • Web Server (NGINX / Apache): Handles incoming HTTP traffic, SSL/TLS, and static files such as images, CSS, and JavaScript.
  • PHP-FPM: Maintains a pool of PHP workers and assigns incoming PHP requests to them.

This is one reason PHP hosting has remained so straightforward. A typical PHP site can be uploaded to a server and served without having to keep a separate application process running or restart the application after every code change.

That simplicity is a big part of why PHP continues to be such a common choice for traditional websites and content management systems.

The Node.js Hosting Stack

Node.js includes a built-in http module, meaning a Node application is its own web server. It binds to a specific port (like 3000) and listens directly for incoming connections.

In a production hosting environment, running Node.js raw isn’t sufficient:

  • Process Manager (PM2): Keeps the Node.js application running in the background, handles automatic restarts if the process crashes, and load-balances across CPU cores using cluster mode.
  • Reverse Proxy (NGINX): Sits in front of the Node app to handle domain routing, SSL certificates, static file delivery, and rate limiting before passing traffic to localhost:3000.

Mini Project 1: Building & Deploying the Node.js Benchmark App

To test performance under identical conditions, I am building a lightweight Node.js script that connects to PostgreSQL, executes SELECT * FROM products LIMIT 10, and returns the dataset as JSON.

Step 1: Local Setup

I open Command Prompt (cmd) and add my standalone binary paths for Node.js and PostgreSQL to the system PATH for this terminal session:

set PATH=%PATH%;C:\Users\abdulrehman\Downloads\node-v24.18.0-win-x64\node-v24.18.0-win-x64;C:\Users\abdulrehman\Downloads\postgresql-18.4-2-windows-x64-binaries\pgsql\bin

Command Prompt with Node.js and PostgreSQL binary paths added to the system PATH

Next, I create a dedicated directory named node-benchmark, move into it, and initialize a new Node.js project:

mkdir node-benchmark
cd node-benchmark

Command Prompt creating the node-benchmark directory and moving into it

npm init -y

Terminal output after running npm init -y to initialize the Node.js project

npm install pg

Terminal output after installing the pg package with npm

I open the project folder in Visual Studio Code, create a file named server.js, and add the following code to configure an HTTP server with database connection pooling:

const http = require('http');
const { Pool } = require('pg');

const pool = new Pool({
  connectionString: process.env.DATABASE_URL || 'postgresql://postgres:@localhost:5432/postgres',
  max: 20
});

const server = http.createServer(async (req, res) => {
  if (req.url === '/api/users' && req.method === 'GET') {
    try {
      const client = await pool.connect();
      const result = await client.query('SELECT * FROM products LIMIT 10');
      client.release();

      res.writeHead(200, { 'Content-Type': 'application/json' });
      res.end(JSON.stringify(result.rows));
} catch (err) {
  console.error('PostgreSQL Connection Error:', err); // <-- Add this line
  res.writeHead(500, { 'Content-Type': 'application/json' });
  res.end(JSON.stringify({ error: 'Database query failed' }));
}
  } else {
    res.writeHead(404, { 'Content-Type': 'text/plain' });
    res.end('Not Found');
  }
});

const PORT = process.env.PORT || 3000;
server.listen(PORT, () => {
  console.log(`Node.js server listening on port ${PORT}`);
});

Visual Studio Code with server.js containing the Node.js HTTP server and PostgreSQL pool

To test the server locally before deploying, I run node server.js in my terminal and verify that http://localhost:3000/api/users returns my catalog data.

Terminal running node server.js with the server listening on port 3000
Browser showing JSON product data from the localhost api/users endpoint

Step 2: Deploying to Cloudways Velocity

With the local test working, I push the node-benchmark directory to a new GitHub repository.

GitHub repository created for the node-benchmark project
node-benchmark project files pushed to the GitHub repository
GitHub repository page with the node-benchmark code committed

In my Cloudways dashboard, I launch a new Velocity Node.js Application and link my GitHub repository.

Cloudways dashboard option to launch a new Velocity Node.js application
Velocity setup linking the GitHub repository to a Cloudways Node.js application
Velocity deployment setup with the GitHub repository and branch selected
Velocity detecting the Node.js environment for the Cloudways application
Velocity configuration page with detected environment and build settings

Velocity automatically detects the Node environment, and sets up process management behind PM2.

But…for this demo, I’ll change the Environment Type Setup to Server-Side Rendering (SSR). And also change Package Manager to npm, Build Command to npm run build and Entry File to server.js.

Velocity settings set to SSR with npm package manager and server.js entry file

Then I hit Deploy Now and wait for 5-6 minutes for the deployment to finish.

Deploy Now button in the Cloudways Velocity deployment settings
Velocity deployment progress on Cloudways after clicking Deploy Now

Once the app is deployed, I go to Access Details and connect the PostgreSQL database to my app.

Cloudways Access Details tab for connecting a PostgreSQL database to the app
PostgreSQL database connection options in the Velocity application
PostgreSQL database credentials listed under Access Details in Cloudways

I head to Environment Variables in the Velocity deployment settings and add my live database connection string:

DATABASE_URL = postgresql://[username]:[password]@localhost:5432/[database_name]

I save the settings and trigger a redeployment.

Environment Variables section in the Velocity deployment settings
DATABASE_URL environment variable added in Velocity settings
Velocity redeployment running after saving the environment variable

Step 3: Verification

Once Cloudways completes the redeploy, I open the database manager and run a SQL script to create the products table and seed it with initial sample data.

CREATE TABLE products (
    id SERIAL PRIMARY KEY,
    name VARCHAR(255) NOT NULL,
    description TEXT,
    price DECIMAL(10, 2) NOT NULL,
    image_url TEXT,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

INSERT INTO products (name, description, price, image_url) VALUES
('Minimalist Desk Lamp', 'Matte black architectural lighting.', 89.00, 'https://images.unsplash.com/photo-1507473885765-e6ed057f782c'),
('Ergonomic Wooden Chair', 'Crafted from sustainable oak.', 249.00, 'https://images.unsplash.com/photo-1580481072645-022f9a6d83d0');

Cloudways database manager with SQL script to create the products table
Products table seeded with sample data in the PostgreSQL database manager

Next, I open my live Velocity application URL in the browser:

https://node-app.cloudwaysnodeapps.com/api/users

Live Velocity application URL opened in the browser

The browser renders the JSON array containing the product catalog pulled straight from PostgreSQL.

Browser displaying the JSON array of products pulled from PostgreSQL

I can also verify if the products are added to my PostgreSQL database by launching the database manager.

Database manager showing the products table entries in PostgreSQL

Mini Project 2: Building & Deploying the PHP Benchmark App

Unlike Node.js, PHP doesn’t require initializing a package manager or writing a custom HTTP server loop. The web server (NGINX/Apache with PHP-FPM) handles request routing automatically, so I only need a single script file.

Step 1: Local Setup

I open a fresh Command Prompt window and create a separate project directory named php-benchmark:

mkdir php-benchmark
cd php-benchmark

Command Prompt creating the php-benchmark project directory

I open this directory in Visual Studio Code, create a file named index.php, and use PHP’s native PDO (PHP Data Objects) extension to query the database.

To keep the environments standard, the PHP application will use its own native MariaDB database rather than connecting remotely to the Node.js PostgreSQL instance.

Initially, I set up the connection with placeholder credentials:

<?php
header('Content-Type: application/json');

// Ensure you update 'your_password_here' before pushing!
$host = getenv('DB_HOST') ?: '127.0.0.1';
$db   = getenv('DB_NAME') ?: 'your_DB_name';
$user = getenv('DB_USER') ?: 'your_DB_user';
$pass = getenv('DB_PASS') ?: 'your_password_here';
$port = getenv('DB_PORT') ?: '3306';

$dsn = "mysql:host=$host;port=$port;dbname=$db;charset=utf8mb4";

try {
    $pdo = new PDO($dsn, $user, $pass, [
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, // Fixed: Added the double colon
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        PDO::ATTR_EMULATE_PREPARES => false,
    ]);

    $stmt = $pdo->query('SELECT * FROM products LIMIT 10');
    $products = $stmt->fetchAll();

    echo json_encode($products);
} catch (Throwable $e) {
    echo json_encode([
        'error' => 'Database query failed',
        'details' => $e->getMessage()
    ]);
}

Step 2: Deploying to Cloudways LAMP

I go back to Cloudways and launch a standard Custom PHP (LAMP) application on a server instance identical in specs to my Node setup.

Cloudways dashboard launching a Custom PHP (LAMP) application
Custom PHP (LAMP) application settings on a server matching the Node.js setup

I push my php-benchmark directory to GitHub and connect it to my Cloudways application via the Deployment via GIT tab.

Deployment via GIT tab in the Cloudways PHP application
Git deployment options in the Cloudways dashboard for the PHP application
Cloudways Git deployment dashboard before generating SSH keys

Then, to authenticate securely:

  • I click Generate SSH Keys and then View SSH Keys in the Cloudways Git deployment dashboard and copy the generated public key.

Generate SSH Keys and View SSH Keys buttons in the Cloudways Git dashboard
Generated public SSH key displayed in the Cloudways Git deployment dashboard
Public SSH key copied from the Cloudways Git deployment dashboard

  • In GitHub, I navigate to my repo Settings, click on Deploy keys and add my key that I copied earlier.

Settings menu of the php-benchmark GitHub repository
Deploy keys section in the GitHub repository settings
Add deploy key form in GitHub with the Cloudways public key pasted
Deploy key added to the php-benchmark GitHub repository

  • Back in Cloudways, I enter my repository’s SSH URL ([email protected]:abdulrehman293/php-benchmark.git) into the GIT Remote Address field and click Authenticate.

GIT Remote Address field and Authenticate button in Cloudways

  • Once authenticated, I select the main branch and click Start Deployment to pull the code into the server.

Start Deployment button with the main branch selected in Cloudways Git deployment

Step 3: Linking the Database

Once the application is launched, I navigate to the Access Details tab and copy my live MariaDB credentials (DB Name, Username, and Password).

MariaDB credentials listed in the Access Details tab of the Cloudways PHP app

I update my local index.php file with these actual credentials, push the commit to GitHub, and click Pull in the Cloudways Git dashboard to update the live server.

Updated index.php file with the live MariaDB credentials in the editor
Pull button in the Cloudways Git dashboard updating the live PHP application

To finalize the setup, I launch the Cloudways Database Manager for this PHP app and run the same SQL script to create and seed the products table.

CREATE TABLE products (
    id SERIAL PRIMARY KEY,
    name VARCHAR(255) NOT NULL,
    description TEXT,
    price DECIMAL(10, 2) NOT NULL,
    image_url TEXT,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

INSERT INTO products (name, description, price, image_url) VALUES
('Minimalist Desk Lamp', 'Matte black architectural lighting.', 89.00, 'https://images.unsplash.com/photo-1507473885765-e6ed057f782c'),
('Ergonomic Wooden Chair', 'Crafted from sustainable oak.', 249.00, 'https://images.unsplash.com/photo-1580481072645-022f9a6d83d0');

Cloudways database manager with SQL script to create the products table in MariaDB
Products table seeded with sample data in the MariaDB database manager

Step 4: Verification

To confirm complete parity, I navigate to my live PHP application URL:

https://phpstack-1070279-6647761.cloudwaysapps.com/

The browser outputs the exact same JSON dataset returned by the Node.js application, proving both environments are perfectly mirrored and ready for stress testing.

Browser displaying the same JSON product data from the live PHP application

And if I launch the database manager in Cloudways, I can see the table and the entries in it.

Database manager showing the products table and entries in MariaDB

Benchmarking & Comparison: The 1,000 Concurrent Request Stress Test

With both endpoints live on Cloudways, serving mirrored JSON payloads but querying their respective native databases (PostgreSQL for Node.js, MariaDB for PHP), I ran the final load test.

Since the live targets require HTTPS, I used the SSL-enabled version of Apache Bench (abs) from the Windows command line to send 1,000 total requests with a concurrency level of 100 parallel connections (-c 100).

Node.js Load Test Command:

abs -n 1000 -c 100 https://nodejs-1666768-6647448.cloudwaysnodeapps.com/api/users

Apache Bench results for 1,000 requests at 100 concurrency against the Node.js endpoint

PHP Load Test Command:

abs -n 1000 -c 100 https://phpstack-1070279-6647761.cloudwaysapps.com/

Apache Bench results for 1,000 requests at 100 concurrency against the PHP endpoint

Metric Node.js (PostgreSQL) PHP 8.2 (MariaDB)
Requests Per Second (RPS) 9.87 req/sec 6.85 req/sec
Time per Request (Across Concurrent) 101.27 ms 146.08 ms
Time per Request (Mean Total) 10,126.90 ms 14,608.50 ms
P95 Latency 11,391 ms 13,934 ms
Failed Requests 0 0

What the Results Mean

  • Throughput Advantage: The Node.js stack delivered roughly 44% higher throughput than the PHP stack. Node.js processed nearly 10 requests per second under heavy concurrent load, completing the full test in about 101 seconds compared to PHP’s 146 seconds.
  • Connection Pooling: A key reason for this performance delta comes down to database connection handling. Node.js keeps its database connection pool open in memory, reusing existing sockets for incoming traffic during the 1,000-request barrage.
  • PHP-FPM Overhead: Standard PHP natively tears down state after every request. Even with MariaDB sitting on the same local server, spawning multiple worker processes to handle 100 parallel hits and repeatedly negotiating new database connections inherently adds latency.
  • Perfect Stability: Despite the intense concurrency and the heavy 10,000+ ms absolute response times under load, both Cloudways environments maintained complete stability. Neither server buckled, finishing with exactly zero failed requests.

Wrapping Up

PHP and Node.js are both capable backend technologies, but they fit different types of applications.

PHP makes a lot of sense for traditional websites, CMS-based projects, and applications that benefit from its large ecosystem. Node.js is a better fit for applications that rely heavily on APIs, real-time connections, or a large number of concurrent I/O operations.

I’ve made the source code and database schemas for both benchmark applications available on GitHub. The Node.js benchmark repository and PHP benchmark repository can be cloned and deployed to another environment to run the same tests.

If you have questions about server configuration, PHP-FPM tuning, or PM2 process management on Cloudways, leave a comment below.

Q1: Which is better, JavaScript or PHP?

Neither is better for every project. JavaScript works well for modern web apps and real-time features, while PHP is a strong choice for websites and CMS platforms such as WordPress and Drupal.

Q2: Is PHP still useful?

Yes. PHP still powers a large part of the web and remains widely used for WordPress, WooCommerce, Laravel, and other web applications.

Q3: Is Python or Node.js better?

It depends on the project. Node.js is a good fit for APIs and real-time applications, while Python is often preferred for data processing, automation, and machine learning.

Q4: Is Node.js just a server?

No. Node.js is a JavaScript runtime that lets JavaScript run outside a web browser. It can be used to build web servers, APIs, command-line tools, scripts, and other applications.

Share your opinion in the comment section. COMMENT NOW

Share This Article

[email protected]

Abdul is a tech-savvy, coffee-fuelled, and creatively driven marketer who loves keeping up with the latest software updates and tech gadgets. He's also a skilled technical writer who can explain complex concepts simply for a broad audience. Abdul enjoys sharing his knowledge of the Cloud industry through user manuals, documentation, and blog posts.

×

Webinar: How to Get 100% Scores on Core Web Vitals

Join Joe Williams & Aleksandar Savkovic on 29th of March, 2021.

Do you like what you read?

Get the Latest Updates

Share Your Feedback

Please insert Content

Thank you for your feedback!

Do you like what you read?

Get the Latest Updates

Share Your Feedback

Please insert Content

Thank you for your feedback!

Want to Experience the Cloudways Platform in Its Full Glory?

Take a FREE guided tour of Cloudways and see for yourself how easily you can manage your server & apps on the leading cloud-hosting platform.

Start my tour