# Securing Nginx with PHP-FPM

> Nginx PHP-FPM security after PHP2Shell: check pools and versions, restart all workers, set interim values via php_admin_value and harden Nginx.

Source: https://phpsos.de/en/server/nginx-php-fpm/  
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:** With Nginx and PHP-FPM, the vulnerability from PHP2Shell sits in PHP-FPM, not in Nginx. Patch all PHP-FPM versions, restart every FPM service completely and prove it with process start times. For lasting Nginx PHP-FPM security, what counts is separate pools, values that cannot be overridden and an Nginx configuration that does not execute uploaded files.

## How Nginx and PHP-FPM work together

Nginx serves static files itself and passes requests for PHP scripts on to PHP-FPM via FastCGI. PHP-FPM consists of a master process and several worker processes, grouped into pools. A worker handles many requests one after another.

That is exactly what matters for PHP2Shell. With CVE-2026-109619, internal state of a request can persist after memory has been freed and be used by a later request in the same worker. With CVE-2026-109618, an out-of-memory condition can interrupt PHP in the middle of initializing a request. Both vulnerabilities are rated CVSS v4.0 9.2 and are fixed in PHP 8.5.12, 8.4.27, 8.3.36 and 8.2.35.

## Check the inventory: versions, pools, processes

Many servers have several PHP-FPM versions in parallel. The following commands only read; package and service names are examples for Debian/Ubuntu and the RHEL family.

```bash
# Which FPM services are running?
systemctl list-units --type=service | grep -i fpm

# Which version is behind them? (example Debian/Ubuntu)
php-fpm8.3 -v
# RHEL family
php-fpm -v

# To which sockets or ports does Nginx pass requests?
nginx -T 2>/dev/null | grep -E 'fastcgi_pass'

# Pools and their users
grep -RhE '^\[|^user|^listen' /etc/php/*/fpm/pool.d/ 2>/dev/null
```

Match each `fastcgi_pass` entry against a running pool. That way you can see which PHP version serves which website. For distribution packages, the changelog with the CVE numbers proves the patch, not the version number alone.

A common finding in such inventories is servers on which several PHP versions were installed over the years, only one of which is actually in use. The remaining FPM services keep running anyway, often with default pools that listen on a local socket or port. They are usually not directly reachable from outside, but they remain an unnecessary risk and lengthen every emergency update. Disable and remove versions that no website uses, after you have verified the assignment via the `fastcgi_pass` entries.

It is equally worth looking at the pools' user. If all pools run as `www-data` or `nginx`, the separation between the websites exists only on paper. Whoever takes over one website can then read the files of all the others, such as configuration files with database passwords. Separate system users per pool and suitable file permissions create a real boundary here. This is no remedy for PHP2Shell, but it limits the possible damage of a successful attack considerably.

## Apply the patch and renew all workers

After the package update, restart every PHP-FPM service. A reload does replace the workers, but a full restart also renews the master process and is the safe route after security updates.

```bash
# Example Debian/Ubuntu, for each installed version
systemctl restart php8.3-fpm
# RHEL family
systemctl restart php-fpm

# Check: start time of master and workers
ps -eo pid,lstart,cmd | grep -E 'php-fpm' | grep -v grep
```

Every process must have been started after the update. This is described in detail under [Restarting PHP-FPM and all workers](https://phpsos.de/en/php2shell/php-fpm-neustart/).

> **Important:**
> A patch does not remove malware that was planted earlier. Keep the Nginx access and error logs from 20 October 2026 onward and check the websites for traces.

## Setting interim values in the pool

As long as a version is not patched, php.net recommends interim measures. They do not offer complete protection. In PHP-FPM, you set the values with `php_admin_value` or `php_admin_flag` in the pool; applications then cannot override them via `ini_set()`.

```ini
; Example: /etc/php/8.3/fpm/pool.d/website.conf
php_admin_flag[session.upload_progress.enabled] = off
php_admin_value[post_max_size] = 32M
php_admin_value[upload_max_filesize] = 24M
```

Without an upload function, php.net gives `post_max_size = 8M`. On PHP 8.5, `fatal_error_backtraces=0` is added. For the note on `memory_limit=-1` you need a memory limit at cgroup level, for example via `MemoryMax` in a systemd drop-in for the FPM service. In Nginx, adjust `client_max_body_size` to the new `post_max_size` so that users get understandable error messages. Then restart FPM.

## Improving Nginx PHP-FPM security for the long term

Beyond the acute case, a few basic rules help limit the damage from future vulnerabilities:

- **One pool per website** with its own system user, its own socket and its own `open_basedir` boundary. More under [open_basedir and isolation per website](https://phpsos.de/en/ratgeber/open-basedir/).
- **Set socket permissions** so that only Nginx can access them, and do not open any FPM ports to the outside.
- **Execute only existing scripts:** set `try_files $uri =404;` in the PHP location so that Nginx does not pass on paths that do not exist.
- **Do not execute uploads:** forbid PHP execution in upload directories. Notes on this are under [Implementing file uploads securely](https://phpsos.de/en/ratgeber/datei-upload-sicher/).
- **Protect status pages:** make `pm.status_path` and `ping.path` reachable internally only.

```nginx
# Example: no PHP execution in the upload directory
location ~* ^/uploads/.*\.php$ {
    deny all;
}
```

Check every change with `nginx -t` before you reload Nginx. Also record which pools, versions and special rules exist on the server. This short documentation saves valuable time at the next critical vulnerability, because nobody has to search first for which website is attached to which pool.

## How does PHP SOS help?

- We assign every website to its pool and PHP version and check the actual package state.
- We patch, restart all FPM services and document process start times as evidence.
- Until the patch, we set the interim values per pool and limit memory via cgroup.
- As part of [server hardening](https://phpsos.de/en/leistungen/server-haertung/), we separate pools, permissions and upload directories cleanly.
- If a break-in is suspected, we take over [incident response](https://phpsos.de/en/leistungen/incident-response/).


## Frequently asked questions

**Is systemctl reload enough for PHP-FPM after an update?**  
A reload renews the workers, but the master process may keep the old code. After a security update, a full restart of the service is the safe route. Afterwards, check the start time of the master and the workers.

**Does Nginx have to be restarted after a PHP update?**  
Nginx itself contains no PHP and does not have to restart for the PHP patch. All PHP-FPM services must restart. If you change the Nginx configuration at the same time, check it with nginx -t before reloading.

**Why separate pools per website?**  
Each pool runs with its own user and its own limits. If one website is compromised, the attacker cannot readily reach the files of the others. Interim values can also be set specifically per pool.

**Is pm.max_requests a protection against PHP2Shell?**  
No, it does not replace a patch. The value limits how many requests a worker handles before it is replaced. That can shorten the lifetime of state in the process, but it is not a remedy named by php.net.

## Sources

- [php.net: Security announcements](https://www.php.net/)
- [PHP 8 ChangeLog](https://www.php.net/ChangeLog-8.php)
- [OWASP Cheat Sheets](https://cheatsheetseries.owasp.org/)
- [BSI (German Federal Office for Information Security)](https://www.bsi.bund.de/)
