Configuring MODX Resource Caching for a Blog: File vs Memcache
Learn how to enable MODX Resource Caching, choose between file and memcache handlers, and set different expiration times for blog posts versus static pages to improve page load times.
07 Jul 2025, 10:18 UTC

The problem: slow page loads on a growing MODX blog
As a MODX‑powered blog accumulates posts and traffic, each request triggers database queries, template processing, and snippet execution. Even with modest hardware, the cumulative latency can push page‑render times above the 2‑second threshold that visitors notice. The goal is to serve previously rendered HTML directly from a cache layer, skipping the heavy lifting on repeat views.
Thesis: enable MODX’s built‑in Resource Caching, choose the right handler, and tune expiration per resource type
MODX can store the fully rendered output of each resource (page) in a cache. Subsequent requests for the same resource hit the cache first, avoiding database reads and template parsing. The decision points are:
- Turn caching on globally via System Settings.
- Select a cache handler—file‑based or memcache—based on traffic volume and operational capacity.
- Set appropriate cache lifetimes: short for frequently updated blog posts, longer for static pages.
Section 1: Enabling caching and picking a handler
Log into the MODX Manager with administrator privileges. Navigate to System → System Settings and filter by the cache_ prefix.
cache_enabled– set to Yes.cache_handler– choose file (default) or memcache.cache_expires– global default in seconds (e.g.,3600for one hour). This value can be overridden per resource.
If you select memcache, ensure a memcached daemon is reachable on the host defined by cache_memcache_servers (e.g., 127.0.0.1:11211). Misconfiguration will cause cache misses and fallback to uncached rendering, which you can spot in the logs.
Section 2: Worked example – different TTL for blog posts vs static pages
Suppose your blog uses a template that marks posts with a TV called is_blog_post (value 1) and static pages have it empty or 0. You can set a per‑resource expiration after the resource is saved, using a simple plugin on the OnDocFormSave event.
// plugins/resourceCacheTtl.php
$event = $modx->event->name;
switch ($event) {
case 'OnDocFormSave':
/** @var modResource $resource */
$resource = $modx->getObject('modResource', $id);
if (!$resource) { break; }
$isBlog = $resource->getTVValue('is_blog_post');
if ($isBlog == '1') {
// 10 minutes for blog posts
$resource->set('cache_expires', time() + 600);
} else {
// 4 hours for static pages
$resource->set('cache_expires', time() + 14400);
}
$modx->save($resource);
break;
}
Place this file in core/components/yourplugin/elements/plugins/ and enable it via the Manager (Elements → Plugins). The plugin runs after a resource is saved, adjusting its cache_expires field. No database schema changes are required.
Section 3: Verifying that caching works
After enabling caching and making a few requests, you can confirm the behavior in three ways:
- File system check – If using the file handler, look in
core/cache/resource/. A newly visited resource ID (e.g.,42.cache) should appear after the first request. - Manager reports – Go to Reports → Cache Management. The resource should show a non‑zero hits count and a future expires timestamp.
- HTTP header test – Run the following curl command (replace
YOUR_DOMAINandRESOURCE_IDwith actual values):
On the first request you will seecurl -I https://YOUR_DOMAIN/index.php?id=RESOURCE_IDX-ModX-Cache: MISS. On a second request (within the TTL) the header should change toX-ModX-Cache: HIT. This confirms the cached response was served.
If you never see a HIT, double‑check that cache_enabled is Yes and that the resource is not excluded by a plugin that calls $modx->clearCache() on each request.
Trade‑off: file cache vs memcache
| Aspect | File‑based cache | Memcache |
|---|---|---|
| Setup complexity | Zero extra services; works out‑of‑the‑box. | Requires a running memcached daemon and proper network configuration. |
| I/O latency | Disk reads; acceptable for low‑to‑moderate traffic. | In‑memory; lower latency, better under high concurrency. |
| Scalability | Limited by disk speed and available inodes. | Scales horizontally; multiple web heads can share the same memcache pool. |
| Operational overhead | Monitor disk usage; occasional cleanup of stale files. | Monitor memcache hit ratio, memory fragmentation, and daemon uptime. |
For a typical blog with a few hundred daily visits, the file handler is sufficient and simpler to maintain. If you anticipate traffic spikes, plan to migrate to memcache early to avoid a sudden cache‑miss surge.
Limitations and practical checks
- Content that depends on the current user (e.g., login status, personalized snippets) will be served stale if cached. Exclude such resources by setting
cacheableto No in the resource settings or via a plugin that unsetscache_expires. - Changing a template or snippet does not automatically purge existing cached HTML. You must manually clear the cache (Site → Clear Cache) or rely on a cache‑purge plugin that triggers on template/snippet save.
- To verify that stale content is not being served after an edit, edit a blog post, wait a few seconds, then request the page and check the
X-ModX-Cacheheader. If it showsHITand the content is outdated, either reduce the TTL or clear the cache manually.
Actionable closing
Start by turning on cache_enabled and using the file handler. Install the small plugin shown above to give blog posts a 10‑minute TTL while keeping static pages cached for four hours. Verify with the file‑system check, the Cache Management report, and the X-ModX-Cache header. Monitor hit ratios for a week; if you see a consistent miss rate above 20 % under load, evaluate migrating to memcache. This approach gives you measurable performance gains without adding unnecessary complexity.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.