Forge and Ploi
Forge and Ploi
This recipe takes a Forge or Ploi host from nothing to a daemon in apply mode. Both lay php-fpm out the same way: Ubuntu, a unit named php8.4-fpm (or whichever version was provisioned), and a pool file per site under /etc/php/8.4/fpm/pool.d. MySQL, and often Redis, share the host's memory with php-fpm, and no cgroup limit holds php-fpm back.
1. Install as root
SSH in as the deploy user and run the installer under sudo, so the binary lands in /usr/local/bin:
curl -fsSL https://raw.githubusercontent.com/cboxdk/fpm-tune/main/install.sh | sudo sh
Run without sudo, the installer cannot write /usr/local/bin and falls back to ~/.local/bin, and sudo fpm-tune then fails because sudo resets PATH. If you download the archive by hand instead, sudo install -m 0755 fpm-tune /usr/local/bin/fpm-tune does the same. Check it:
fpm-tune version
0.1.0-beta.22
2. Turn on the status pages
fpm-tune sizes a pool from its status page, and the pool files Forge and Ploi write ship with pm.status_path commented out. A pool without one is not sized at all, and on a fresh host that is every pool, so the first fpm-tune plan reports no pools found. Turn the pages on:
sudo fpm-tune enable-status
This writes zz-fpm-tune-status.conf beside the pool files, validates the whole configuration with php-fpm -t, reloads php-fpm gracefully, and rolls back if the master does not come back. Your pool files are not touched. Run it again and it reports that every pool already exposes a status page and there is nothing to do.
3. Read a plan
fpm-tune plan
host memory 7.5GiB, 4 CPU(s) (via /proc/meminfo)
used by other services: 3.0GiB (left for them; cap php-fpm's cgroup for a hard limit)
reserve kept: 1.1GiB (15% of 7.5GiB)
available to workers: 3.5GiB
POOL MODE NOW PLAN MEMORY WHY
www dynamic 20 20 960.0MiB peak 2 workers busy, but not yet watched under load; held at its configured 20, estimated 48.0MiB/worker
www-forge dynamic 10 41 1.7GiB peak 33 workers busy; raised to 41, measured 41.9MiB/worker + 37.6KiB children
allocated 2.6GiB of 3.5GiB, 869.2MiB free
plan changes nothing. The first block is the budget: what MySQL and everything else is using is left to them, a 15% reserve is kept, and the rest is what the pools may fill. The table is one row per site. A pool that has not been watched under load is held at its configured value and marked estimated; a measured one shows its per-worker cost and the ceiling it gets. Reading a plan explains every line.
4. Let it watch
Install the service. It starts in advisory mode: it measures, logs and writes a recommendation, and changes nothing.
sudo fpm-tune install-service
journalctl -u fpm-tune -f
time=2026-09-03T17:11:01.747Z level=INFO msg="Pool recommendation" pool=www now=20 recommend=20 why="peak 2 workers busy, but not yet watched under load; held at its configured 20, estimated 48.0MiB/worker"
A line like that appears whenever a pool's recommendation moves, and every 30 min as a sign of life. The current recommendation is also written to /var/lib/fpm-tune/recommended.conf. Leave it for a day or a week, long enough to cover a traffic peak and a deploy, and check that the numbers have settled and match what you know about the sites: the shop is the heavy one, the docs site is cheap.
5. Let it act
sudo fpm-tune mode apply
The same service restarts in apply mode. From here it writes each pool's ceiling to zz-fpm-tune.conf and reloads php-fpm when a change is large enough, and has been stable long enough, to be worth a reload (hysteresis). Back to advisory at any time:
sudo fpm-tune mode advisory
To apply the current plan once without waiting for the daemon's damping, see Applying once.
6. Put a limit on php-fpm
The used by other services line is what MySQL and Redis hold right now, and it is subtracted from the budget on every round. It is not a promise about next Tuesday. For a hard wall, give the php-fpm unit a cgroup memory limit; it takes effect live and survives reboots:
sudo systemctl set-property php8.4-fpm.service MemoryMax=4G
fpm-tune reads the limit from php-fpm's cgroup on its next round and sizes inside it, and the first line of the plan changes to php-fpm's memory 4.0GiB. The budget explains how the limit and the reserve combine.
When the host is out of capacity
When every pool wants more and the budget is gone, the plan says so and the daemon stops growing pools. On a Forge or Ploi host that is a sizing question, not a tuning one: a bigger server, or a heavy site moved off. Metrics and alerting has the signal to alert on.