Linux Cgroups v2 for WordPress: Per-Site Resource Limits with systemd

On a WordPress multisite or a server hosting multiple WordPress sites, a single runaway plugin — a plugin executing an infinite loop, a poorly written import job, or a DDoS-amplified PHP process — can consume all CPU and RAM, taking every other site offline. Linux cgroups v2 (control groups), managed via systemd slices, let you place hard CPU and memory limits on each site’s PHP-FPM pool, guaranteeing that one site’s load spike cannot starve another’s resources.


The configuration below creates a systemd slice for a WordPress site, places the PHP-FPM pool inside it with memory and CPU limits, verifies the limits are applied using systemd-cgtop, and shows how to apply the same pattern to multiple sites as a template.


# ── 1. Create a systemd slice for a site ────────────
# /etc/systemd/system/site-helloadmin.slice
sudo tee /etc/systemd/system/site-helloadmin.slice << 'SLICE'
[Unit]
Description=Resource slice for helloadmin.com
Before=slices.target

[Slice]
# Memory: hard limit at 512 MB — processes are OOM-killed beyond this
MemoryMax=512M
# Swap limit: 256 MB of swap on top of the memory limit
MemorySwapMax=256M
# CPU: limit to 25% of one CPU core (250ms per 1000ms period)
CPUQuota=25%
# I/O: limit to 50 MB/s read/write (adjust to your storage)
IOReadBandwidthMax=/dev/sda 50M
IOWriteBandwidthMax=/dev/sda 50M
SLICE

sudo systemctl daemon-reload
sudo systemctl start site-helloadmin.slice

# ── 2. Assign PHP-FPM pool to the slice ────────────
# /etc/php/8.2/fpm/pool.d/helloadmin.conf — add these lines:
# systemd_interval = 0
# ; Assign to our site slice:
# php_admin_value[cgroup] = /site-helloadmin.slice

# Or use a PHP-FPM service override:
sudo mkdir -p /etc/systemd/system/php8.2-fpm-helloadmin.service.d
sudo tee /etc/systemd/system/php8.2-fpm-helloadmin.service.d/slice.conf << 'OVERRIDE'
[Service]
Slice=site-helloadmin.slice
OVERRIDE

sudo systemctl daemon-reload && sudo systemctl restart php8.2-fpm

# ── 3. Verify cgroup limits ────────────
# Show live resource usage per slice:
systemd-cgtop -d 2

# Show cgroup hierarchy:
systemctl status site-helloadmin.slice

# Check actual limits applied:
cat /sys/fs/cgroup/site-helloadmin.slice/memory.max
cat /sys/fs/cgroup/site-helloadmin.slice/cpu.max

# ── 4. Monitor cgroup memory pressure ────────────
# When a cgroup approaches its memory limit, the kernel writes to memory.pressure
cat /sys/fs/cgroup/site-helloadmin.slice/memory.pressure
# Format: some avg10=X avg60=Y avg300=Z total=N
# avg10 > 20% means the site is under memory pressure — investigate PHP-FPM pool size

# ── 5. Template: apply to multiple sites ────────────
for site in helloadmin staging devblog; do
    sudo cp /etc/systemd/system/site-helloadmin.slice \
            /etc/systemd/system/site-${site}.slice
    sudo sed -i "s/helloadmin/${site}/g" /etc/systemd/system/site-${site}.slice
done
sudo systemctl daemon-reload


NOTE: MemoryMax in a systemd slice is a hard limit — when a PHP-FPM worker exceeds it, the kernel's OOM killer terminates that process immediately, which appears to the user as a 502 Bad Gateway; to detect limits that are too tight before users experience errors, start with MemoryHigh (a soft limit that triggers memory reclaim and logs a warning) and only add MemoryMax once you have profiled your site's peak memory usage with the soft limit in place for a week.