Stronger PHP-FPM isolation for Virtual Sites

28Oct 2026投稿者: mstauberカテゴリー: Development

systemd sandboxing and per-Vsite temp/session directories for BlueOnyx 5210R/5211R/5212R (base-apache 2.4.9-9).

BlueOnyx already had a long list of measures to keep PHP safer on multi-tenant hosts - long before systemd offered much help: Per-Vsite pool users, open_basedir, admin-level PHP flags, private sockets, and more.

What changed is the toolkit underneath. Looking at how the Sury PHP-FPM packages on Debian lean on modern systemd options (for example ProtectSystem), we did not want those levers sitting unused. Several of them give a real extra security margin on top of what we already enforce in the application stack.

So this release hardens how PHP-FPM runs for Virtual Sites, without changing day-to-day PHP behaviour for normal apps. It ships in base-apache 2.4.9-9 (5210R: 2.4.9-7) for 5210R, 5211R and 5212R.

Two layers land together:

  1. systemd unit sandboxing for every customer PHP-FPM service
  2. per-Vsite PHP runtime directories for temporary files and sessions

GUI PHP (admserv-php-fpm) is intentionally left alone.

Update (base-apache 2.4.9-9 / 5210R 2.4.9-7): The first hardening build also set NoNewPrivileges=true. That broke PHP outbound mail through the local setgid sendmail submit path (can not chdir(/var/spool/clientmqueue/): Permission denied). That directive is removed; the rest of the sandbox stays.

Why this change

BlueOnyx was never "open PHP with nothing around it." Isolation at the Vsite level has been part of the design for years.

What was thinner was the process sandbox around the FPM master/workers itself, and the fact that temporary files and sessions still shared global paths (/tmp, /var/lib/php/session) across sites.

systemd can now lock down a service in ways that older stacks simply did not expose cleanly. After seeing that approach in the wild (Sury and similar), we adopted the options that help us, and left out the ones that would break real hosting workloads.

Net effect: the same Vsite model you already know, harder and safer at the edges.

systemd hardening (customer PHP-FPM units)

All customer php-fpm*.service units now include a sandbox block, including:

  • ProtectSystem=full
  • PrivateDevices=true
  • kernel / cgroup / clock / hostname protections (where the OS systemd version supports them)
  • RestrictSUIDSGID=true
  • RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
  • a tight CapabilityBoundingSet for privilege dropping and socket ownership
  • UMask=0022 (explicit default)

Not enabled on purpose:

  • NoNewPrivileges — intentionally not set. With it enabled, setgid /usr/sbin/sendmail (smmsp) cannot drop into /var/spool/clientmqueue, so PHP mail() fails with Permission denied on clientmqueue. Local MTA submission must keep working.
  • ProtectHome - Vsite docroots live under /home/.sites
  • SystemCallFilter / PrivateUsers / RootDirectory - deferred; too likely to break ionCube, JIT and real apps
  • UMask=0077 - would break Apache serving of PHP-created files under /web (Apache runs as apache:apache and is not in the site groups)

On upgrade, %post runs daemon-reload and try-restart for running PHP-FPM units only. Dormant PHP versions stay dormant.

Per-Vsite temp and session directories

Each Vsite now gets its own PHP runtime tree:

/home/.sites/<siteX>/php/            siteAdmin:siteX  0750
/home/.sites/<siteX>/php/tmp         siteAdmin:siteX  1777
/home/.sites/<siteX>/php/sessions    siteAdmin:siteX  0700

(If no preferred siteAdmin is set, ownership falls back to apache:siteX.)

Pool / suPHP configuration points here:

  • php_admin_value[sys_temp_dir] → php/tmp
  • php_admin_value[upload_tmp_dir] → php/tmp
  • php_admin_value[session.save_path] → php/sessions (admin level, so apps cannot ini_set() sessions back to a shared path)

Both paths are auto-appended to open_basedir when open_basedir is a normal path list.
Special case: if open_basedir is deliberately set to none, it stays none and is not rewritten.

suPHP sites get the same directories and values in the per-Vsite php.ini.

Web Ownership follows the tree

When you change Site Management → Web → Web Ownership (prefered_siteAdmin), BlueOnyx already reassigns /web, wwwroot and optional vhosts/.

It now also reassigns the full php/ tree and restores the intended modes (0750 / 1777 / 0700). Session and temp files written under the previous owner remain usable by the new FPM or suPHP worker.

Fleet update helper

New script:

/usr/sausalito/sbin/force_php_pool_update.pl

On package upgrade it:

  • inspects existing FPM pool configs and suPHP php.ini copies
  • if any still lack the new directives, fires PHPVsite.force_update for all Vsites
  • if everything is already current, does nothing

No flag files. Idempotent and self-healing.

RPM install stays quiet on the console: helper output goes to syslog (logger -t base-apache). A failure line is logged only if the helper exits non-zero.

php.d: php_admin_flag now works

Customer drop-ins under wwwroot/php.d/*.ini already supported php_admin_value, php_value and php_flag.

php_admin_flag was missing from that hard-way filter and was silently dropped. It is now accepted in both parse and emit paths, so lines such as:

php_admin_flag display_errors Off

flow into the vhost (mod_php), the FPM pool, and the suPHP ini like the other operators.

Note for FPM: generator directives for temp/session isolation are written before php.d extras. PHP-FPM is first-wins for php_admin_*, so customers cannot override the private temp/session paths via php.d. That is intentional.

Environment variables still do not go through php.d. Use the Vsite Apache include with SetEnv and a literal value.

What this does not change

  • Application behaviour for normal PHP sites (uploads, sessions, mail, ionCube)
  • exec() / outbound network from PHP
  • Apache still serving customer files under /web
  • Optional future work still design-only: PHP chroot (Jailkit reuse), per-Vsite isolated FPM masters, seccomp

Upgrade notes

  1. Install the new base-apache packages (glue/ui/capstone as shipped for your product line).
  2. Running PHP-FPM services pick up the hardened units via %post try-restart.
  3. Vsite pools and directories are rewritten by the upgrade helper when needed.
  4. Existing sites keep working; isolation improves as pools are regenerated.
  5. After upgrade you should see …/php/{tmp,sessions} under each Vsite basedir, owned by the siteAdmin who owns /web.

If a Vsite basedir already carries a setgid bit (Jailkit/layout specifics), ls may show setgid on children as well. Modes remain 0750 / 1777 / 0700; that setgid inheritance is cosmetic.

Bottom line

Area Before After
FPM unit sandbox mostly PrivateTmp plus BlueOnyx’s existing PHP controls additional systemd protections on top of that stack
PHP temp files shared /tmp per-Vsite php/tmp
PHP sessions shared /var/lib/php/session per-Vsite php/sessions (0700)
php.d php_admin_flag silently dropped accepted
Web Ownership change /web (+ vhosts) also full php/ tree

In short: we did not invent PHP security yesterday - we took the modern systemd options that were left on the table and used them where they actually make Virtual Sites safer, without turning hosting into a science project.

Questions or breakage reports: open a ticket with the Vsite name, PHP version/serving mode (FPM/suPHP/mod_php), and the relevant pool or php.ini snippet.

Oct 28, 2026 カテゴリー: Development 投稿者: mstauber
← ニュースへ戻る