قراءة 14 دقيقة

Patchman activation breaks PHP sites: memory_limit gotcha

Patchman daily scans on cPanel push large WordPress sites past the PHP memory_limit and trigger 500s. Diagnostic flow, two-part fix, pre-activation audit.

Patchman activation breaks PHP sites: memory_limit gotcha

The ticket landed mid-morning. Thirteen WordPress sites on cpanel-host were intermittently returning 500s. Not all at once, not on a clean five-minute beat, not correlated with traffic. The sites that broke were the ones with the most plugins. The sites that stayed up were the small brochure builds. The error log on each broken site said the same thing: PHP Fatal error: Allowed memory size of 134217728 bytes exhausted. WordPress's own request-time memory footprint had not changed in months. Nothing had been pushed to any of those sites in the last week.

What had changed was that we had activated Patchman on the server two days earlier and walked away from it. Patchman is a daily-scan tool that opens every PHP file in every cPanel account, reads them into memory, and matches them against a signature database for known vulnerable plugin and theme code. On a server with one or two small WordPress installs you will never notice it. On a server with a dozen mature WordPress sites (themes, page builders, fifty plugins, the whole stack) the scanner blows through the default PHP memory_limit and the scan crashes mid-file. Some of those crashes land inside a PHP-FPM worker that is also serving a public request, and the visitor sees a 500.

This is the postmortem of that activation and the fix. It also covers the five-minute audit we now run before we turn Patchman on for any new server, so this never happens again on the way in.

What Patchman does and why most agencies install it

Patchman, as installed via the WHM plugin, runs a daily scan across every cPanel account on a server. The scan walks the user's web root, opens each PHP file, normalises it, and compares it against a signature database of known-vulnerable WordPress, Joomla, and Drupal code. When it finds a match, it either auto-patches the file in place (by replacing the vulnerable function with a patched version) or flags the site for manual review in the Patchman dashboard.

The reason agencies install it on top of an Imunify360 or ImunifyAV stack is that it catches a different class of problem. Imunify360 detects malicious code: backdoors, web shells, defacement payloads. Patchman detects vulnerable code: plugins that are out of date, known CVEs in third-party libraries, end-of-life Joomla extensions that the client has not touched in three years. The two tools overlap maybe twenty percent. On a multi-tenant hosting server where you cannot force every client to keep their plugins updated, having a scanner that patches without consent is a real time-saver.

We still recommend it. The gotcha we are about to describe is a documentation gap, not a reason to skip the tool.

The symptom after activation

Within four hours of activating Patchman the first wave of 500s landed. The pattern that day:

  • Eight sites broke between 02:00 and 04:00 local time. That is when the Patchman daily scan runs by default.
  • Five more sites broke between 02:00 and 04:00 the next night.
  • The thirteen affected sites were the largest WordPress installs on the box, measured by total disk size of the WordPress directory.
  • The smaller WordPress sites and the static HTML accounts were unaffected.

The per-site error log on each broken site said:

[10-May-2026 02:14:37 UTC] PHP Fatal error:  Allowed memory size of
134217728 bytes exhausted (tried to allocate 20480 bytes) in
/home/riftvm/public_html/wp-content/plugins/elementor/core/files/css/base.php
on line 218

The error frames varied (sometimes Elementor, sometimes WooCommerce, sometimes a page builder cache file) but the limit was always the same: 134217728 bytes, which is 128 MB, the cPanel ea-php81 default for memory_limit. The line tried to allocate 20480 bytes was the giveaway: the script was not asking for a huge allocation. It was asking for a normal 20 KB and PHP had nothing left to give it because something else, earlier in the process, had eaten the budget.

The misdirect is that the file in the error frame belongs to the WordPress site. The natural first guess is that one of the plugins is leaking memory and the site needs wp_options cleaned up, or a caching plugin tuned. That is the wrong rabbit hole. The site is not leaking. The PHP process serving the request was sharing its worker pool with the Patchman scanner, and the scanner is the thing that pushed cumulative memory over 128 MB.

Why it happens

The mechanic underneath is worth understanding before reaching for a fix, because if you do not understand it you will set the wrong ceiling.

Patchman scans run as the cPanel user. They are not a long-lived daemon with its own PHP configuration. The Patchman agent kicks off a PHP process under the target user's PHP-FPM pool, with the user's PHP configuration, which means the user's memory_limit. The scanner then walks the user's public_html (and any addon domain directories), opens each PHP file with file_get_contents or similar, holds it in memory, and runs it through the signature matcher. Files are not held forever (there is a per-file GC pass) but the steady-state memory footprint of the scanner on a mature WordPress install is well over what the same user's regular requests need.

The defaults make the trap easy to walk into:

  • The cPanel ea-php81 package ships with memory_limit = 128M in its primary php.ini. So does ea-php82. So does ea-php83.
  • The WHM MultiPHP INI Editor preserves whatever the system default was the first time the operator opened it. If nobody has ever changed it, it is still 128M.
  • Some hosts run a custom value (we have seen 64M on shared hosting that we inherited from a previous provider; that breaks immediately on Patchman activation).
  • A WordPress install with thirty active plugins and a page builder routinely hits 80 to 100 MB of request-time memory under normal load. There is not much headroom left for a parallel scanner job in the same PHP-FPM worker pool.

128 MB is enough for most WordPress sites' actual requests. It is not enough for a Patchman scan plus the request. The fix is to give the worker pool more room, and to be honest about why.

The diagnostic flow

When the symptom is "500s on multiple WordPress sites starting at the same time", the diagnostic flow is short. Three commands and a log read.

First, correlate the start of the 500s with anything that changed on the server in the last week. We keep a wall log of operator actions for exactly this purpose, but dnf history and the WHM "Change Log" view are usable substitutes:

# When was the patchman package installed or updated?
rpm -qi patchman-client 2>/dev/null | grep -E '^Install Date'
# Or, if it came in via the WHM plugin installer:
ls -lt /var/cpanel/perl/Cpanel/Plugins/ | head -20

Second, read the per-user PHP error log on one of the affected sites. Under cPanel ea-php, every user gets a personal error log at /home/<user>/logs/php_errors.log. That is where the 128M Fatal will be:

sudo tail -50 /home/riftvm/logs/php_errors.log

The timestamps in that file are the second confirmation. If every Fatal error is in the 02:00 to 04:00 window (Patchman's default scan schedule) you have your culprit. WordPress's own request errors spread across the day; Patchman-induced errors cluster on the scan window.

Third, watch a live scan if one is running:

ps -eo pid,user,rss,vsz,cmd | grep -E 'patchman|php' | grep -v grep

You want the RSS column. On a server mid-scan you will see one or two patchman-scan or pmscan workers per cPanel user, each with RSS well above 100 MB, sometimes 200 MB on large sites. If those numbers are climbing past whatever your memory_limit is in MB, the diagnosis is confirmed.

For posterity, we also keep an audit query that matches "new PHP fatal errors that did not exist before a given date":

# Substitute the date you activated Patchman.
sudo grep -h "Allowed memory size" /home/*/logs/php_errors.log \
  | awk '{print $1, $2}' \
  | sort -u

If the earliest line in that output is the day you turned on Patchman, you have your answer.

The fix in two parts

The fix is layered. The first part (raising memory_limit) is correct for most servers and fixes the immediate symptom. The second part (Patchman-specific exclusions) is only needed for the biggest sites where no reasonable memory_limit is enough.

Part 1: raise memory_limit (carefully)

The crude option is to raise the global PHP memory_limit via WHM › MultiPHP INI Editor › Editor Mode. Switch to Editor mode for each active PHP version (ea-php80, ea-php81, ea-php82, ea-php83) and bump memory_limit from 128M to 256M:

; /opt/cpanel/ea-php81/root/etc/php.ini (excerpt)
memory_limit = 256M

After saving, WHM will rebuild the PHP-FPM pools. New requests pick up the new limit immediately; in-flight requests finish on the old config.

If you want a per-user override rather than a server-wide change (say, you only want to raise the limit for the thirteen WordPress accounts that are actually big) drop a .user.ini into the user's document root. cPanel honours .user.ini because PHP itself does:

; /home/riftvm/public_html/.user.ini
memory_limit = 256M

PHP rereads .user.ini every user_ini.cache_ttl seconds (default 300). If you want it picked up immediately, restart the user's PHP-FPM pool:

sudo /scripts/restartsrv_apache_php_fpm

Whichever path you take, raise the limit one tier at a time. Do not jump from 128M straight to 1024M. The reason memory_limit exists at all is to bound a runaway script before it eats the whole server. A site that legitimately needs 1 GB to render a page has a problem that more memory will not solve. Our rule of thumb, calibrated against the thirteen sites we fixed:

WordPress directory sizeRecommended memory_limit
Under 50 MB128M is fine post-fix (the original default)
50 MB to 200 MB256M
200 MB to 500 MB384M
500 MB+ (rare, usually media-heavy)512M

The directory size is a proxy for "how much code does Patchman have to walk", which is what actually drives scanner RAM. A site that is mostly uploads in wp-content/uploads/ is not as expensive as a site that has the same total size but it is all plugin code.

For our thirteen affected sites, bumping from 128M to 256M was sufficient. Two of them (the ones with WPML plus WooCommerce plus a page builder, all running on the same install) also needed a follow-up to 384M a week later when a plugin update made the scan heavier. We did not need 512M for anything on that server. The default 128M is genuinely too low for modern WordPress, Patchman or not; if you have never raised it, raising it is overdue.

Part 2: Patchman-specific exclusions where memory is not enough

A handful of sites in our portfolio are too large for any reasonable memory_limit. The pattern is usually a legacy WooCommerce install with several thousand product variations, a learning-management system with ten thousand lesson files, or a multisite network. On those sites, raising memory_limit to 768M makes the scanner work, but it also gives a runaway request enough rope to OOM the server.

For those, the answer is to exclude the site from Patchman scanning in the Patchman dashboard:

  1. Open the Patchman admin UI (WHM › Plugins › Patchman, or the standalone dashboard URL).
  2. Find the user account or domain.
  3. Set the scan policy for that account to Excluded (or to Manual only if you want to scan on demand but not on the daily schedule).

The trade-off is real and worth documenting on the ticket: the excluded sites lose Patchman's vulnerability detection. Note it, schedule a quarterly manual review for those sites, and make sure the operations runbook has them flagged. Patchman also supports per-plugin exclusions if your problem is one specific plugin that the scanner cannot finish (fewer collateral consequences, more fiddly to configure).

The default policy across our fleet is now: raise memory_limit first, exclude as a last resort, and revisit excluded sites every quarter.

The proper memory_limit math

The temptation, after a 500 outage at 02:00, is to set memory_limit to something comically large (1024M or 2048M across the board) and never think about it again. Resist that.

memory_limit is a per-PHP-process ceiling. If a server runs twenty concurrent PHP-FPM workers and each one is allowed 1 GB, the worst case is 20 GB of resident memory across the PHP layer alone, with no MySQL, no Redis, no nginx workers accounted for. On a 16 GB VPS that is OOM territory. The OOM killer will not be selective about who it kills; we have seen it take MariaDB out because PHP-FPM happened to outgrow it at the wrong moment.

The right ceiling is the smallest number that lets the legitimate work happen, plus a safety margin. For a server running cPanel + Patchman + a normal WordPress workload, 256M per worker is the sweet spot we keep landing on. 384M is fine on bigger boxes with fewer accounts per server. 512M is the upper bound we will go to without escalating to "this site needs its own pool" or "this site needs to be excluded from the scanner".

If you find yourself wanting to set memory_limit past 512M, the question to ask first is not "how much memory does this site need" but "what is forcing this site to need that much memory". Usually the answer is one specific plugin doing something pathological: an ImportExport plugin trying to load a 50 MB CSV into a single PHP array, a backup plugin streaming a 4 GB tarball through PHP instead of letting nginx handle it. Those are bugs in the plugin. Raising memory_limit masks them. Fixing the plugin (or replacing it) solves the actual problem.

The unglamorous truth about Patchman

Patchman has one clear documentation gap, and that gap costs the average sysadmin a half day of false leads the first time they hit it. The official Patchman docs at the time of writing recommend a default memory_limit of 128M and do not flag that the scanner itself needs headroom above the application's request-time footprint. The fix is small, but the time-to-fix without a postmortem like this one is not.

We still recommend Patchman, with two caveats:

  1. Run the pre-activation audit in the next section. It takes five minutes and prevents the entire outage we have just described.
  2. Document the memory_limit change in the same ticket that activates Patchman, so the next operator knows the two are related.

The alternatives are worth naming:

  • ImunifyAV (the free signature scanner from CloudLinux). Detects malicious files, not vulnerable plugins. Useful but not a Patchman replacement.
  • Wordfence (per-site, runs inside each WordPress install). Detects vulnerable plugins and malicious files but only for WordPress, only on sites where you control the install, and with a per-site cost.
  • Doing nothing. Relying on the client to patch their own plugins. This works on the eight sites you have the closest relationship with and silently fails everywhere else.

None of the alternatives are strictly better. Patchman remains the best fit for an agency running fifty-plus mixed-CMS sites on cPanel where you cannot force clients to update.

The 5-minute audit before activating Patchman on a new server

This is the audit we now run on every new server before we click "activate" on the Patchman plugin. It would have prevented the outage above.

Step 1: baseline the current PHP memory_limit for every active PHP version.

# Print memory_limit for each ea-php package installed.
for v in /opt/cpanel/ea-php*/root/usr/bin/php; do
  echo -n "$(basename $(dirname $(dirname $(dirname $v)))): "
  "$v" -r 'echo ini_get("memory_limit"), "\n";'
done

Sample output on a fresh CloudLinux box:

ea-php80: 128M
ea-php81: 128M
ea-php82: 128M
ea-php83: 128M

Step 2: estimate the size of every WordPress install on the server. The size of wp-content/plugins plus wp-content/themes is the practical proxy for how hard Patchman will work on each account:

# List WordPress installs and their plugin + theme footprint.
sudo find /home/*/public_html -maxdepth 4 -name 'wp-config.php' 2>/dev/null \
  | while read -r cfg; do
      site=$(dirname "$cfg")
      size=$(du -sm "$site/wp-content/plugins" "$site/wp-content/themes" \
              2>/dev/null | awk '{s+=$1} END {print s}')
      printf '%5s MB  %s\n' "$size" "$site"
    done | sort -nr | head -20

Step 3: decide the memory_limit for this server using the table from the fix section. If the largest WordPress install on the box is 180 MB of plugins + themes, set memory_limit = 256M before activating Patchman, not after. If the largest is 600 MB and you have only 8 GB of RAM, plan on excluding that site from Patchman from day one and set the global memory_limit to 384M.

Step 4: record the decision in the operations log. The next operator should be able to look up "why is memory_limit 384M on this server" and find "because we activated Patchman on 2026-05-10 and the largest WordPress install was 612 MB of plugins and themes". Write it down.

That is the audit. Five minutes of work, one outage avoided.

If you found this useful, two other postmortems on this blog cover adjacent failure modes that interact with PHP memory and worker pools:

  • WordPress WP-Cron stacking on cPanel: a complete fix walks through what happens when the same PHP-FPM pool is asked to serve real requests and run PHP scripts triggered by wp-cron.php at the same time. The interaction with a memory_limit that is too low looks similar from the outside.
  • Locked out of cPanel SSH: VNC, iptables, and the way back in is the recovery use case for when a panel-level change (a firewall rule, a plugin activation, a Patchman scan that triggered a brute-force protection rule against your own admin IP) locks you out of the box you are trying to fix.

How ServerGuard handles this

ServerGuard's use case for "third-party WHM plugin activation breaks PHP requests" sits across our web-server health and WordPress hardening coverage.

Detection. SGuard watches every cPanel account's per-user PHP error log via the standard tailing pipeline (the same one that catches WP-Cron PHP-FPM saturation, OPcache misconfiguration, and plugin fatal errors). When the rate of Allowed memory size of N bytes exhausted lines crosses a per-account threshold, and the errors correlate in time with a recent package install or WHM plugin activation event in the server's dnf history / /var/cpanel/logs, the incident is filed under "post-activation regression" rather than the more generic "WordPress fatal" bucket. This correlation matters: the diagnostic path for "PHP fatals because the application is broken" and "PHP fatals because a scanner is competing for memory" are completely different, and SGuard picks the right path automatically.

Action (Safe, auto). SGuard raises memory_limit by one tier (128 to 256, 256 to 384, 384 to 512) on the affected ea-php version when the error pattern matches the Patchman signature: errors clustered in a known scan window, errors across multiple unrelated sites on the same server, errors stopping when the scan window ends. The change is written via WHM API (set_php_ini) rather than by editing php.ini directly, and the previous value is stored as a before snapshot so SGuard can revert if the operator rejects the change in the dashboard. SGuard will not auto-raise past 512M without a human in the loop. That ceiling is intentional and matches the table in this post.

Action (Moderate, requires approval). Per-site Patchman exclusions are a business decision (the site loses vulnerability detection in exchange for stability), so SGuard surfaces the candidate sites and the trade-off, but it does not toggle the exclusion without an operator approving it from the dashboard. Same applies to changing the Patchman scan schedule to a lower-traffic window.

Before-after snapshot for third-party WHM activations. SGuard captures a baseline snapshot (memory_limit per ea-php version, per-user PHP error log volume over the last seven days, the list of cPanel accounts with WordPress installs over 200 MB) before any third-party WHM plugin install or activation event it can detect. If post-activation errors spike, the snapshot is what SGuard's surgical revert reads from, so the rollback is to the exact prior config and not to a guessed default. This is the shape of coverage the web-server health use case requires.

What SGuard does not do. SGuard does not install, configure, or remove Patchman. The decision to run a third-party scanner is a business decision, not an operational one: vendor choice, licensing, what to scan, what to exclude. SGuard's job is to make sure the scanner running alongside your sites does not take them down, and to give you a clean revert path if it does.

The full surgical revert for a Patchman-induced memory_limit incident (re-applying the captured baseline and re-running a fleet-wide health check) is implemented today. Per-site Patchman exclusion management from the SGuard dashboard is upcoming roadmap.

If you run Patchman on a cPanel fleet and you have not yet baselined your memory_limit against your largest WordPress installs, the five-minute audit in the section above is worth running today. The next Patchman activation is the wrong time to discover that your default is still 128M.

شارك هذه المقالة

XLinkedInEmail
  • قراءة 8 دقيقة

    The corrupted WordPress db.php dropin nobody warned you of

    The corrupted WordPress db.php dropin nobody warned you about The ticket reads "Error establishing a database connection". You SSH into the box. MySQL is up. works. The other twelve WordPress sites on the same server are loading fine. Only

  • قراءة 8 دقيقة

    Hardening every WordPress site on cPanel in one loop

    Hardening every WordPress site on cPanel in one loop You manage twenty-seven WordPress sites on one cPanel server. A clean hardening pass on a single site (disable xmlrpc, lock down file editing, force SSL on the admin, security headers int

  • قراءة 14 دقيقة

    xmlrpc.php abuse and the 27-site one-shot fix on cPanel

    xmlrpc.php abuse and the 27-site one-shot fix on cPanel The first time floods one of your servers, you Google the symptom, find a guide called "how to disable xmlrpc.php in WordPress", install a plugin, click a checkbox, and move on. The se