Moodle - Hardened Self-Hosted LMS Platform
بواسطة Lynxroute
Moodle 4.5.14 - CIS Level 1 hardened self-hosted LMS on Ubuntu 24.04 LTS
What is Moodle
Moodle is the world's most widely deployed open-source learning management system (LMS), implemented in PHP 8.3 and served by nginx with PHP-FPM over MySQL 8.0. This image is powered by Moodle™ LMS. It delivers the full teaching and training workflow - courses, enrolment, quizzes, assignments and rubric grading, the gradebook, forums and messaging, SCORM and H5P content, completion tracking, badges and certificates, and reporting - with role-based access control, cohorts, groups, multi-language support, a REST/web-services API, and an extensive plugin and theme ecosystem. It connects to any standard SMTP relay and supports SSO via OAuth2, SAML and LDAP. This image ships the GPL-3.0 community edition and runs entirely on the VM you launch - no external account, subscription or license key, all data inside your own Azure tenant. Moodle™ is a registered trademark of Moodle Pty Ltd; this product is community-built and not affiliated with or endorsed by Moodle Pty Ltd.
Why self-host Moodle
Running Moodle on a VM you control keeps your courses, learner records and grades inside your own tenant rather than a managed education service. Self-hosting suits institutions with data-residency requirements (GDPR, FERPA, ISO 27001) and any training programme where learner data must stay within your perimeter with no per-seat fees. Moodle is GPL-3.0, fully auditable, no vendor lock-in.
What this VM image adds
Security hardening:
- Administrator password generated uniquely per instance on first launch - never baked in - written to /root/moodle-credentials.txt (mode 0600)
- Site installed automatically on first launch via the Moodle CLI installer - no setup wizard exposed on the public internet
- moodledata outside the web root - course files, sessions and caches on a dedicated path with non-world-readable permissions (mode 02770)
- MySQL 8.0 bound to 127.0.0.1 only - the database user password is generated per instance
- nginx terminates TLS on port 443 with a self-signed certificate at first launch; HTTP on port 80 redirects to HTTPS; certbot pre-installed for Let's Encrypt
- PHP-FPM and MySQL InnoDB buffer pool auto-sized to instance RAM at first boot
- Scheduled-task runner runs every minute as a systemd service - no manual cron
- Site address reconciled at boot - the LMS keeps serving after a stop/start changes the IP
- UFW firewall - TCP 443 and 80 for buyer use, TCP 22 for SSH; all other inbound dropped
- fail2ban - SSH brute-force protection
- AppArmor - mandatory access control
- CVE scan - every image scanned with Trivy before release
OS hardening (CIS Level 1):
- CIS Ubuntu 24.04 LTS Level 1 Benchmark via ansible-lockdown
- auditd for system call auditing of critical paths
- SSH hardening - PasswordAuthentication disabled, key-only access, PermitRootLogin no, LoginGraceTime 60
- Kernel hardening - SYN cookies, ASLR, rp_filter, kexec disabled, IPv6 off
- /tmp as tmpfs - nosuid, nodev, noexec
Compliance artifacts (inside the VM):
- SBOM - CycloneDX 1.6 at /etc/lynxroute/sbom.json with Moodle pinned by version, PURL, GPL-3.0 license and hash
- CIS Conformance Report at /etc/lynxroute/cis-report.html (OpenSCAP, Azure tailoring profile, 0 FAIL rules)
- Tailored CIS profile at /usr/share/doc/lynxroute/
- Operator credentials file at /root/moodle-credentials.txt (mode 0600) with the admin username, password and HTTPS Web UI URL
Quick Start
- Deploy VM from Azure Marketplace (Standard_D2s_v3 recommended)
- Open NSG: TCP 443 from your trusted sources first, TCP 80 for the redirect / Let's Encrypt, TCP 22 from your management IPs only
- Wait on first launch - the URL shows a "Starting up" splash for ~2-5 minutes while the schema is built and the admin account is created
- SSH: ssh -i key.pem azureuser@<PUBLIC_IP>, then sudo cat /root/moodle-credentials.txt for the admin password
- Open https://<PUBLIC_IP>/, accept the self-signed certificate warning, log in as admin; change the password and set a real admin email, then create your courses
MySQL listens on 127.0.0.1 only; nginx is the TLS perimeter on port 443. The site address tracks the instance IP; to use a domain, point it at the instance and run sudo certbot --nginx -d your.domain.com, then sudo systemctl reload nginx.