Current situation: PHP2Shell. Two critical PHP vulnerabilities (CVSS 9.2) allow malicious code without login. Patches since 21 October 2026.What to do now →

Securing Nginx with PHP-FPM

With Nginx and PHP-FPM, security lies in pools, permissions and a clean restart. Here is how to patch after PHP2Shell and harden Nginx and PHP-FPM for good.

Last updated 5 min readServer & Hosting

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.

# 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.

# 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.

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().

; 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.
  • 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.
  • Protect status pages: make pm.status_path and ping.path reachable internally only.
# 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, we separate pools, permissions and upload directories cleanly.
  • If a break-in is suspected, we take over 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

Immediate contact

Every hour counts. Write to us now.

Briefly describe your system, PHP version and observation. We will get back to you, clarify the situation and work remotely until the incident is closed and documented.

Immediate help