PHP-FPM Deep Dive — How Nginx Runs WordPress PHP
We now move from Nginx to the PHP execution layer.
The complete flow is:
Browser
↓
DNS
↓
IP
↓
TCP 443
↓
Nginx
↓
FastCGI
↓
PHP-FPM
↓
WordPress
↓
MySQL
The key concept:
Nginx does not execute PHP. PHP-FPM executes PHP for Nginx.
1. What Is PHP?
PHP is a server-side programming language.
WordPress is primarily written in PHP.
For example:
index.php
wp-load.php
wp-settings.php
are PHP files.
The browser does not normally receive the PHP source code.
Instead:
PHP source
↓
PHP execution
↓
HTML
↓
Browser
2. Why PHP-FPM Exists
Nginx is excellent at:
HTTP
HTTPS
static files
connections
proxying
But it doesn’t execute PHP.
Therefore:
Nginx
↓
FastCGI
↓
PHP-FPM
↓
PHP
3. PHP-FPM
PHP-FPM means:
PHP FastCGI Process Manager
It manages PHP worker processes.
Conceptually:
PHP-FPM
│
├── Worker 1
├── Worker 2
├── Worker 3
└── Worker 4
Each worker can execute PHP requests.
4. Check PHP-FPM
On Ubuntu, first check which PHP version is installed:
php -v
Then:
systemctl list-units --type=service | grep php
You may see something similar to:
php8.3-fpm.service
The exact version on your server may be different.
5. Check the Service
For example:
sudo systemctl status php8.3-fpm
If your installed version differs, substitute it.
You want:
Active: active (running)
6. PHP CLI vs PHP-FPM
These are different.
PHP CLI
php
runs PHP from the command line.
PHP-FPM
php-fpm
runs PHP workers that applications such as Nginx can communicate with.
So:
CLI
=
command-line PHP
while:
FPM
=
web-request PHP execution
7. PHP-FPM Master Process
PHP-FPM generally has a master process.
Conceptually:
PHP-FPM
│
└── Master
│
├── Worker
├── Worker
├── Worker
└── Worker
The master manages the worker pool.
8. Worker Processes
When PHP requests arrive:
Nginx
↓
PHP-FPM
↓
available worker
↓
execute PHP
The worker processes the PHP request.
9. Why Multiple Workers?
Suppose:
User A → WordPress
User B → WordPress
User C → WordPress
User D → WordPress
You don’t want one PHP process to handle everything serially.
Multiple workers allow concurrent processing.
Conceptually:
Request A → Worker 1
Request B → Worker 2
Request C → Worker 3
Request D → Worker 4
10. PHP-FPM Pool
A group of PHP workers is called a:
Pool
A PHP-FPM installation can have multiple pools.
For example:
PHP-FPM
│
├── www pool
├── site1 pool
├── site2 pool
└── site3 pool
11. Why Pools Matter for Hosting
If you have:
site1.com
site2.com
site3.com
you could use one shared pool:
All websites
↓
www pool
or separate pools:
site1 → site1 pool
site2 → site2 pool
site3 → site3 pool
For a hosting platform, separate pools can provide stronger isolation and more controllable resource limits.
12. Shared Pool
Simple architecture:
Nginx
│
▼
PHP-FPM
│
▼
www pool
│
├── site1
├── site2
└── site3
Advantages:
simple
fewer processes
easy management
Disadvantages:
weaker isolation
one busy site can consume shared PHP capacity
13. Separate Pools
More isolated:
Nginx
│
├── site1 → PHP pool 1
├── site2 → PHP pool 2
└── site3 → PHP pool 3
Advantages:
better isolation
different users
different limits
different PHP settings
Disadvantages:
more configuration
more process-management complexity
more memory overhead
14. Hosting Architecture
For your CresignSys hosting platform, the long-term architecture can be:
site1
├── Linux user
├── files
├── Nginx config
├── PHP-FPM pool
├── PHP socket
└── database
site2
├── Linux user
├── files
├── Nginx config
├── PHP-FPM pool
├── PHP socket
└── database
15. Unix Socket
Nginx and PHP-FPM can communicate using a Unix socket.
Example:
/run/php/site1.sock
Flow:
Nginx
↓
Unix socket
↓
PHP-FPM
16. Why Unix Socket?
When both services are on the same server, a Unix socket is often a simple and efficient local communication mechanism.
You don’t need a network IP/port between Nginx and PHP-FPM.
17. TCP Socket
PHP-FPM can also listen on TCP.
For example:
127.0.0.1:9000
Then:
Nginx
↓
127.0.0.1:9000
↓
PHP-FPM
18. Unix vs TCP
For your single-server hosting setup:
Unix socket
is often convenient.
For distributed architectures:
TCP
can be more appropriate.
For example:
Nginx Server
↓
network
↓
PHP Application Server
requires network communication.
19. PHP-FPM Configuration
Configuration locations vary by PHP version.
A common structure is:
/etc/php/<version>/fpm/
with:
php-fpm.conf
pool.d/
Inside:
pool.d/
you may find:
www.conf
20. Default Pool
A common default pool is:
www
with configuration such as:
/etc/php/8.x/fpm/pool.d/www.conf
The exact path depends on your installed PHP version.
21. Pool Configuration
A simplified pool configuration might look like:
[site1]
user = site1
group = site1
listen = /run/php/site1.sock
Then process-management settings can be added.
22. User and Group
This is extremely important for hosting.
Suppose:
site1
has its own Linux user.
PHP-FPM workers for that pool can run as:
site1
instead of:
www-data
This improves isolation between websites.
23. Why User Isolation Matters
Suppose:
site1
site2
are owned by different customers.
If both PHP applications run as:
www-data
then filesystem separation can become more difficult.
With:
site1 → user1
site2 → user2
you can enforce stronger boundaries.
24. Example
/storage/websites/
│
├── site1.com/
│ └── public/
│
└── site2.com/
└── public/
Ownership:
site1.com → site1
site2.com → site2
PHP:
site1 pool → site1
site2 pool → site2
This creates a consistent security model.
25. Nginx Worker User
Nginx may still run workers as:
www-data
while PHP runs as:
site1
This is possible if the relevant filesystem and socket permissions are configured correctly.
26. Socket Permission Problem
Suppose PHP-FPM creates:
/run/php/site1.sock
owned by:
site1:site1
If Nginx cannot access it:
Nginx
↓
socket permission denied
↓
502 Bad Gateway
This is one of the most important causes of PHP-related 502 errors.
27. Socket Ownership
Pool configuration can specify socket ownership and permissions.
Conceptually:
listen.owner = www-data
listen.group = www-data
listen.mode = 0660
This is only an example; the correct ownership model depends on how you isolate Nginx and PHP-FPM.
28. Process Manager
PHP-FPM has process management settings.
The most important modes are:
static
dynamic
ondemand
29. static
With:
pm = static
PHP-FPM maintains a fixed number of worker processes.
For example:
pm.max_children = 5
means approximately five worker processes are maintained.
30. dynamic
With:
pm = dynamic
PHP-FPM adjusts the number of workers within configured limits.
Important settings include:
pm.max_children
pm.start_servers
pm.min_spare_servers
pm.max_spare_servers
31. ondemand
With:
pm = ondemand
workers are created when requests arrive and can be terminated after being idle.
This can be useful for sites with relatively low or irregular traffic.
32. pm.max_children
This is one of the most important settings.
Example:
pm.max_children = 10
It limits the maximum number of simultaneous PHP worker processes in that pool.
33. Why This Matters
Suppose one PHP request consumes:
100 MB RAM
and you allow:
10 workers
Very roughly:
10 × 100 MB
=
1000 MB
plus:
Nginx
MySQL
OS
cache
other processes
So PHP worker limits must be based on actual memory usage, not arbitrary large numbers.
34. Too Few Workers
Suppose:
pm.max_children = 2
but:
20 requests
arrive simultaneously.
Only two PHP requests can execute at once.
The others wait.
This can increase response time.
35. Too Many Workers
Suppose:
pm.max_children = 100
but your VPS has limited RAM.
If PHP workers consume significant memory:
100 workers
↓
RAM exhaustion
↓
swap/OOM
↓
slow server
↓
possible crashes
Therefore:
More PHP workers does not automatically mean more performance.
36. CPU Also Matters
PHP workers consume:
RAM
CPU
Some requests are CPU-intensive.
For example:
large WordPress plugin
image processing
complex database operations
can consume significant resources.
37. Worker Concurrency
Think:
pm.max_children
=
maximum PHP concurrency for that pool
It is not simply:
number of visitors
One visitor can generate multiple requests.
And not every request requires PHP.
38. Static Files Don’t Need PHP
For:
logo.png
style.css
script.js
Nginx can often serve them directly.
Therefore:
100 static requests
do not necessarily mean:
100 PHP workers
39. WordPress Dynamic Requests
Requests such as:
/wp-admin/
login
uncached pages
POST requests
AJAX/API operations
may require PHP.
The exact behavior depends on the application and caching configuration.
40. PHP-FPM Queue
If all workers are busy:
Request
↓
PHP-FPM
↓
no free worker
↓
wait
Too much waiting can produce slow responses and eventually upstream timeouts.
41. pm.max_requests
Another useful setting:
pm.max_requests = 500
This tells a worker to process a limited number of requests before being recycled.
Why?
Long-running PHP processes can sometimes accumulate memory or other state.
Recycling workers can help mitigate certain forms of gradual memory growth.
42. pm.start_servers
For dynamic mode:
pm.start_servers = 3
controls how many workers are started initially.
43. pm.min_spare_servers
Example:
pm.min_spare_servers = 2
controls the minimum number of idle workers maintained in dynamic mode.
44. pm.max_spare_servers
Example:
pm.max_spare_servers = 5
controls the maximum number of idle workers maintained.
45. Example Pool
A simplified educational example:
[site1]
user = site1
group = site1
listen = /run/php/site1.sock
pm = dynamic
pm.max_children = 10
pm.start_servers = 2
pm.min_spare_servers = 2
pm.max_spare_servers = 5
pm.max_requests = 500
These numbers are examples, not recommended defaults for your VPS.
46. Resource Calculation
Before choosing:
pm.max_children
measure actual PHP worker memory usage.
For example:
ps --no-headers -o rss,cmd -C php-fpm8.3
The exact process name depends on your PHP version.
47. RSS
RSS means:
Resident Set Size
It provides an approximation of how much physical memory a process currently occupies.
For PHP-FPM tuning, this is useful.
48. Better Principle
Don’t say:
My VPS has 8 GB RAM, so I’ll set 50 PHP workers.
Instead:
Available RAM
↓
reserve RAM for OS
↓
reserve RAM for MySQL
↓
reserve RAM for Nginx
↓
reserve RAM for other services
↓
remaining RAM
↓
measure PHP worker size
↓
calculate reasonable concurrency
49. Example Calculation
Suppose:
VPS RAM = 8 GB
Reserve approximately:
OS + services = 2 GB
MySQL = 2 GB
Remaining:
4 GB
If average PHP worker usage is:
100 MB
then a theoretical upper bound is roughly:
4000 / 100
=
40 workers
But you should not automatically configure 40.
You need safety margin and real workload measurements.
50. WordPress Can Vary Greatly
One PHP request might use:
50 MB
while another might use:
250 MB
depending on:
plugins
theme
queries
image processing
API calls
application state
Therefore average and peak behavior matter.
51. PHP-FPM Slow Requests
PHP-FPM can be configured with a:
Slowlog
This helps identify requests that take too long.
A slowlog can help you discover:
slow plugin
slow WordPress operation
slow PHP script
database-related delay
52. Request Timeout
Nginx also has upstream timeout settings.
Conceptually:
Nginx
↓
waiting for PHP-FPM
↓
too long
↓
timeout
This can produce:
504 Gateway Timeout
53. 502 vs 504
Remember:
502
Often:
Nginx
↓
can't properly communicate with upstream
504
Often:
Nginx
↓
upstream didn't respond in time
These are clues, not absolute diagnoses.
54. Check PHP-FPM Logs
Depending on your installation:
sudo journalctl -u php8.3-fpm
or:
sudo journalctl -u php8.3-fpm --since "30 minutes ago"
Replace the version with your installed PHP-FPM service.
55. Check Nginx Errors
sudo tail -f /var/log/nginx/error.log
Then load the website.
Watch what appears.
This is an excellent troubleshooting technique.
56. Real-Time Debugging
Open terminal 1:
sudo tail -f /var/log/nginx/error.log
Open terminal 2:
curl -I https://learn.cresignsys.com
Now observe whether Nginx reports an error.
57. PHP-FPM Status
You can also inspect:
systemctl status php8.3-fpm
Then:
ps aux | grep php-fpm
You may see:
master process
pool site1
pool site2
58. One Pool Per Website
Your future CresignSys architecture could look like:
PHP-FPM
│
├── site1.com
│ ├── user = site1
│ └── socket = site1.sock
│
├── site2.com
│ ├── user = site2
│ └── socket = site2.sock
│
└── site3.com
├── user = site3
└── socket = site3.sock
59. Nginx Mapping
Then:
site1.com
↓
Nginx server block
↓
site1.sock
↓
site1 PHP-FPM pool
and:
site2.com
↓
Nginx server block
↓
site2.sock
↓
site2 PHP-FPM pool
60. Stronger Isolation
Now imagine:
site1
is compromised through a vulnerable plugin.
If its PHP processes run as:
site1
rather than a shared hosting user, access to other sites can be more strongly restricted by filesystem permissions.
This is one reason shared-hosting systems isolate accounts.
61. Important Security Rule
Do not assume:
different directories
=
security isolation
If all applications run under the same powerful user:
www-data
one compromised application may potentially access other files that user can read.
User/process isolation is therefore important in multi-tenant hosting.
62. Database Isolation Too
The same principle applies to MySQL.
Instead of:
all websites
↓
one database
↓
one powerful database user
prefer:
site1
↓
database1
↓
dbuser1
site2
↓
database2
↓
dbuser2
with only the required privileges.
63. Complete Per-Site Isolation Model
A strong hosting model looks like:
SITE 1
│
├── Linux user
├── filesystem
├── Nginx server block
├── PHP-FPM pool
├── PHP socket
├── database
└── database user
SITE 2
│
├── Linux user
├── filesystem
├── Nginx server block
├── PHP-FPM pool
├── PHP socket
├── database
└── database user
64. Your Hosting Automation
When you create a new domain:
create-site example.com
your automation can eventually perform:
1. Create Linux user
2. Create website directories
3. Set ownership
4. Create PHP-FPM pool
5. Create PHP socket
6. Create Nginx configuration
7. Validate Nginx
8. Reload Nginx
9. Create MySQL database
10. Create database user
11. Install WordPress
12. Configure SSL
65. The Most Important PHP-FPM Settings
At your current level, memorize:
user
group
listen
pm
pm.max_children
pm.start_servers
pm.min_spare_servers
pm.max_spare_servers
pm.max_requests
66. Their Meaning
user
→ Which Linux user executes PHP?
group
→ Which Linux group?
listen
→ Where does Nginx connect?
pm
→ How are PHP workers managed?
pm.max_children
→ Maximum simultaneous PHP workers
pm.start_servers
→ Initial workers
pm.min_spare_servers
→ Minimum idle workers
pm.max_spare_servers
→ Maximum idle workers
pm.max_requests
→ Requests before recycling a worker
67. The Complete PHP Request
Now combine everything:
Browser
↓
HTTPS
↓
Nginx
↓
location /
↓
try_files
↓
index.php
↓
FastCGI
↓
PHP-FPM socket
↓
PHP-FPM pool
↓
PHP worker
↓
WordPress
↓
MySQL
68. Where 502 Happens
Nginx
↓
PHP-FPM
If this communication fails:
502 Bad Gateway
Investigate:
PHP-FPM running?
↓
socket exists?
↓
socket permissions?
↓
correct socket path?
↓
correct PHP-FPM pool?
69. Where 504 Happens
Usually:
Nginx
↓
PHP-FPM
↓
long-running request
If the upstream takes too long:
504 Gateway Timeout
Investigate:
slow PHP
slow WordPress
slow plugin
slow MySQL
external API
resource exhaustion
70. Lesson 060 — Core Principle
Remember this:
Nginx receives the web request; PHP-FPM provides the PHP execution environment.
The architecture is:
INTERNET
│
▼
NGINX
│
FastCGI │
▼
PHP-FPM
│
┌──────┴──────┐
▼ ▼
Site 1 Site 2
Pool 1 Pool 2
│ │
▼ ▼
WordPress WordPress
│ │
▼ ▼
MySQL 1 MySQL 2
This is the foundation for turning your Ubuntu VPS into a proper multi-website hosting server.
Next Lesson — 061
MySQL for Hosting — Databases, Users, Privileges & WordPress
We will next learn:
MySQL
↓
Database
↓
Table
↓
Row
↓
Column
↓
Database user
↓
Privileges
↓
WordPress wp-config.php
↓
PHP → MySQL
Then we will design the correct one-database + one-database-user-per-website model for your CresignSys hosting platform and learn how to create it safely from the command line.
Leave a Reply