Installing Moodle is the easy part. The decisions you make in the first few weeks are what decide whether it runs itself for years or becomes something you nurse. Most Moodle problems we see trace back to setup: email that never arrives, learners who can register but cannot enrol, progress that never updates, and a configuration only one person understands.
We set up Moodle from scratch for a national digital-skills programme, from the server through to the branding, and then cloned it for three more countries. This guide walks through the setup in the order it actually happens, with the stack and plugins we used and the traps we hit along the way, so you can skip them.
Here is the short version:
| Step | What to decide | The trap to avoid |
|---|---|---|
| Hosting | Server size, stack, backups | Buying big before measuring real use |
| A transactional email service per site | Opening registration before email is tested | |
| Sign-up and enrolment | How learners get in and move forward | Learners who can register but cannot enrol |
| Course structure | One clear path through the programme | Modules that feel like unrelated courses |
| Completion and cron | What counts as finished, and background processing | Activities done, course still "incomplete" |
| Plugins and integrations | Only what you need, built securely | Assuming plugin settings carry over |
| Theme and branding | How far to make it look like you | Custom code tied to course IDs |
| Documentation | How it is configured and run | A platform only one person understands |
1. Hosting and the server
Each of our platforms runs on its own cloud server, and the setup is deliberately simple to repeat. Moodle 4.5 runs in Docker containers: one for the Moodle web application on PHP 8.3, and a separate one for a PostgreSQL 16 database. Nginx sits in front as the reverse proxy and handles the TLS certificate. Keeping each piece in its own container makes the whole thing easier to rebuild, back up, and copy.
For sizing, each server has 2 virtual CPUs, about 4 GB of RAM, and about 75 GB of storage. Where a second application had to share the same server, we added a 4 GB swap file, and before deciding anything bigger was needed, we measured how much memory the existing workload actually used instead of trusting generic hardware recommendations. Size for how many people will be active at the same time, not how many accounts you will have, and measure before you buy.
2. Core settings and email
Email is where new Moodle sites most often look broken, so handle it deliberately. During the build we kept outgoing email switched off using Moodle's no-email setting, so nobody received half-finished notifications. Before opening registration, we connected each platform to an external transactional email service, set up its own sender domain with the right DNS authentication records (SPF and DKIM), and sent test messages until they landed reliably.
The lesson that saved us the most time: when emails do not arrive, check the email provider's delivery logs and the sender domain's DNS first, before you start digging into Moodle itself. Most "Moodle email problems" are really domain verification problems.
If learners need more than one language, plan it now. Moodle's language packs cover most of the interface, and its built-in language customisation lets you translate strings that are missing, such as navigation labels. One catch: any labels written into your own custom code are not touched by language packs, so those need translating separately.
3. Sign-up, roles, and enrolment
Decide exactly how a learner gets from "never heard of this" to "working through Module 1," because this is where avoidable admin work is either created or prevented. On our platforms, learners create their own accounts through email-based self-registration, self-enrol in the main programme course, and then self-enrol in Module 1. Administrators keep the ability to create staff accounts by hand. Nobody adds hundreds of learners one at a time.
From there, progression is automatic. We used a course-completed enrolment plugin, which enrols a learner in the next course the moment they complete the previous one. Six rules carry learners through the whole programme in order, so nobody can skip ahead and nobody has to be moved along by hand.
Test the full journey as a brand-new learner before launch. On one of our cloned platforms, learners could register but could not enrol, because self-enrolment had not been switched on for Module 1. It is a one-minute fix, and an embarrassing one to discover from learner complaints.
4. Course structure
A learner should never have to wonder what comes next. There are two common ways to build that path. You can put everything in one course divided into sections, and use Moodle's restrict-access settings so each section opens only once the previous one is finished. Or you can make each module its own course and link them together, which makes every module its own unit of completion.
We used the second approach. The programme has one visible entry course that links out to separate module courses, and custom code hides those underlying courses from the course listings, so learners see one coherent programme rather than a pile of unrelated courses. Previous and Next buttons move them between activities. The result feels like one course, while each module tracks its own completion and unlocks the next.
5. Completion tracking and cron
If you get one step right, make it this one. Turn on completion tracking, decide what counts as complete for each activity, and set the course to require all of the relevant criteria. Then make sure Moodle's background processing is actually running, because course completion is calculated by scheduled tasks, not the instant a learner clicks.
We run Moodle's cron through Linux cron on a schedule, with a lock so two runs never overlap. The reason we care so much: on one platform, learners had completed all 26 required activities in a module, yet the course still showed as incomplete, because the scheduled completion processing was not running. Since the next module only unlocks when the previous course is complete, those learners were stuck. We repaired their records and got cron running properly, and it has processed completion automatically since. Test completion end to end, with a real test learner, before your first cohort starts.
6. Plugins and integrations
Install only what your programme actually needs. Ours came down to a short list: the Moove theme, the course-completed enrolment plugin for progression, and a custom plugin we built for an integration. Keep a written record of what is installed and why, because every plugin has to keep working through future upgrades.
Integrations deserve the most care. Our programme needed learners to activate an account in a separate data-collection system once they reached Module 3. We built a small custom Moodle plugin for it. The learner clicks a button inside the Module 3 lesson; the plugin checks that they are signed in and have access to that module, takes their verified name and email from Moodle, and sends a signed server-to-server request through a small bridge service, which creates their account invitation. The secret never reaches the learner's browser, and any request without a valid signature is rejected. Making activation on-demand also fixed an earlier problem, where invitations sent too early expired before learners needed them.
One lesson that is easy to miss: copying a plugin's code to a new site does not copy its configuration. An automation plugin we expected to carry over arrived on the cloned sites with no workflows set up at all, which is part of why we replaced it with the purpose-built plugin.
7. Theme and branding
Out of the box, Moodle looks generic. We used the Moove theme as the base and customised it with each client's logo on the login page and in the header, the programme's colours, a single global font, and custom navigation styling, using Moove's Raw SCSS setting and Moodle's additional HTML settings for extra CSS and JavaScript. For many programmes a well-branded theme like this is all they need.
Watch any custom code closely. Ours hides module courses and builds the navigation, and it depended on course IDs from the original site. After restoring courses on a cloned site, the IDs changed, and the wrong courses showed up on the homepage until we updated the code. If you need the platform to feel entirely like your own product rather than a branded Moodle, that is when a custom frontend makes sense, which we cover in why a custom frontend beats building your own LMS.
8. Load the content, test it, and document it
Content goes in once the structure is ready. We moved our courses as Moodle backup files, eight of them, verified each with a checksum after transfer so nothing arrived corrupted, and restored them one by one. The content was mostly pages, links, video lessons, and media files. Expect to repair links after a restore: links between courses and embedded file addresses both change, so search for any references to the old site until there are none left.
Then write it down. Document how the platform is configured and run: the enrolment flow, the progression rules, the plugins and why each is there, the cron setup, the email setup, and how to read the completion reports. It is the single thing that stops a platform depending on one person's memory, and it is what makes a real handover possible.
Build it once, then clone it
This is the payoff of doing everything above carefully. When the programme expanded to three more countries, we did not start from nothing three times. We also did not copy the whole database. Instead we copied the Moodle code and plugins from the working site, left out its configuration file, installed Moodle fresh on each new server with its own database and settings, and restored each course from its backup. Before each high-risk change, such as bulk link repairs or plugin installs, we took a database checkpoint we could roll back to.
Then came the per-country checklist, which we now treat as the standard for any clone:
- Domain, DNS, and TLS certificate
- Moodle's public site address
- Course and file references remapped after restore
- Country branding and logo
- Custom navigation code updated for new course IDs
- Language and translated labels
- Email service and sender domain verification
- Cron switched on and checked
- Progression rules recreated
- A full completion test with a test learner
- Integrations reconnected and tested
Change the domain, the proxy, the certificate, and the site address in a controlled order, one at a time, because doing them all at once is how a site ends up redirecting to the wrong address with a certificate error. You can read the full project in our learning platform case study.
The lessons, in one list
- Test email before opening registration, and check DNS and provider logs before blaming Moodle.
- Walk through sign-up as a brand-new learner before anyone real does.
- Confirm cron and completion processing work before the first cohort starts.
- Never assume custom code survives a restore. Course IDs change.
- Plugin code is not plugin configuration. Recheck settings on every new site.
- Keep integration secrets on the server and reject unsigned requests.
- Take a checkpoint before bulk changes, and break long scripts into small stages.
- Measure the real workload before paying for a bigger server.
Frequently asked questions
What server do you need for Moodle?
It depends on how many people use it at the same time. Each of the platforms we built runs on its own cloud server with 2 virtual CPUs, about 4 GB of RAM, and about 75 GB of storage, with Moodle and its PostgreSQL database in Docker containers behind an Nginx reverse proxy. Where a second application shared the server, we added swap and measured real memory use before deciding anything bigger was needed.
Why does Moodle course completion not update?
The most common cause is that Moodle's scheduled tasks are not running. Course completion is calculated by background tasks driven by cron, so a learner can finish every required activity while the course still shows as incomplete. Make sure cron is running on a schedule, then test completion end to end before real learners start.
How do you clone a Moodle site for another country or organisation?
We copied the Moodle code and plugins from the working site, installed it fresh on a new server with its own database and configuration, then restored each course from Moodle backup files. After that we updated everything that changes per site: the address, certificate, branding, email setup, course links, progression rules, and cron. Plugin settings and any custom code that depends on course IDs must be checked, because they do not carry over automatically.
Can I set up Moodle myself?
Yes. Moodle is open source and well documented, and installing it is the easy part. Where experience pays off is configuration: email, enrolment and progression, completion processing, branding, integrations, and keeping the setup clean enough to repeat. For what professional help costs, see our guide to how much a custom LMS costs.
Getting it right the first time
Set Moodle up in order: hosting, settings and email, sign-up and enrolment, course structure, completion and cron, plugins and integrations, branding, then content, testing, and documentation. None of it is glamorous, and all of it decides whether your platform runs itself or runs you. Do it carefully once and you also get a template you can roll out again, which is exactly how one platform became four.
At KWA Digital we set up Moodle from scratch for schools and training programmes, build the integrations they need, and build custom frontends when a programme needs to go further. See our LMS development service or get in touch and we will talk through your setup.
Setting up Moodle for your programme?
Tell us about your learners, your courses, and how you want to run them, and we will show you how we would set the platform up, properly, from day one.
See our LMS development service