PHP-FPM: How Many Workers Your Site Needs to Stay Fast
Each site starts with just five workers. Here is how to give it more, use sockets, and put the cache to work. One edit in the pool file does the job.
The default PHP-FPM pool caps child tasks at five. When web load jumps, that cap builds a queue. Hits then drop.
On phone networks in India, big bursts clog a pool fast. When each child stalls, pages slow down. Search ranks drop with them.
You can fix this on a Linux VPS. You do not need new hardware. You need a pool of the right size. You need fast Unix sockets. You need good OPcache rules. This guide walks through each rule. Each number comes from a live web node.
How PHP-FPM works.
PHP-FPM stands for FastCGI Process Manager. It links your web server to your PHP code.
When a guest opens your site, Nginx gets the first TCP call. Nginx reads the call headers. It sends the script path to PHP-FPM over a local socket.
- The master process. Runs as the root user. It reads the pool file. It binds sockets and manages child tasks.
- Child processes. Run as a normal user like www-data. Each child serves one web call at a time.
- The request flow. A child runs the script. It runs DB queries. It builds the HTML and sends it back to Nginx.
- Child reset. After a task ends, the child wipes its state. Then it waits for the next call.
Incoming Web Traffic
│
▼
┌──────────────┐ Unix Domain Socket ┌────────────────────────────────┐
│ Nginx Server │ ────────────────────────────> │ PHP-FPM Master Process (Root) │
└──────────────┘ (/run/php/php8.3-fpm.sock) └───────────────┬────────────────┘
│
┌────────────────────────────┼────────────────────────────┐
▼ ▼ ▼
┌────────────────────┐ ┌────────────────────┐ ┌────────────────────┐
│ PHP Worker 1 │ │ PHP Worker 2 │ │ PHP Worker N │
│ (Active - 65 MB) │ │ (Active - 68 MB) │ │ (Idle - 60 MB) │
└────────────────────┘ └────────────────────┘ └────────────────────┘
When all child tasks are busy, PHP-FPM puts new hits in a queue. The queue holds them. If that queue fills up, Nginx drops the call. It then sends a 502 Bad Gateway error to the browser.
Sizing pm.max_children.
Short answer: Take the OS and DB RAM out of total RAM. Then divide the rest by the RAM one child uses.
Set pm.max_children too low and you waste free power. Set it too high and you drain RAM. Then the Linux OOM killer stops your DB or web host.
Measure child RAM.
To set your pool cap, find the RAM one child uses under full load.
Run this shell pipe on your managed app hosting node.
ps --no-headers -o "rss,cmd" -C php-fpm8.3 | awk '{ sum+=$1; count++ } END { if (count > 0) print "Average Worker RSS:", sum/count/1024, "MB (Total Workers:", count, ")"; else print "No active PHP-FPM workers found" }'
RSS (Resident Set Size) shows the RAM the task holds. On a clean WordPress site, a base child takes 45 MB to 85 MB. Heavy plugins can push this to 110 MB per child.
The pool size rule.
Use this rule to set your pool cap.
pm.max_children = (Total Server RAM - OS Buffer - Database Allocation) / Average Worker Process RSS
- OS Buffer. Keep 300 MB to 700 MB free for the Linux kernel, SSH, and Nginx.
- DB Reserve. Leave room for the buffer pool. Do this if MySQL or MariaDB share one node.
| VPS Tier | Total RAM | OS & DB RAM | PHP RAM | Avg Child RAM | pm.max_children | Peak Hits |
|---|---|---|---|---|---|---|
| Starter | 1 GB | 350 MB | 650 MB | 65 MB | 10 | Up to 120 peak hits. |
| Growth | 2 GB | 600 MB | 1,400 MB | 70 MB | 20 | Up to 260 peak hits. |
| Pro | 4 GB | 1,200 MB | 2,800 MB | 70 MB | 40 | Up to 500 peak hits. |
| Business | 8 GB | 2,500 MB | 5,500 MB | 75 MB | 72 | Up to 1000 peak hits. |
| Enterprise | 16 GB | 4,000 MB | 12,000 MB | 80 MB | 150 | Up to 2200 peak hits. |
Note: Peak numbers assume full page caching. They also need TTFB tuning on live pages.
Process manager modes.
In short: Use static for web nodes with flat load. Use dynamic for mixed hosts.
The pool directive (pm) sets how PHP-FPM builds and drops child tasks.
1. Static mode.
In static mode, PHP-FPM starts each child at boot. It keeps them in RAM.
- Pros. Children skip fork delay. Response times stay flat when load jumps.
- Cons. Children hold the same RAM even when the site is calm.
Choose static if the node runs web apps only. You must know its RAM.
2. Dynamic mode.
In dynamic mode, PHP-FPM scales child counts up and down. It stays inside set bounds.
pm.start_servers: How many tasks start at boot.pm.min_spare_servers: The floor for spare tasks.pm.max_spare_servers: The cap for spare tasks.pm.max_children: The top cap of active tasks.
Choose dynamic when web and DB apps share one host.
3. Ondemand mode.
In on demand mode, no tasks run while the site is calm. When a guest arrives, PHP-FPM forks a child. It kills it after a short wait.
- Pros. It uses almost no RAM when idle.
- Cons. It adds 15 ms to 35 ms of fork lag. The first guest on a cold pool waits that long.
Choose on demand for test sites or admin panels.
| Feature | static | dynamic | ondemand. |
|---|---|---|---|
| RAM use | Always high. | Varies. | Low when calm. |
| Fork lag | 0 ms. | Low. | High (15-35 ms). |
| Burst defense | Top tier. | High. | Fair. |
| Best role | Web nodes. | Live VPS. | Test apps. |
The pool file.
To edit your pool, open the pool file. Use any text editor.
sudo nano /etc/php/8.3/fpm/pool.d/www.conf
Here is a tested pool block for a 4 GB RAM Linux host. It runs web and DB tools.
[www]
; Process identity
user = www-data
group = www-data
; Socket binding
listen = /run/php/php8.3-fpm.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660
listen.backlog = 1024
; Process Manager settings
pm = dynamic
pm.max_children = 40
pm.start_servers = 10
pm.min_spare_servers = 8
pm.max_spare_servers = 16
pm.max_requests = 1000
pm.process_idle_timeout = 10s
; Execution limits
request_terminate_timeout = 60s
request_slowlog_timeout = 5s
slowlog = /var/log/php-fpm/www-slow.log
; Status and health checks
pm.status_path = /fpm-status
ping.path = /fpm-ping
ping.response = pong
; Environment isolation
catch_workers_output = yes
decorate_workers_output = no
clear_env = no
Why pm.max_requests matters.
Look at the line pm.max_requests = 1000. Long running tasks can leak small bits of RAM. Big plugin sets make it worse.
pm.max_requests tells each child to respawn after 1,000 tasks. This clears split heap RAM. No hit drops. No line stalls.
Socket backlog.
When you set listen.backlog = 1024, check that your kernel allows a queue that deep. Check your current limit.
sysctl net.core.somaxconn
If the value is low, add this rule to /etc/sysctl.d/99-networking.conf.
net.core.somaxconn = 1024
Apply the setting now. No reboot needed.
sudo sysctl -p /etc/sysctl.d/99-networking.conf
Unix vs TCP sockets.
In short: Unix sockets skip the TCP stack. They cut lag by 15 percent over TCP loopbacks.
You can link Nginx to PHP-FPM two ways. Use a TCP loopback link (127.0.0.1:9000). Or use a local Unix socket file (/run/php/php8.3-fpm.sock).
Socket paths.
- TCP Loopback (
127.0.0.1:9000). Needs packet framing, TCP checksum tests, and port binds. Under high load, port limits can stall hits. - Unix Socket (
unix:/run/...). It works in kernel RAM buffers as a file inode. Data moves with no packet overhead.
TCP Loopback Path (Higher CPU overhead):
Nginx ──> TCP Packet Framing ──> IP Checksum ──> Local Routing ──> TCP Handshake ──> PHP-FPM
Unix Domain Socket Path (Direct kernel stream):
Nginx ──> Kernel File Descriptor Buffer Copy ──> PHP-FPM
| Metric | TCP Socket (127.0.0.1:9000) | Unix Socket | Speed gain. |
|---|---|---|---|
| Handshake | 3-way packet setup. | Direct kernel link. | Zero lag. |
| RAM copy | Network stack pack. | Kernel buffer stream. | 15% less CPU. |
| Port risk | Can fail under load. | Not an issue. | No port clash. |
| Round trip | 4.8 ms. | 4.1 ms. | 14% faster. |
Nginx setup.
In your Nginx host block, route PHP calls through the socket path.
location ~ \.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
# Connect directly to the Unix domain socket
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
fastcgi_index index.php;
# FastCGI parameter configuration
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
# Buffer tuning for high concurrency
fastcgi_buffer_size 128k;
fastcgi_buffers 4 256k;
fastcgi_busy_buffers_size 256k;
fastcgi_temp_file_write_size 256k;
# Timeout protection
fastcgi_connect_timeout 30s;
fastcgi_send_timeout 60s;
fastcgi_read_timeout 60s;
}
Make sure file rights match on both tools. Set listen.owner = www-data and listen.mode = 0660. Nginx can then read and write the socket. No permission errors.
OPcache rules.
OPcache saves you from parsing raw PHP code on each HTTP hit. It keeps the compiled code in shared RAM.
Open the OPcache file.
sudo nano /etc/php/8.3/fpm/conf.d/10-opcache.ini
Apply these tested lines.
[opcache]
; Enable Zend OPcache engine
opcache.enable = 1
opcache.enable_cli = 0
; Memory allocation (in Megabytes)
opcache.memory_consumption = 192
opcache.interned_strings_buffer = 16
; Maximum cached scripts
opcache.max_accelerated_files = 20000
; Cache validation rules
opcache.validate_timestamps = 1
opcache.revalidate_freq = 60
; Optimization flags
opcache.save_comments = 1
opcache.fast_shutdown = 1
opcache.enable_file_override = 1
Key OPcache settings.
opcache.memory_consumption = 192: Gives 192 MB of RAM to hold built code. For WordPress app hosting, normal plugins use 80 MB to 140 MB.opcache.max_accelerated_files = 20000: Top count of cached scripts. WordPress core and plugins hold 4,000 to 12,000 files.opcache.validate_timestamps = 1withopcache.revalidate_freq = 60: Checks for file edits once per minute, not on each hit. This saves thousands of disk calls per minute.
On servers that run code deploys, set opcache.validate_timestamps = 0. That gives top speed. Just flush the cache during each deploy.
sudo systemctl reload php8.3-fpm
Pool status.
PHP-FPM ships a status page. It shows live call counts, queue depth, and child states.
1. Status path.
Add a status block to your Nginx config.
location = /fpm-status {
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
# Restrict access to local requests only
allow 127.0.0.1;
allow ::1;
deny all;
}
Reload Nginx to apply the rule.
sudo nginx -t && sudo systemctl reload nginx
2. Pool health.
Check your pool metrics in the host shell.
curl -s http://127.0.0.1/fpm-status?full
You will see output like this.
pool: www
process manager: dynamic
start time: 20/Oct/2026:10:00:00 +0530
start since: 3600
accepted conn: 14250
listen queue: 0
max listen queue: 3
listen queue len: 1024
idle processes: 12
active processes: 4
total processes: 16
max active processes: 28
max children reached: 0
slow requests: 0
Key pool metrics.
- listen queue. Must stay at
0. A positive number shows hits in line. All tasks are full. - max children reached. If this counter grows, your
pm.max_childrencap has been hit. Check free RAM. Then raise the cap. - active processes. How many tasks run code now. Compare it with the total to check pool load.
- slow requests. Tracks scripts that ran longer than your
request_slowlog_timeoutlimit.
Fixing 502 and 504 errors.
When PHP speed dips, Nginx logs show the root cause.
Case 1: 502 Bad Gateway.
Look in /var/log/nginx/error.log for this common line.
[error] 1420#1420: *8451 connect() to unix:/run/php/php8.3-fpm.sock failed (111: Connection refused)
Root cause: PHP-FPM stopped or crashed.
Check the system journal to see why it died.
sudo journalctl -u php8.3-fpm -n 50 --no-pager
If the service died fast, check if the kernel killed it. The cause is often low RAM.
sudo dmesg -T | grep -i -E "oom[-_]killer|out of memory"
If the OOM killer stopped a child, pm.max_children is too high for your RAM. Cut it by 25% and restart the service.
Case 2: 504 Gateway Timeout.
Look for this line in your web logs.
[error] 1420#1420: *9812 upstream timed out (110: Connection timed out) while reading response header from upstream
Root cause: A child took the call but did not finish. The fastcgi_read_timeout expired first.
This often happens when a remote API hangs. Or a slow DB query locks table rows.
Check the slow query log. It shows the script that caused the stall.
sudo tail -n 40 /var/log/php-fpm/www-slow.log
The trace stack points to the call that held the child back.
Deploy checklist.
Tuning PHP-FPM turns a slow node into a fast, steady host. It stays online through big load spikes.
Before you push your edits live, run this six point list:
- Measure child RAM. Run the ps and awk pipe to find your true RSS size.
- Set
pm.max_children. Use this rule: (Free RAM - Buffer) / Child Size. - Use Unix sockets. Route Nginx calls through
/run/php/php8.3-fpm.sockinstead of a loopback port. - Tune OPcache. Set
opcache.memory_consumptionto at least 128 MB. Set the file cap to 20,000. - Check socket backlogs. Confirm
listen.backlog = 1024inwww.confandnet.core.somaxconn = 1024in your sysctl file. - Test and reload. Always check them before you apply changes.
sudo php-fpm8.3 -t && sudo nginx -t
sudo systemctl reload php8.3-fpm nginx
Hosting in India on fast cloud hosts cuts lag. It works well across Indian networks. You get 7-day backup retention. You get a 30 days money-back guarantee. You get month-to-month terms. You keep full control of your stack.
Need help with high traffic web stacks or custom cloud hosts? Message our team on WhatsApp for quick tech advice.