Size Moodle hosting by concurrent (peak) users, not total accounts — peak concurrency is usually only 10–25% of your total user base. A single well-tuned server (4–8 vCPU, 16 GB RAM) comfortably serves up to roughly 100–200 concurrent users. Beyond that you scale in stages: first split the database and cache onto their own servers, then add multiple load-balanced application nodes with shared moodledata for thousands of concurrent users, and finally a high-availability setup with database replicas and object storage for 10,000+. The database is almost always the first bottleneck. edzlms runs Moodle on this exact staged architecture as a managed, performance-tuned service, sized to your real concurrency.
Key takeaways
- Capacity planning starts with peak CONCURRENT users, not total enrolments — estimate concurrency at 10–25% of your active base.
- Scale in four stages: single server → split DB + cache → horizontal app nodes → full high availability. Don't over-build before you need to.
- The database is the first thing to buckle under load; give it dedicated resources before you add web nodes.
- The moment you run more than one application node, moodledata and sessions must be shared (object storage / NFS + Redis) or logins and files break.
- Slow Moodle is not just an IT issue — latency drives learner drop-off and lower completion, so architecture is a learning-outcomes decision.
- This is Spoke 1 of the Moodle performance series; deep PHP/DB and caching tuning are covered in the linked pillar and companion posts.
This is part of our Moodle Performance Optimization Guide (2026) — the pillar covers all five performance levers; this spoke goes deep on the first one: hosting and server architecture.
Size by concurrent users, not total accounts
The most common Moodle sizing mistake is planning around total enrolments. A platform with 10,000 registered learners does not need to serve 10,000 simultaneous requests — it needs to serve however many are online at the same moment during your busiest period. That peak concurrency is the number that sizes your hardware, and it is usually a small fraction of the total.
| Usage pattern | Peak concurrency (of total users) | Example |
|---|---|---|
| Self-paced, spread across the day | ~5–10% | 10,000 users → ~500–1,000 concurrent |
| Corporate training, business hours | ~10–20% | 5,000 users → ~500–1,000 concurrent |
| Scheduled exams / live cohorts | ~25–40%+ | 2,000 users → ~500–800 concurrent spikes |
If you run timed assessments or instructor-led sessions where everyone logs in at once, plan for the spike, not the average — those synchronised events are what bring under-provisioned servers down.
The four architecture stages
Moodle scales horizontally very well, but you should grow into each stage rather than over-engineering on day one. Here is the progression and roughly where each stage tops out:
| Stage | Peak concurrent | Approx. total users | Architecture |
|---|---|---|---|
| 1. Single server | up to ~100–200 | up to ~1,000 | All-in-one: web + PHP-FPM + DB + Redis + moodledata on one box |
| 2. Split services | ~200–500 | ~1,000–5,000 | Dedicated DB server + Redis server; app stays separate |
| 3. Horizontal scale | ~500–2,000 | ~5,000–20,000 | Load balancer + multiple app nodes, shared moodledata, DB read replicas |
| 4. High availability | 2,000+ | 20,000+ | Multi-AZ HA, DB primary+replicas, object storage, autoscaling, CDN |
The ranges overlap because tuning matters as much as tier — a well-configured single server can beat a poorly-tuned split setup. Treat these as planning guides, then load-test against your real content and concurrency.
Stage 1 — the single well-tuned server
Most organisations start (and many happily stay) here. One server runs the web server, PHP-FPM, the database, Redis and the moodledata directory. The goal is to right-size CPU and RAM, then make sure PHP-FPM and the database buffer pool are tuned to actually use that RAM.
| Peak concurrent | vCPU | RAM | Notes |
|---|---|---|---|
| up to ~50 | 2–4 | 8 GB | Small team / pilot |
| ~50–100 | 4 | 16 GB | Tune OPcache + PHP-FPM pool |
| ~100–200 | 8 | 16–32 GB | Start planning to split the DB |
The single biggest web-tier lever is the PHP-FPM process pool. Each PHP-FPM child handles one request at a time and consumes memory, so you size pm.max_children by how much RAM you can spare divided by the average process size:
; /etc/php/8.3/fpm/pool.d/www.conf ; Rule of thumb: max_children = (RAM for PHP) / (avg PHP-FPM process ~64-128MB) ; e.g. 8GB reserved for PHP / 100MB ≈ 80 pm = dynamic pm.max_children = 80 pm.start_servers = 20 pm.min_spare_servers = 10 pm.max_spare_servers = 30 pm.max_requests = 500 ; recycle workers to curb memory leaks
Set max_children too high and a traffic spike swaps the box to death; too low and requests queue even though CPU is idle. Measure your average process size under load, then size deliberately. Deep PHP, OPcache and database tuning is the subject of Spoke 2 — here we care about the architecture around it.
Stage 2 — split the database and cache off first
When one server can no longer keep both PHP and the database happy, the first move is not a second web server — it is giving the database its own machine. The DB is the shared bottleneck every request touches, and it competes with PHP for exactly the same RAM. Separating them lets you tune each independently and size the database's InnoDB buffer pool to hold your working set in memory:
# my.cnf on the dedicated DB server (MariaDB/MySQL) # Give InnoDB ~60-70% of the DB server's RAM innodb_buffer_pool_size = 12G # on a 16GB DB box innodb_buffer_pool_instances = 8 innodb_flush_log_at_trx_commit = 2 # faster; small durability trade-off max_connections = 300 # >= peak PHP-FPM children across app nodes innodb_flush_method = O_DIRECT
At the same time, move Redis (used for Moodle's Universal Cache / MUC and sessions) onto its own service so cache pressure never evicts database pages. A typical Stage 2 layout is three roles: app server (web + PHP-FPM + moodledata), database server, and Redis server. You are still single-app-node here, so moodledata can stay local — that changes in Stage 3.
Redis/MUC and CDN specifics are covered in Spoke 3 of this series.
Stage 3 — scale out to multiple app nodes
Past roughly 500 concurrent users you scale the application tier horizontally: put a load balancer in front of two or more identical app nodes. This adds throughput and removes the app server as a single point of failure — but it introduces the rule that trips up most self-hosters: state can no longer live on any single node.
Three things must become shared
- moodledata — the shared files directory must be reachable by every app node, via NFS or an S3-compatible object store. If node A writes an uploaded file and node B can't see it, learners get broken downloads.
- Sessions — store sessions in Redis (not on local disk) so a learner load-balanced to a different node stays logged in. Configure this in
config.php:
// config.php — shared Redis sessions across app nodes $CFG->session_handler_class = '\\core\\session\\redis'; $CFG->session_redis_host = '10.0.0.20'; // Redis server $CFG->session_redis_port = 6379; $CFG->session_redis_acquire_lock_timeout = 120; $CFG->session_redis_lock_expire = 7200;
- Cron — run Moodle's scheduled cron on exactly one node (or a dedicated worker), never on all of them, to avoid duplicate task execution.
For the database, add one or more read replicas and point Moodle's read-only queries at them to take load off the primary. With shared state handled, you can add or remove app nodes freely — the basis for Stage 4.
Stage 4 — high availability for 10,000+
At the top tier the goal shifts from raw capacity to resilience: no single failure should take the platform down, and capacity should flex with demand. The building blocks:
- Multiple app nodes across availability zones behind a highly-available load balancer, with autoscaling on CPU/traffic for exam-day spikes.
- Database HA — a primary with synchronous/asynchronous replicas and automatic failover; reads spread across replicas.
- Object storage for moodledata (S3-compatible) instead of a single NFS box, removing a shared point of failure.
- Redis HA (replicated/clustered) for sessions and MUC.
- CDN in front of static assets and media to cut origin load and latency for distributed learners.
This is real infrastructure engineering, and it is where DIY self-hosting gets expensive in both cloud spend and specialist time. The learning-outcomes payoff is concrete: even during your busiest assessment window, pages stay fast, sessions hold, and learners don't abandon mid-course because the site stalled.
- 1Estimate peak concurrency
Take your active user count and apply a realistic peak factor (10–25%, higher for timed exams). This single number drives the whole design.
- 2Pick the lowest stage that fits
Match concurrency to Stage 1–4. Don't build HA for 80 concurrent users — right-size, then grow.
- 3Tune before you scale out
Max out a well-configured single server (OPcache, PHP-FPM pool, InnoDB buffer pool) before adding machines. It's cheaper and simpler.
- 4Split the database first
When one box strains, give the DB its own server before adding web nodes — it's the shared bottleneck every request hits.
- 5Share state before adding app nodes
Move sessions to Redis and moodledata to shared storage, and pin cron to one node, the moment you run more than one app server.
- 6Load-test against real content
Simulate your peak (e.g. an exam start) with realistic courses and users, then adjust. Never size on guesswork alone.
DIY self-hosted scaling
- You size, tune and load-test every tier yourself
- You engineer shared moodledata, Redis sessions & DB replicas
- You own failover, patching, backups and 3am incidents
- Cloud bill and specialist time grow fast at Stage 3–4
- Mis-sizing shows up as exam-day outages
Managed Moodle (edzlms)
- Architecture sized to your real peak concurrency
- Shared storage, caching & DB scaling pre-engineered
- HA, failover, backups and patching handled for you
- Predictable pricing instead of runaway infra spend
- Performance tuned so latency stays low under load
Not sure which stage you need?
Tell us your active user count and how learners use the platform (self-paced vs timed exams vs live cohorts) and we'll size the right architecture — and run it for you as managed, performance-tuned edzlms Moodle hosting in India.
Design for your spike, not your average
If you run timed assessments or instructor-led sessions, everyone logs in at once. Size for that peak minute — it's what actually takes servers down — and use autoscaling so you're not paying for it 24/7.
Frequently asked questions
How many users can a single Moodle server handle?
A well-tuned single server (4–8 vCPU, 16 GB RAM) typically handles about 100–200 concurrent users, which can map to well over 1,000 total accounts depending on how spread-out usage is. Tuning OPcache, the PHP-FPM pool and the InnoDB buffer pool matters as much as raw hardware.
Should I size Moodle by total or concurrent users?
Concurrent (peak) users. Peak concurrency is usually only 10–25% of total accounts, and it's the number that determines how much CPU, RAM and how many app nodes you need. Timed exams and live sessions push concurrency higher.
What is the first bottleneck when Moodle slows down under load?
Almost always the database. Before adding web servers, give the database its own machine and size the InnoDB buffer pool to hold your working set in RAM. Only then scale the application tier.
What breaks when I add a second Moodle app server?
Anything stored on a single node. moodledata must move to shared storage (NFS or S3-compatible), sessions must move to Redis, and cron must run on only one node. Miss these and you get broken file downloads, random logouts, or duplicated scheduled tasks.
Do I need high availability for a few hundred users?
Usually not. A tuned single server or a split DB+cache setup comfortably serves several hundred concurrent users. Full HA (replicas, object storage, autoscaling) is for thousands of concurrent users or where downtime is unacceptable.
Does slow hosting really affect learning?
Yes. Added page latency increases drop-off and reduces course completion — learners abandon slow pages. So server architecture is not just an IT concern; it directly affects training outcomes and ROI.
Get an architecture sized to your real load
Whether you're at 100 users on one server or planning for 10,000 across a high-availability cluster, the right design comes from your actual peak concurrency and usage pattern. Share those and we'll map the stage you need — and run it for you, tuned and monitored, as managed edzlms Moodle hosting.
Next in the series: the Moodle Performance Optimization pillar ties together architecture, PHP/OPcache, database, caching and CDN.
Prefer to pick a slot directly? Grab a time here, or email marketing@edzlms.com.
Written by Mihir Jana, founder of edzlms — connect on LinkedIn.