Let’s get your server capable of running 100s or 1000s of concurrent PHP processes without blinking.

When people think about PHP and performance, memory tuning isn’t always the first thing that comes to mind. But if you’re running a website or app that needs to scale, especially one built with Laravel or WordPress, memory allocation and PHP-FPM tuning might be the difference between a snappy experience and a server meltdown.

Let’s walk through how to tune PHP and PHP-FPM for different memory environments — 8GB, 16GB, and 32GB — and understand the real cost of getting this wrong.

Memory Use in PHP Is About Scale, Not Just Size

Unlike C, you don’t manage memory in PHP by allocating and freeing it manually. Instead, PHP spins up a new process (or pool child) for each request, uses memory, and then recycles that memory when the request is done. This model is easy to reason about — until you start thinking in scale.

Every process uses memory. Multiply that by concurrent users, and things get expensive fast.

That means optimizing memory usage isn’t about maxing it out per process. It’s about reducing how much each process needs so you can run more of them simultaneously — without dipping into swap memory (a nightmare we’ll get to in a second).

The Cost of Getting It Wrong: Hello, Swap!

Let’s say your server has 8GB of RAM, and each PHP process uses 256MB. You’re capped at about 30 concurrent PHP workers — assuming nothing else is using RAM (which is never true). So, maybe you get 20–25 concurrent users before performance nosedives.

Now let’s say traffic spikes. More users hit the site, more processes are needed, and boom — you hit your RAM ceiling.

At that point, Linux starts using swap memory (aka: your hard drive as pretend RAM). It’s slow. Your CPU cries. Your site grinds.

You don’t want swap. It’s not a backup plan. It’s a red flag.

PHP-FPM + Memory = Throughput

Here’s where PHP-FPM tuning comes in. PHP-FPM is responsible for managing pools of PHP workers (aka: those individual processes). You tell it:

  • How many processes to start (pm.start_servers)
  • The max number it can spin up (pm.max_children)
  • And how aggressive it should be (pm.min_spare_serverspm.max_spare_servers)

But here’s the catch: Each process eats memory. So, pm.max_children * average memory per process must be less than your total usable RAM.

Let’s get practical.

Configuring for 8GB, 16GB, and 32GB Servers

Let’s assume your app uses ~100MB of memory per PHP process under load. You’ll want to confirm this by watching real usage with tools like tophtop, or ps.

Rule of Thumb: Leave at least 1GB of RAM for the OS and other processes (like MySQL, Redis, Nginx).

8GB Server

  • OS + other services: 1.5–2GB
  • Available for PHP: ~6GB
  • Memory per process: 100MB
pm = dynamic
pm.max_children = 60
pm.start_servers = 10
pm.min_spare_servers = 5
pm.max_spare_servers = 15

16GB Server

  • Available for PHP: ~12–13GB
pm = dynamic
pm.max_children = 120
pm.start_servers = 20
pm.min_spare_servers = 10
pm.max_spare_servers = 30

32GB Server

  • Available for PHP: ~28–29GB
pm = dynamic
pm.max_children = 280
pm.start_servers = 30
pm.min_spare_servers = 15
pm.max_spare_servers = 45

Don’t Forget About Nginx, Apache, and Your Database

When people calculate memory usage, they often obsess over PHP-FPM — and forget everything else.

If you’re running:

  • Nginx or Apache to serve assets, route requests, and proxy to PHP
  • MySQL or PostgreSQL to fetch and write data
  • RedisElasticSearch, or any other services

They all eat memory. And they eat more of it under load.

ServiceTypical Usage
Nginx50–150MB
Apache300MB–1GB+
MySQL500MB–2GB
Redis~100MB+

Your total usable RAM isn’t just for PHP-FPM. It’s shared. So budget accordingly.

The php.ini Memory Limit Is Per Worker — Not a Global Limit

Here’s a huge misconception that causes a lot of people to overload their servers:

“I’ll raise the memory limit in php.ini to 4GB so my app has more headroom.”

Wrong. That memory limit is per process — meaning every single PHP-FPM worker can eat up to 4GB.

So let’s say:

  • You’re on an 8GB VM
  • You set memory_limit = 4096M
  • You configure PHP-FPM with pm.max_children = 3

Boom. Three simultaneous users, and you’re already in swap. But, lets be honest, you probably set php.ini to 4GB and your max_childern to 200 (that is 400GB of expected memory).

A Better Approach

Set memory_limit in php.ini to something that reflects the real use-case of your app:

  • 64M–128M: For most typical PHP applications
  • 256M: If you have complex tasks or image processing
  • 512M+: Only if you’re doing large imports or PDF generation

Then, tune pm.max_children so that all your processes together don’t exceed your free memory after accounting for Nginx, the DB, and the OS.

Also, instead of raising the default, you can set the memory limit per request in PHP for things like PDF generation within the PHP file being accessed like this:

<?php
ini_set('memory_limit', '512M');

This lets you handle memory-hungry tasks (like PDF exports or CSV imports) without sacrificing concurrency across your whole application.

Then, tune pm.max_children so that all your processes together don’t exceed your free memory after accounting for Nginx, the DB, and the OS.

Running Multiple PHP Versions? Watch Your Idle Workers

If you’re hosting multiple apps on the same server — each with different PHP versions (like PHP 7.4, 8.1, 8.3) — you might assume they only eat memory when in use.

But here’s the catch:

Each version of PHP-FPM runs its own pool. That means its own workers. That means its own memory usage — even when idle.

If you’ve got:

  • php7.4-fpm with pm.max_children = 20
  • php8.1-fpm with pm.max_children = 20
  • php8.3-fpm with pm.max_children = 20

Now you’ve got 60 workers potentially consuming memory, even if most of them are idle. Depending on your memory_limit, that could mean 6–12GB+ of memory allocated before your apps do anything.

What To Do Instead

  • Only run the versions you need. Disable unused ones.
  • Use pm = ondemand for infrequently used versions.
  • Lower the pool settings on low-traffic versions.
pm = ondemand
pm.max_children = 5
pm.process_idle_timeout = 10s

Why Use pm = ondemand for Infrequently Used PHP Versions?

When you configure PHP-FPM, you choose how it manages worker processes using the pm setting. There are three modes:

ModeBehavior
staticSpawns exactly pm.max_children workers at all times
dynamicSpawns between pm.min_spare_servers and pm.max_children, scaling up
ondemandSpawns zero workers at rest, only creating them on incoming traffic

So if you have a PHP version installed just to support one legacy app that gets a few hits a day:

  • static or dynamic means workers are always sitting in memory, idle.
  • ondemand means no workers exist until they’re needed, and they shut down after inactivity.

This avoids wasting RAM on processes that aren’t doing anything.

Final Thoughts

PHP scales not by giving each request all the memory it wants, but by being stingy. With proper tuning, a 32GB server can handle hundreds of concurrent users (e.g., 400 at 70MB per process), while a 64GB server can exceed 1,000 (at 60MB each). Add proper caching, and you can support thousands of concurrent requests.

But if you misconfigure PHP-FPM or leave the default memory_limit at 512M — expect swap, slowdowns, and angry users.

Scale is about how little each request needs, not how much each request gets.

Get your memory and PHP-FPM config right. Your future self (and your server) will thank you.


Comments

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Get Involved & Explore More

an abstract painting with blue and yellow colors

Catch up on what I’ve been writing lately.

Show your gratitude.

Join Dare To Code Email List

Get emails from me on full-stack PHP development by subscribing to the Dare To Code mailing list.