Why MODX Revolution pages slow down

MODX Revolution assembles every page from a tree of Resources, Templates, Chunks, Snippets, and Template Variables. When a visitor requests a page, the MODX parser walks that tree, resolves tags, and either serves a cached copy or re-executes uncached snippets on every hit. The framework is flexible precisely because so much is dynamic, and that flexibility is where slow response times begin. A single Snippet called uncached (with the exclamation prefix, for example [[!getResources]]) forces a database query and PHP execution on every request, multiplied across every visitor.

The second common source of latency is the MODX cache directory itself. MODX writes compiled PHP representations of Resources and system settings into core/cache/. On shared hosting this directory can grow large, become fragmented, or fail to write because of permission or inode limits, silently forcing MODX back into full parsing. When the cache is healthy, a cacheable Resource is served from a pre-built PHP file with almost no parsing overhead. When it is broken or being cleared too aggressively by a plugin, every page behaves like a cache miss.

The third layer sits below MODX entirely: PHP configuration. On a Managed Cloud Shared account running CloudLinux and LiteSpeed, your PHP version and extensions are chosen through the PHP Selector, and default settings frequently ship without OPcache tuned or with a low memory_limit. Without OPcache, PHP recompiles MODX core files on every request. This is invisible in the MODX Manager but very visible in Time To First Byte. Before changing anything, confirm the actual delay: open the front end, then in your browser developer tools look at the TTFB for the main document. A TTFB above roughly 800ms on a simple page almost always points to uncached snippets or missing OPcache rather than network or asset problems.

You can also read the evidence directly. Check core/cache/logs/error.log and the PHP error_log that appears in the document root via cPanel File Manager or DirectAdmin's file manager. Repeated cache-write warnings, memory exhaustion notices, or slow-query patterns tell you which layer to fix first. Diagnose before you tune, because clearing the cache or adding Redis will not help if the real problem is a snippet that should have been cacheable all along.

PHP version, OPcache, and memory through the PHP Selector

Start at the layer with the highest return for the least effort. In cPanel Jupiter open Select PHP Version (MultiPHP / PHP Selector); in DirectAdmin Evolution open Select PHP Version under the PHP area. MODX Revolution 2.8+ runs cleanly on PHP 8.1 or 8.2, and moving off an older 7.x branch alone often cuts execution time noticeably. Confirm the version applies to the correct domain or subdomain before continuing.

On the extensions tab, enable opcache if it is not already checked. OPcache stores compiled PHP bytecode in memory so MODX core files are not recompiled on each request. Then open the PHP Options (or Switch to PHP Options) screen and raise the relevant values within the ceiling your plan allows. Reasonable targets for MODX on shared hosting are memory_limit of 256M, max_execution_time of 60, opcache.enable on, and a healthy opcache.memory_consumption around 128 if exposed. If the Selector does not expose an OPcache value you need, you can set it in a .user.ini file at your document root:

; .user.ini in the domain document root
memory_limit = 256M
max_execution_time = 60
opcache.enable = 1
opcache.revalidate_freq = 60
opcache.max_accelerated_files = 20000

Changes in .user.ini are picked up after the PHP FastCGI process recycles, which can take a couple of minutes. Verify the result by creating a temporary phpinfo() file, confirming OPcache reads Enabled with a non-zero hit rate, then deleting that file so it is not left exposed. A high opcache.max_accelerated_files matters for MODX because the compiled cache and core produce thousands of PHP files; too low a value evicts them constantly and defeats the purpose.

MODX cache handling, snippet review, and Redis object caching

With PHP tuned, return to MODX itself. In the Manager go to System → System Settings and confirm cache_disabled is set to No and cache_handler is defined. Audit your templates for uncached tags. Every [[!snippet]] with a leading exclamation mark runs on every request; remove the exclamation mark wherever the output does not genuinely change per visitor, converting [[!Wayfinder]] or [[!pdoMenu]] style calls to their cached form. Reserve uncached calls for forms, search results, and anything session-specific. This single review often produces the largest real-world improvement because it removes database queries from the hot path.

For object caching, MODX supports xPDO cache providers, and if your account offers Redis you can point MODX at it. Check availability in the PHP Selector extensions list for redis, and look in cPanel for a Redis or key-value store section that shows a socket path or a localhost port. Add the configuration to core/config/config.inc.php by defining the cache handler and connection, for example:

$config['cache_handler'] = 'xPDO\\Cache\\xPDORedisCache';
$config['cache_redis_server'] = '127.0.0.1';
$config['cache_redis_port'] = 6379;

Use the exact host, port, or Unix socket your control panel reports rather than assuming defaults. After editing, clear the MODX cache via Manage → Clear Cache in the top toolbar and load the front end twice, watching TTFB drop on the second load as objects populate the store. If Redis is not offered on your plan, the default file cache combined with OPcache still performs well; do not treat Redis as mandatory.

LiteSpeed Cache, browser caching, and .htaccess controls

Your account runs LiteSpeed Web Server, which can cache full HTML responses in front of PHP. Because MODX has no official LiteSpeed Cache plugin in the way WordPress does, drive it through .htaccess using LiteSpeed cache directives for pages that are safe to serve statically, while excluding the Manager and any dynamic endpoints. Place rules in the document-root .htaccess, keeping them above the standard MODX rewrite block:

<IfModule LiteSpeed>
  CacheLookup on
  RewriteEngine On
  # Do not cache the Manager or connectors
  RewriteRule ^manager/ - [E=Cache-Control:no-cache]
  RewriteRule ^connectors/ - [E=Cache-Control:no-cache]
  # Do not cache logged-in or form sessions
  RewriteCond %{HTTP_COOKIE} PHPSESSID [NC]
  RewriteRule .* - [E=Cache-Control:no-cache]
  # Cache public GET pages for 5 minutes
  RewriteCond %{REQUEST_METHOD} GET
  RewriteRule .* - [E=Cache-Control:max-age=300]
</IfModule>

Test carefully after adding these rules. Load a public page, confirm it renders correctly, then edit a Resource and clear the MODX cache; the front-end page will only update after the LiteSpeed TTL expires, so keep max-age short until you are confident. If anything renders stale for logged-in editors, the session-cookie exclusion above is what prevents that, so verify it works before relying on public caching.

Add ordinary browser caching and compression for static assets, which reduces repeat-visit load and offloads bandwidth without touching PHP at all:

<IfModule mod_expires.c>
  ExpiresActive On
  ExpiresByType image/jpeg "access plus 1 month"
  ExpiresByType image/png "access plus 1 month"
  ExpiresByType image/webp "access plus 1 month"
  ExpiresByType text/css "access plus 1 week"
  ExpiresByType application/javascript "access plus 1 week"
</IfModule>
<IfModule mod_deflate.c>
  AddOutputFilterByType DEFLATE text/html text/css application/javascript
</IfModule>

Finally, tidy the application itself. Trim unused Snippets and Plugins, since every registered Plugin fires on system events and adds overhead. Keep core/cache/ from ballooning by disabling verbose logging once diagnosis is complete, and periodically clear the cache after bulk content imports so stale partial files do not accumulate against your inode quota. Layer these fixes in order: PHP and OPcache first, then MODX cacheable snippets and object caching, then LiteSpeed and browser caching on top. Measured together against your original TTFB baseline, they turn a slow MODX site into one that serves most requests before PHP does meaningful work. For teams also running other applications, the same tuning philosophy appears in our companion piece on Buzzy performance and caching on shared hosting.