# Isolating PHP-FPM Pools per Website

> Isolate PHP-FPM pools per website: check existing pools, protect sockets, limit extensions, hide status pages and set limits for each pool.

Source: https://phpsos.de/en/blog/php-fpm-pools-isolieren/  
As of: 25/10/2026  
Provider: PHP SOS, PHP emergency help and server security. Immediate help via WhatsApp: https://wa.me/491736494914?text=Hello%20PHP%20SOS%2C%20we%20need%20help%20with%20our%20PHP%20server.%20Company%3A%20%E2%80%A6%20Website%3A%20%E2%80%A6

**In short:** Every website on a server should have its own PHP-FPM pool with its own system user and its own socket. The separation only becomes effective through the details: socket permissions for the web server only, `security.limit_extensions` set to `.php`, no publicly reachable status pages, separate limits for processes and runtime, and separate logs. Against PHP2Shell itself only the update protects; pools limit how far an attack reaches.

## One server, many websites, one shared risk

On many servers, over the years, something has grown that nobody planned that way. The company website, the shop, a campaign page from 2019 and a test system run side by side, often under the same user and in the same pool. If the forgotten campaign page is compromised, the shop's customer data lies open as well.

PHP-FPM, PHP's FastCGI process manager, solves this with pools: groups of worker processes with their own settings. How the basic principle works is explained in [PHP-FPM explained: process manager, pools and workers](https://phpsos.de/en/php/php-fpm-erklaert/). The setup of separate pools with their own user, their own temp folder and `open_basedir` is described in the guide [open_basedir and isolation per website](https://phpsos.de/en/ratgeber/open-basedir/). This article goes one step further: how do you check existing pools, and which settings make the separation robust?

An important note on classification: with PHP2Shell, the vulnerabilities CVE-2026-109618 and CVE-2026-109619 lie in the PHP interpreter, which all pools of a PHP version share. A separate pool does not prevent exploitation. But it decides whether an attacker afterwards reaches one website or all of them.

## PHP-FPM pools with their own user: checking the existing setup

Before you change anything, get an overview. The following commands only read. Paths and version numbers differ by distribution:

```bash
# All pools with user and socket
grep -HE '^\s*(\[|user|group|listen)\s*' /etc/php/*/fpm/pool.d/*.conf

# Running pools and their users
ps -eo user,pid,cmd | grep '[p]hp-fpm: pool'

# Sockets and their permissions
ls -l /run/php/

# Check the configuration for errors (changes nothing)
sudo php-fpm8.3 -t
```

Watch for three patterns: several websites in the same pool, pools that run as `www-data` or even as `root`, and sockets that are writable for all users. Each of these undermines the separation.

If several PHP versions run on the server, each has its own FPM service with its own pool directories. Check all of them, not just the version you set up last. Old versions with forgotten pools in particular are a risk, because they are often neither maintained nor restarted after updates. Notes on this are found under [Several PHP versions on one server](https://phpsos.de/en/blog/mehrere-php-versionen-server/). Record the result in a small table: website, pool, user, socket, PHP version. This overview is also the basis for every later change.

## The most important settings per pool

The following overview is based on the FPM configuration in the PHP manual. Values for processes and times depend on your load and application:

| Setting | Purpose | Recommendation |
|---|---|---|
| `user`, `group` | user of the workers | separate user per website, never `root` |
| `listen.owner`, `listen.group`, `listen.mode` | permissions on the socket | web server group only, mode `0660` |
| `listen.acl_users` | access via ACL instead of owner | list only the web server user |
| `security.limit_extensions` | executable extensions | limit to `.php` (default `.php .phar`) |
| `clear_env` | clear environment variables | leave at the default `yes` |
| `pm.max_children` | maximum number of workers | limit so that one website cannot overload the server |
| `request_terminate_timeout` | abort hanging requests | set to match the longest legitimate request |
| `pm.status_path`, `ping.path` | status and ping page | set only if needed, and secure internally |
| `slowlog`, `request_slowlog_timeout` | log of slow requests | own file per pool |

An excerpt for a hardened pool looks like this:

```ini
; /etc/php/8.3/fpm/pool.d/shop.conf (excerpt)
[shop]
user = shop
group = shop
listen = /run/php/shop.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660

pm = dynamic
pm.max_children = 20
pm.start_servers = 4
pm.min_spare_servers = 2
pm.max_spare_servers = 6
request_terminate_timeout = 60s

security.limit_extensions = .php
clear_env = yes

slowlog = /var/log/php/shop.slow.log
request_slowlog_timeout = 10s
php_admin_value[error_log] = /var/log/php/shop.error.log
```

Settings such as `memory_limit` or `session.upload_progress.enabled` are set per pool with `php_admin_value` and `php_admin_flag`. According to the manual, these values cannot be overridden with `ini_set()`. More on this under [php_admin_value: locking settings against overrides](https://phpsos.de/en/blog/php-admin-value-sperren/).

## Putting status pages and worker recycling in context

`pm.status_path` and `ping.path` are active only if they are set. The status page shows running requests, processes and load. This is helpful for monitoring, but publicly reachable it is an unnecessary source of information for attackers. Forward these paths in the web server only for internal addresses. How Nginx and PHP-FPM work together securely is described in [Securing Nginx with PHP-FPM](https://phpsos.de/en/server/nginx-php-fpm/).

`pm.max_requests` determines after how many requests a worker restarts; the default is `0`, meaning never. A value above zero helps against slowly growing memory consumption. It is not intended as protection against PHP2Shell: php.net does not list it among the interim measures and stresses that only the update protects completely. After the update, it also applies that all workers of all pools must be restarted, because old processes would otherwise keep running with the vulnerable code.

> **Tip:**
> Maintain a template for new pools and set up every new website with it. That way the separation comes about automatically instead of being retrofitted years later.

## Typical mistakes during the changeover

The most common mistake is a new pool that runs under its own user while its files still belong to `www-data`. The pool then cannot write where it must, and someone sets the permissions too generously. Clarify beforehand which folders must be writable. The second mistake is scheduled tasks that keep running under the old user and create files with the wrong owner. The third is a shared session folder for all pools, which cancels the separation again.

After every changeover, test the website's login, uploads, caches and scheduled tasks, and watch the pool's error log.

## How does PHP SOS help?

- We record all pools, users, sockets and settings and show where websites share processes or permissions today.
- We set up a hardened pool for each website, with socket permissions, an extension limit, resource limits and separate logs.
- We secure status pages, restart all workers in a controlled way and test every website.
- As part of [server hardening](https://phpsos.de/en/leistungen/server-haertung/), we document the separation as a template for future projects.


## Frequently asked questions

**Why should every website have its own PHP-FPM pool?**  
A separate pool with its own system user separates the websites at the level of processes and file permissions. If one website is compromised, the code cannot simply read the files of the others. Limits and settings can also be defined separately for each website.

**Does a separate pool protect against PHP2Shell?**  
No. The vulnerabilities lie in the PHP interpreter itself, which all pools share. Only the update to a fixed version, with a restart of all workers, offers protection. Separate pools do, however, limit how far a successful attack reaches.

**What does security.limit_extensions do?**  
According to the PHP manual, the setting limits which file extensions PHP-FPM executes as the main script. The default is .php and .phar. Limiting it to .php prevents, for example, uploaded image files from being executed as PHP if the web server is misconfigured.

**Is the PHP-FPM status page dangerous?**  
It reveals details about running processes and requests. If it is publicly reachable without protection, it makes orientation easier for attackers. According to the manual it is active only if pm.status_path is set. In that case the web server should restrict the path to internal addresses.

## Sources

- [PHP manual: FPM configuration](https://www.php.net/manual/en/install.fpm.configuration.php)
- [Nginx documentation](https://nginx.org/en/docs/)
- [BSI IT-Grundschutz (German Federal Office for Information Security)](https://www.bsi.bund.de/DE/Themen/Unternehmen-und-Organisationen/Standards-und-Zertifizierung/IT-Grundschutz/it-grundschutz_node.html)
