Skip to content
All articles

Isolating Websites with PHP-FPM Pools: One Site, One User

When every site on a server shares one PHP user, one hacked plugin can reach them all. Per-site PHP-FPM pools contain the damage. Here is how.

M By · 5 min read
Isolating Websites with PHP-FPM Pools: One Site, One User

If one server hosts several websites, giving each site its own PHP-FPM pool running as its own Linux user is one of the most effective ways to contain a break-in. When every site runs as the same user, an attacker who gets into one outdated WordPress plugin can usually read and modify every other site on the server, including their database passwords. With one pool and one system user per site, plus correct file permissions, the attacker is largely stuck inside the site they broke into. This guide explains how that works, what to configure, and where the limits are.

What PHP-FPM and pools are

PHP-FPM (FastCGI Process Manager) is the standard way to run PHP behind web servers such as Nginx, Apache and LiteSpeed. Instead of PHP running inside the web server, FPM keeps a set of PHP worker processes ready and the web server passes requests to them.

A pool is a group of those workers with its own settings. The PHP manual puts it simply: "With FPM you can run several pools of processes with different setting." The key per-pool settings include:

  • user and group: the Linux account the workers run as (mandatory for each pool).
  • listen: the socket or port where the pool accepts requests from the web server.
  • pm and pm.max_children: how many worker processes the pool may run.
  • php_admin_value / php_admin_flag: PHP settings for that pool that scripts cannot override with ini_set().

Why "one site, one user" contains damage

Linux decides what a process can touch based on the user it runs as. That makes the pool's user setting the real security boundary.

Shared user (common on older or quick setups): every site's PHP runs as www-data or apache, and every site's files are readable (often writable) by that account. A web shell uploaded through one vulnerable plugin can browse the whole web directory, read every wp-config.php or .env file, and plant code in every site.

Separate users: site A's PHP runs as site_a, site B's as site_b. If site A is compromised, the attacker's code runs as site_a. With proper permissions, it cannot read site B's files or credentials, and it cannot write malware into site B.

This also helps with cleanup. When only one site is affected, you restore and clean one site instead of auditing an entire server.

Server hardware in a data center rack

What a per-site pool looks like

A typical pool file (for example /etc/php/8.4/fpm/pool.d/site_a.conf on Debian or Ubuntu; paths vary by distribution and control panel):

[site_a]
user = site_a
group = site_a

listen = /run/php/site_a.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660

pm = ondemand
pm.max_children = 10
pm.process_idle_timeout = 10s
pm.max_requests = 500
request_terminate_timeout = 120s

security.limit_extensions = .php

php_admin_value[open_basedir] = /home/site_a:/tmp/site_a
php_admin_value[session.save_path] = /home/site_a/sessions
php_admin_value[upload_tmp_dir] = /tmp/site_a
php_admin_value[error_log] = /home/site_a/logs/php-error.log
php_admin_flag[log_errors] = on
php_admin_flag[display_errors] = off

What each part does, in plain language:

  • Own user and group: the core of the isolation.
  • Own Unix socket with 0660 permissions: only the web server's account can talk to this pool. The PHP manual warns that an exposed FastCGI endpoint "allows arbitrary code execution," and recommends a Unix socket when the web server is on the same host. Never expose an FPM port to the internet.
  • pm.max_children: caps how many workers one site can run, so a traffic spike or attack on one site can't consume all the server's capacity.
  • pm.max_requests: recycles workers periodically, which keeps memory leaks in third-party code in check.
  • request_terminate_timeout: kills requests that run too long.
  • security.limit_extensions = .php: FPM only executes .php files. The default also allows .phar; the manual recommends limiting FPM to .php.
  • Separate sessions, temp and logs: sessions in a shared world-readable /tmp can be read by other sites' users, so give each pool its own directory.
  • php_admin_value: locks those settings so the site's own code cannot loosen them.

File permissions are half of the job

A separate PHP user does nothing if every site's files are world-readable. The usual pattern:

  1. Each site's home directory is owned by its user and not readable by other users (for example mode 0750 or 0710, with the web server given access through its group or a specific ACL).
  2. Configuration files with secrets (wp-config.php, .env) are readable only by the site user, for example 0600 or 0640.
  3. Code files are not writable by the PHP process unless the application truly needs it. Where possible, deploy code as a different user than the one PHP runs as, and give PHP write access only to upload and cache folders.

Control panels such as cPanel/WHM, Plesk and others can create per-account PHP-FPM pools for you. Check that the option is actually enabled for every account, and that older sites were migrated to it.

Know the limits

Per-site pools are a strong, practical improvement, but they are not a complete wall:

  • Shared OPcache. The PHP manual states it plainly: "Pools are not a security mechanism, because they do not provide full separation; e.g. all pools would use a single OPcache instance." Settings such as opcache.validate_permission and opcache.validate_root (for chroot setups) reduce cross-site cache issues; enable them on multi-user servers.
  • Same kernel and same PHP master. A vulnerability in the Linux kernel or in PHP itself can cross the boundary. Keep both patched and on supported versions.
  • Same database server. Give each site its own database user with access only to its own database.
  • Stronger isolation exists. For sites with very different risk levels (a public WordPress blog next to a billing portal), separate containers or separate virtual machines give much firmer separation.

Quick checklist

  • Each site has its own PHP-FPM pool and Linux user
  • Each pool listens on its own Unix socket, 0660, owned by the web server group
  • pm.max_children, pm.max_requests and request_terminate_timeout set per site
  • security.limit_extensions = .php
  • Sessions, temp files and logs separated per site
  • Home directories not readable by other site users; secrets 0600/0640
  • One database user per site
  • OPcache permission validation enabled on shared servers

Let us check your server

Many small business servers in South Florida grew one site at a time, and the oldest sites often still share a single PHP user. Core Florida IT audits and re-structures web servers, cPanel/WHM and Linux hosts for businesses in Miami-Dade and South Florida, with remote support nationwide. Request a free IT assessment and we will review how your sites are separated.

Sources

Keep reading

Let's make your IT the strongest part of your business.

Book a free, no-obligation IT assessment. We'll show you exactly where you stand and what to fix first.

Call Free assessment