A technical breakdown of where Moodle installs actually slow down under real load — PHP-FPM and opcache settings, MUC caching with Redis, database indexing, the cron job, and the scaling issues that only show up once enrolment numbers get large.
A default Moodle install tuned for a handful of pilot courses behaves very differently once thousands of learners, large logstore tables, and concurrent quiz attempts show up. Most performance problems we're called in for aren't Moodle being slow by nature — they're default configuration never revisited as the site grew.
Default MUC file caching doesn't share state across multiple web servers, and gets slower as cache size grows.
Logstore and grade tables that grow for years without index review or retention policy.
PHP-FPM and opcache settings left at distro defaults instead of sized against real memory and traffic.
Every slow Moodle site we've diagnosed traces back to some combination of these four layers.
Undersized pm.max_children starves Moodle under concurrent logins, while oversized pools exhaust server memory. Pool size has to be set against actual per-process memory usage, not a guess.
Without a properly sized opcache.memory_consumption and opcache.max_accelerated_files, PHP recompiles Moodle's large codebase on every request instead of serving it from memory.
Large course catalogs, gradebooks, and logstore tables slow down fast without the right indexes — the mdl_logstore_standard_log table in particular grows unbounded and needs attention.
Moodle Universal Cache (MUC) defaults to file-based caching, which doesn't scale across multiple web servers. Application and session caches belong in Redis or Memcached once you're load-balanced.
MUC is Moodle's built-in caching abstraction, and the single highest-leverage place to tune performance once a site outgrows a single small server.
Site Administration > Plugins > Caching > Configuration lets you assign a Redis or Memcached store to each cache definition — application, session, and request layers can each use a different store.
Moving the application cache and session store off the filesystem and onto Redis is usually the single highest-impact change for a multi-server or high-concurrency Moodle install.
$CFG->cachejs (enabled by default) controls whether Moodle's combined JavaScript is cached and served with far-future expiry headers — leaving it off in production forces browsers to refetch on every load.
Compiled SCSS and language string caches avoid recompiling theme CSS or rebuilding string tables on every request; both are purged automatically on theme or language pack changes but should stay warm otherwise.
Caching hides a lot of database inefficiency, but not all of it — the underlying schema and query patterns still matter as data volume grows.
Tables like mdl_logstore_standard_log, mdl_user_enrolments, and mdl_grade_grades are the ones queried most under real usage — missing indexes here show up directly as slow page loads at scale.
The standard logstore captures every user action and grows continuously. Setting a log retention policy (Site Administration > Reports > Logs) or moving to an external logstore keeps the table from degrading query performance over time.
Moodle supports configuring a read-only database replica ($CFG->dbreadonly) so reporting and read-heavy queries don't compete with transactional writes on the primary database.
innodb_buffer_pool_size needs to be large enough to hold your working data set in memory — undersized buffer pools force constant disk reads for data that should be cached.
Moodle's cron.php drives scheduled tasks — enrolment sync, notifications, cache cleanup, and more. Running it too infrequently backs up queued tasks; running heavy ad-hoc tasks during peak hours competes with user traffic for the same PHP-FPM workers.
Offloading theme CSS, JS bundles, and course files to a CDN cuts origin server load and improves load times for geographically distributed learners.
Public-facing pages (course catalogs, login pages) can be cached at a reverse proxy layer, reducing the number of requests that ever reach PHP-FPM at all.
File-based PHP sessions don't work cleanly across multiple web servers behind a load balancer. Centralizing sessions in Redis (via $CFG->session_handler_class) keeps users logged in consistently regardless of which server handles a request.
We start from your site's actual Performance overview report and slow query logs, not a generic checklist.
PHP-FPM, opcache, and database tuning alongside Moodle-level cache and cron configuration — not just one half of the stack.
Changes verified against realistic concurrent-user and quiz-attempt scenarios before they touch production.
Tuning informed by having watched what actually slows down Moodle installs at scale, not just documentation.
Straight answers to what comes up most in a performance audit.
In most audits we run, it's caching — specifically moving MUC's application and session cache off the default file store and onto Redis. The second most common win is a properly sized PHP opcache, since Moodle's codebase is large enough that recompiling it per-request is expensive.
Start with Site Administration > Reports > Performance overview, which shows real request timing and cache statistics from your own traffic. From there, slow query logs on the database and PHP-FPM's slow log narrow down whether the bottleneck is database, PHP execution, or caching.
Yes. Moodle's cache API (MUC) includes Redis and Memcached store plugins that can be configured for any cache definition — application, session, or request-level — through Site Administration > Plugins > Caching > Configuration.
Yes, mdl_logstore_standard_log grows with every user action and, left unmanaged, becomes one of the largest tables in the database. Setting a log retention period or moving to an alternate logstore keeps queries against it from slowing down.
Yes. Static assets (theme CSS/JS, course files) are commonly served through a CDN, and anonymous-accessible pages can be cached at a reverse proxy or Varnish layer in front of Moodle to reduce origin load.
For large installs with heavy reporting load, yes — Moodle supports a read-only database connection so reports and analytics queries run against a replica instead of competing with transactional writes on the primary database.
Cron drives Moodle's scheduled tasks — enrolment sync, notifications, cache cleanup, adhoc tasks. Running it too infrequently causes task backlogs; running it too frequently or with too many heavy tasks during peak hours competes with real user requests for the same PHP-FPM workers. Tuning cron frequency and task scheduling windows matters as much as server-level tuning.
The full overview of our Moodle development services, pricing, and process.
SAML2, OAuth2, and CAS single sign-on with enterprise identity providers.
Custom plugins and full theme builds, upgrade-safe from the ground up.
SCORM, xAPI, cmi5, and LTI connections into Moodle.
Tell us about your install size and traffic and we'll scope a performance review.
Get a Free Performance Review