Moodle performance tuning works best as a sequence, not a list of tweaks: measure where time is actually spent, fix the platform baseline (a supported Moodle on PHP 8.3 or 8.4 with a properly sized OPcache), move caches and sessions into Redis, tune the database, get cron and ad hoc tasks under control, offload file delivery, audit plugins, and then prove the result with a load test before your next peak.
Most slow Moodle sites we review are not short of hardware. They are running disk-based caches on network storage, an untuned database, a cron backlog or one badly behaved plugin. Adding servers to that setup mostly adds cost. The checklist below is ordered so that each step either removes a common bottleneck or tells you where the real one is.
Versions and requirements below were checked against Moodle's official release notes and documentation in September 2026.
1. Measure first: build a baseline before changing anything
Record a baseline for a normal weekday and for your busiest period (exam week, enrolment day, a compliance deadline):
- Server response time at p50 and p95 for key pages: login, dashboard, course view, quiz attempt, gradebook.
- PHP-FPM worker saturation, CPU and memory on web nodes.
- Database CPU, slow queries and connection counts.
- Cron run times, ad hoc queue length and failing tasks.
Useful tools that are already available:
- Site administration > Reports > Performance overview lists configuration issues that affect performance, with links to fix each one.
- Performance info (under Development > Debugging) prints page generation time, database queries and cache hits in the footer for admins.
- Slow query logging: enable it in the database (for example
log_min_duration_statementon PostgreSQL or the slow query log on MySQL), or use Moodle'slogslowoption in$CFG->dboptionsduring an investigation. - Excimer profiler (
tool_excimer, a community plugin by Catalyst IT) is a low-overhead sampling profiler that is safe to run in production, records why slow pages were slow, and produces flame graphs.
Write the numbers down. Every later change should be judged against them.
2. Get the platform baseline right
Old PHP and unsupported Moodle versions cost you speed and security. Moodle releases every April and October, with a long-term support (LTS) version about every two years.
| Moodle version | Status (Sept 2026) | PHP | Minimum databases |
|---|---|---|---|
| 4.5 LTS | Security fixes until 4 Oct 2027 | 8.1 to 8.3 | PostgreSQL 13, MySQL 8.0, MariaDB 10.6.7 |
| 5.0 | Security fixes end 5 Oct 2026 | 8.2 to 8.4 | PostgreSQL 14, MySQL 8.4, MariaDB 10.11 |
| 5.1 | Supported | 8.2 to 8.4 | PostgreSQL 15, MySQL 8.4, MariaDB 10.11 |
| 5.2 | Current stable | 8.3 to 8.4 | PostgreSQL 16, MySQL 8.4, MariaDB 10.11, SQL Server 2019 |
| 5.3 LTS | Scheduled for 5 Oct 2026 | 8.3 to 8.4 | Same as 5.2 |
All current versions require 64-bit PHP, the sodium extension and max_input_vars of at least 5000. Oracle is no longer supported from Moodle 5.0. If you are on 5.0, plan your upgrade now. If you are moving to 5.1 or later, remember that the web server document root must point to the new /public directory.
3. PHP-FPM and OPcache
Run PHP through PHP-FPM behind Nginx or Apache with the event MPM. Size pm.max_children from measured memory per worker, not from guesswork: too few workers queue requests, too many push the server into swap.
OPcache must be on, and its defaults are too small for Moodle. MoodleDocs notes that Moodle 5.0 alone contains more than 17,600 PHP files totalling around 153 MB, before plugins and language packs. A sensible starting point:
opcache.enable = 1
opcache.memory_consumption = 256
opcache.max_accelerated_files = 20000
opcache.revalidate_freq = 60
opcache.use_cwd = 1
opcache.save_comments = 1
opcache.enable_file_override = 0
opcache.enable_cli = 1
Check OPcache statistics after a day of traffic: if the cache is full or keys are exhausted, increase the limits. enable_cli helps CLI cron on larger sites.
4. Caching: MUC, Redis and local cache
The Moodle Universal Cache (MUC) stores application caches in moodledata by default. On a single server with fast local disk that is tolerable; on network storage it is one of the most common causes of slowness. The fixes:
- Add a Redis cache store and map the application cache to it under Site administration > Plugins > Caching > Configuration. MoodleDocs recommends the igbinary serializer for speed and memory. Leave request caches on the default static store, because Redis is not suitable for that mode.
- Move sessions to Redis with
$CFG->session_handler_class = '\core\session\redis';and thesession_redis_*settings. Keep sessions separate from caches (a different Redis instance, or at least a different database and prefix) so a cache purge never logs everyone out. - Set
$CFG->localcachedirto fast local disk on every web node. It holds theme, JavaScript and language caches and does not need to be shared. - Confirm production settings: theme designer mode off and JavaScript caching on.
5. Database tuning
Moodle is database-heavy, and the database is usually the last bottleneck standing. Give it dedicated resources and tune for memory:
- MySQL or MariaDB: size
innodb_buffer_pool_sizeto hold your working set (MoodleDocs suggests up to 80% of RAM on a dedicated server). Ignore old advice about the query cache, which MySQL 8 removed. - PostgreSQL: tune
shared_buffers,effective_cache_sizeandwork_memfor your RAM and connection count, and keep autovacuum enabled. - Control table growth: set a log retention period for the standard log store and task logs that matches your compliance needs, rather than keeping everything forever.
- Read replicas: Moodle can send safe reads to replicas through the
readonlysection of$CFG->dboptions, supported for PostgreSQL and MySQL-family drivers, with a configurable replication latency.
Use the slow query log from step 1 to find the queries that matter; they often point straight at a plugin or a missing index in custom code.
6. Cron, scheduled tasks and ad hoc tasks
Run cron from the CLI every minute, never over HTTP. On busy sites, add dedicated ad hoc task runners, because some plugins queue very large numbers of ad hoc tasks:
* * * * * /usr/bin/php /path/to/moodle/admin/cli/cron.php
* * * * * /usr/bin/php /path/to/moodle/admin/cli/adhoc_task.php --execute --keep-alive=59
Parallelism is capped by $CFG->task_scheduled_concurrency_limit and $CFG->task_adhoc_concurrency_limit (both default to 3), so raise them as you add runners and watch database load while you do. Schedule automated course backups and other heavy tasks outside peak hours, and review the task logs for tasks that fail repeatedly or run for hours.
7. Files, web server and CDN
- Storage: keep
datarooton fast storage. In a cluster it must be shared with proper locking, whilelocalcachedirstays local. - X-Sendfile: let the web server stream files instead of PHP, for example
$CFG->xsendfile = 'X-Accel-Redirect';on Nginx with matching$CFG->xsendfilealiases. - Object storage: for large file estates, Moodle's alternative file system API lets plugins such as
tool_objectfsmove files to S3-compatible, Azure or other object storage. - Web server: enable HTTP/2 and compression, keep keep-alive timeouts short, and use the Apache event MPM or Nginx.
- CDN: theme, JavaScript and other static assets are served on revision-based URLs, which makes them good CDN candidates. Keep authenticated course files out of shared caches unless the design is deliberate.
8. Plugin and configuration audit
Third-party plugins are the most common source of unexpected load. For each additional plugin, check whether it is still used, whether it supports your Moodle version, what scheduled tasks and event observers it registers, and whether it adds work to every page. The Excimer profiler makes the expensive ones obvious. Remove what you do not need.
Also review the dashboard and course pages: heavy blocks on the dashboard, courses with thousands of activities, and gradebooks with very large numbers of items all create load that no server upgrade fully hides. Our LMS security checklist is a useful companion, since unmaintained plugins are also a security risk.
9. Load test before your peak
Only a load test tells you whether the changes worked at the concurrency you expect. Moodle includes developer tools for this under Site administration > Development: a test course generator and a JMeter test plan generator with sizes from XS to XXL. They are for developer use only, require developer debugging and must never be run on a live site, so test against a staging copy with production-like data and infrastructure.
Model the real peak, not an average: a quiz where hundreds of learners start within the same minute behaves very differently from steady browsing. Watch PHP-FPM queues, database CPU and Redis during the test, fix the first bottleneck you find, and repeat. Then compare against your baseline from step 1.
How we can help
If your Moodle site is slow or you are preparing for a busy term or exam window, our LMS performance optimization service starts with profiling and a written bottleneck report, then implements and load-tests the fixes. For ongoing upgrades, monitoring and plugin reviews, see LMS maintenance and support, or read more about our Moodle work. Get in touch to talk about your setup.
Frequently asked questions
Why is my Moodle site so slow?
The usual culprits are file-based caches on network storage, an untuned or undersized database, too few PHP workers, a backlog of cron and ad hoc tasks, or a heavy third-party plugin. Profiling a few slow pages normally reveals which one applies.
Does Redis make Moodle faster?
Usually yes. Redis moves application caches and sessions out of the file system and into memory, which cuts disk I/O and makes multi-server setups practical. Keep request caches on the default store and keep sessions separate from caches.
What PHP version should I use for Moodle in 2026?
For Moodle 5.2 and the 5.3 LTS release, use 64-bit PHP 8.3 or 8.4. Moodle 4.5 LTS supports PHP 8.1 to 8.3. Always check the release notes of your exact Moodle version before changing PHP.
How many users can a Moodle server handle?
There is no fixed number: it depends on concurrency, activity types, plugins and infrastructure. A quiz start spike is far heavier than casual browsing, so the only reliable answer comes from load testing your own site on production-like infrastructure.
Free 30-minute consultation
Not sure where to start? Talk to an LMS engineer.
Tell us what platform you run and what you need. You’ll get honest, practical advice - even if the answer is that you don’t need us.
Sources & references
- Moodle releases and support dates - Moodle
- Moodle 5.2 release notes and requirements - Moodle
- Performance recommendations - MoodleDocs
- OPcache - MoodleDocs
- Cron - MoodleDocs
- config-dist.php (read replicas, X-Sendfile, localcachedir, sessions) - Moodle (GitHub)