How to Speed Up a LifterLMS Site
LMS sites are slow for different reasons than blogs. Here is what you can and cannot cache on a LifterLMS site, where the database work goes, and how…
Read articleA persistent object cache, usually Redis or Memcached, is one of the most effective performance upgrades for a LifterLMS site. Course sites are full of logged-in students, and logged-in pages mostly bypass page caching. Each lesson view loads options, post meta, user meta and enrollment data, and an object cache keeps much of that in memory instead of asking MySQL again.
This guide is for site owners and developers who run LifterLMS with Redis, or are about to. It explains what object caching does and does not speed up, what LifterLMS stores in the object cache, what changed in the 10.x releases to make it more reliable, and how to set it up without the classic problems: stale progress and staging sites reading production data.
For the wider performance picture, including hosting, page caching and video, see our guide on how to speed up a LifterLMS site.
WordPress has a built-in object cache, but by default it lasts only for a single request. The WP_Object_Cache reference explains that it persists between requests only when a persistent cache backend such as Redis or Memcached is installed through a drop-in.
With Redis in place:
What it does not do: it does not cache custom-table queries that LifterLMS runs directly, such as many enrollment and reporting queries against wp_lifterlms_user_postmeta, unless the code caches them explicitly. It does not replace good hosting, and it does not fix slow queries in a heavy report. Think of it as removing repeated reads, not as a fix for every slow page.
From the 10.3 source, these are the main cache groups LifterLMS uses:
| Group | Holds | Expiry |
|---|---|---|
llms_user_{id} | Per-student values such as enrollment dates | None; replaced when data changes |
student_{id} | Calculated grades, invalidated with a group prefix | One hour |
| Per-membership groups | Posts associated with a membership | One hour |
llms_product_subscriptions_count | Active subscription counts per product | One hour |
| Default group | Core form definitions | One hour |
llms_session_id | Front-end session data, also stored in its own table | Session lifetime |
llms_template_functions | Theme template override directories | One day; cleared on theme switch |
llms_media_authorization | Whether a user may load a protected file | One minute, filterable with llms_media_protection_cache_expiration_time |
llms_customers | Customer metrics for the Customers screen added in 10.3 | Cleared when orders change |
Student progress is cached differently: LLMS_Student::get_progress() stores results in user meta (keys such as llms_course_456_progress), and LifterLMS resets them when lessons are completed or the course outline changes. With Redis, those user meta reads also come from memory.
LifterLMS uses a "prefix" pattern for groups it needs to clear at once. LLMS_Cache_Helper::get_prefix() returns a prefix stored in the cache, and LLMS_Cache_Helper::invalidate_group() replaces it, so every old key in the group is orphaned in one call. This works with any backend because it does not need group flushing, which Redis drop-ins do not all support.
Several 10.x releases fixed problems that only appeared with a persistent object cache. If you run Redis or Memcached, these are good reasons to stay current:
null into an empty string, such as enrollment date lookups.In practice, sites on older versions sometimes saw students who had to wait for a value to expire, or clear a cache, before a change showed up. Upgrade first, then enable or tune Redis.
wp-config.php, define WP_REDIS_PREFIX with a value unique to this site and environment, for example the domain plus -prod. If several sites or a staging copy share a Redis instance with the same prefix, they read each other's cached options and user data.WP_REDIS_MAXTTL to force expiry of keys that were stored without one. A day or a week is a sensible safety net.wp cache flush, then log in as a test student, complete a lesson, take a quiz and buy a plan in test mode. Check that progress, grades and access update instantly.Do not confuse the two. A page cache stores whole HTML pages and is ideal for anonymous visitors on sales pages and the blog. An object cache stores data and helps every request, logged in or not.
LifterLMS protects its personal pages from page caching. On the checkout page, the student dashboard and quizzes, it sends no-cache headers and defines DONOTCACHEPAGE, DONOTCACHEOBJECT and DONOTCACHEDB, constants that many caching plugins respect, plus a WP Engine specific exclusion. You can add other pages with the llms_no_cache_page_ids filter. Our article on LifterLMS caching conflicts covers page cache rules in detail.
If you build custom reports or widgets, use the same pattern core uses: a group prefix, an expiry, and the $found argument so a cached zero is not treated as a miss.
<?php
/**
* Cache an expensive report value the way LifterLMS 10.x does:
* a group prefix for one-call invalidation, an expiry, and $found
* so a cached zero is not mistaken for a cache miss.
*/
function llmsx_course_completion_count( $course_id ) {
global $wpdb;
$group = 'llmsx_reports';
$key = LLMS_Cache_Helper::get_prefix( $group ) . 'completions_' . absint( $course_id );
$found = null;
$count = wp_cache_get( $key, $group, false, $found );
if ( $found ) {
return (int) $count;
}
$count = (int) $wpdb->get_var(
$wpdb->prepare(
"SELECT COUNT(*) FROM {$wpdb->prefix}lifterlms_user_postmeta
WHERE post_id = %d AND meta_key = '_is_complete' AND meta_value = 'yes'",
$course_id
)
);
wp_cache_set( $key, $count, $group, HOUR_IN_SECONDS );
return $count;
}
// Invalidate every cached report value when a course is completed.
add_action(
'lifterlms_course_completed',
function () {
LLMS_Cache_Helper::invalidate_group( 'llmsx_reports' );
}
);
wp cache flush after imports, migrations or manual fixes.WP_REDIS_PREFIX and flush both.Yes. LifterLMS uses the standard WordPress object cache API, so it works with any persistent backend, and 10.x specifically improved reliability on Redis and Memcached.
Either works. Redis is more common on WordPress hosts and has a well-maintained drop-in, so it is our default choice. Use whichever your host supports best.
It helps, but heavy reports are limited by custom-table queries. For large sites, see our guide to scaling LifterLMS.
Update LifterLMS to the latest 10.x release, enable Redis with a unique prefix on staging, run a full student journey, then repeat on production.
If your site still feels slow for logged-in students, or you see stale data you cannot explain, our LifterLMS optimization service profiles the queries and cache behaviour on your site. Get in touch and we will find where the time goes.
LMS sites are slow for different reasons than blogs. Here is what you can and cannot cache on a LifterLMS site, where the database work goes, and how…
Read article
How to build payment plans, subscriptions with a fixed number of payments, free and paid trials, and sale prices on LifterLMS access plans, with…
Read article
Set up LifterLMS checkout properly: the checkout page and confirmation flow, the billing form, terms and privacy, currency and spam protection, what…
Read articleSkip the trial and error. Get specialist LifterLMS help and ship faster.