Deployment guide

Moodle HA

Overview

This product deploys Moodle as a production-grade, highly available environment using AWS CloudFormation. Instead of a single instance, it creates a complete multi-tier stack in its own VPC across two Availability Zones. The platform tolerates the loss of a node or an Availability Zone and scales the web tier to demand.

The CloudFormation template reuses the SecureAnyCloud Moodle Amazon Machine Image as its web node, so you do not need to build an image of your own. On launch, a bootstrap script makes that image stateless: it mounts shared storage, points Moodle at the managed database and cache, performs a one-time schema install, and configures the application to run behind the load balancer.

What the stack creates:

  • AWS WAF with AWS managed rules, attached as a Web ACL on the load balancer (optional).
  • Application Load Balancer spanning two Availability Zones, terminating HTTPS.
  • Auto Scaling group of stateless Moodle web nodes, plus a single dedicated cron node for Moodle scheduled tasks.
  • Aurora Serverless v2 (PostgreSQL) database, with optional Multi-AZ.
  • Amazon EFS for the Moodle data directory (shared course files), Regional or One Zone.
  • ElastiCache Serverless for Valkey for sessions and cache, so any web node can serve any user.
  • AWS Secrets Manager for the database and administrator credentials, NAT for private egress, and least-privilege IAM roles.
  • 24/7 SecureAnyCloud support

Architecture

Traffic flows from learners through AWS WAF and the Application Load Balancer to the Auto Scaling web tier. Every web node is stateless: sessions and cache live in Valkey, uploaded files live on EFS, and all course data lives in Aurora. The load balancer can therefore route any request to any node, and the Auto Scaling group can replace an unhealthy node automatically.

The VPC is split into three subnet tiers, each across both Availability Zones:

  • Public subnets: load balancer and NAT.
  • Private application subnets: web and cron nodes.
  • Isolated data subnets: database, EFS mount targets and cache.

Prerequisites

  • An AWS account and a subscription to this product in AWS Marketplace.
  • An EC2 key pair in the deployment Region, for administrative SSH access to the web nodes.
  • The public IP address you will administer from, as a CIDR (for example 203.0.113.4/32). The template never opens SSH to the whole internet.
  • For production HTTPS: an AWS Certificate Manager certificate in the deployment Region, and a domain name you control.
1

Deploy from the AWS Marketplace console

  • Open the product in AWS Marketplace. Choose Continue to Subscribe, then Continue to Configuration.
  • Choose the Moodle HA CloudFormation delivery option and your Region, then choose Continue to Launch.
  • Choose Launch CloudFormation. The Marketplace fills in the Moodle AMI for the ImageId parameter automatically.
  • Complete the parameters (see Stack parameters below), acknowledge that the stack creates IAM resources, and create the stack.
  • Wait for the stack to reach CREATE_COMPLETE. Then allow a few minutes for the web nodes to finish bootstrapping and become healthy behind the load balancer.
2

Deploy from the AWS CLI

After you subscribe, deploy the template directly. Supply the subscribed Moodle AMI ID for ImageId, shown on the Marketplace launch page:

aws cloudformation create-stack \
  --stack-name moodle-ha \
  --template-url https://sac-website-prod.s3.amazonaws.com/marketplace/cfn/moodle-ha-aws/moodle-ha.yaml \
  --capabilities CAPABILITY_IAM \
  --parameters \
  ParameterKey=ImageId,ParameterValue=ami-0example1234567890 \
  ParameterKey=KeyName,ParameterValue=my-key \
  ParameterKey=AvailabilityZone1,ParameterValue=us-east-1a \
  ParameterKey=AvailabilityZone2,ParameterValue=us-east-1b \
  ParameterKey=AdminCidr,ParameterValue=203.0.113.4/32 \
  ParameterKey=CertificateArn,ParameterValue=arn:aws:acm:us-east-1:111122223333:certificate/abcde \
  ParameterKey=DomainName,ParameterValue=moodle.example.com

Watch the stack's progress with:

aws cloudformation describe-stacks --stack-name <stack-name> --region <region> \
  --query 'Stacks[0].StackStatus' --output text

Stack parameters

  • ImageId: the Moodle web AMI. AWS Marketplace injects it automatically.
  • InstanceType: EC2 type for web and cron nodes. Default m5.large.
  • KeyName: EC2 key pair for SSH administration. Required.
  • VpcCidr: CIDR for the new VPC. Default 10.0.0.0/16.
  • AvailabilityZone1 / AvailabilityZone2: the two Availability Zones for the layout. Pick two distinct zones.
  • AdminCidr: CIDR allowed to SSH to the nodes. Use your IP as a /32, never 0.0.0.0/0.
  • CertificateArn: ACM certificate for HTTPS. Leave blank for an HTTP-only test.
  • DomainName: site domain. Optional; otherwise the load balancer DNS name is used.
  • EnableWAF: attaches AWS WAF managed rules. yes or no (default yes).
  • MinSize / MaxSize / DesiredCapacity: Auto Scaling web tier sizing. Default 2 / 4 / 2.
  • DbMultiAz: Aurora writer plus reader across two Availability Zones. yes for HA, no for lower cost.
  • DbMinAcu / DbMaxAcu: Aurora Serverless v2 capacity range. Default 0.5 to 4 ACUs.
  • EfsHighAvailability: Regional (multi-AZ) or One Zone EFS. yes for HA, no for lower cost.
  • MoodleAdminEmail: site administrator email address.

First access

Confirm the stack finished and read its Outputs:

aws cloudformation describe-stacks --stack-name <stack-name> --region <region> \
  --query 'Stacks[0].StackStatus' --output text | grep -qx CREATE_COMPLETE && echo "stack CREATE_COMPLETE"
aws cloudformation describe-stacks --stack-name <stack-name> --region <region> \
  --query 'Stacks[0].Outputs' --output table
  • MoodleURL: the site address (your domain, or the load balancer DNS name).
  • AlbDnsName: the load balancer DNS name to point your domain at.
  • DbSecretArn: the AWS Secrets Manager secret that holds the database and administrator credentials.

First, wait until every web node is healthy behind the load balancer. The one-time schema install finishes shortly after the stack completes, so this command polls. It also catches a node that deployed but cannot serve, for example because a storage mount did not attach, which a single page request would hide:

for i in $(seq 1 96); do states=$(aws elbv2 describe-target-health --target-group-arn "<TargetGroupArn>" --region <region> --query 'TargetHealthDescriptions[].TargetHealth.State' --output text); healthy=$(printf '%s' "$states" | tr '[:space:]' '\n' | grep -c '^healthy$'); other=$(printf '%s' "$states" | tr '[:space:]' '\n' | grep -cv '^healthy$'); echo "targets healthy=$healthy other=$other"; [ "$healthy" -ge 1 ] && [ "$other" -eq 0 ] && break; sleep 15; done; [ "$healthy" -ge 1 ] && [ "$other" -eq 0 ]

Next, confirm that the load balancer serves the Moodle sign-in page. Once your DNS points at AlbDnsName, the --resolve flags are unnecessary. They let you verify immediately, before DNS propagates, by pinning your site host to the load balancer (across all of its IPs):

url="<MoodleURL>"; host=$(printf '%s' "$url" | sed -E 's#^https?://##; s#/.*##'); albdns="<AlbDnsName>" for i in $(seq 1 96); do res=""; if [ "$host" != "$albdns" ]; then albips=$(dig +short "$albdns" | grep -E '^[0-9.]+$' | paste -sd, -); [ -n "$albips" ] && res="--resolve $host:443:$albips --resolve $host:80:$albips --resolve $albdns:443:$albips --resolve $albdns:80:$albips"; fi; code=$(curl -fsS -o /dev/null -w '%{http_code}' -L $res "$url/login/index.php" 2>/dev/null || echo 000); [ "$code" = "200" ] && { echo "login page HTTP 200 after ~$((i*15))s"; break; }; sleep 15; done; [ "$code" = "200" ]

The administrator password is generated during the one-time schema install and stored in Secrets Manager. It is never shipped in the image. Retrieve it and confirm that both credential keys are present. The install writes them shortly after the site comes up, so this command polls:

for i in $(seq 1 24); do out=$(aws secretsmanager get-secret-value --secret-id "<DbSecretArn>" --region <region> --query SecretString --output text 2>/dev/null); printf '%s' "$out" | python3 -c 'import sys,json; d=json.load(sys.stdin); assert d.get("moodle_admin_user") and d.get("moodle_admin_pass")' 2>/dev/null && break; sleep 15; done; printf '%s' "$out" | python3 -c 'import sys,json; d=json.load(sys.stdin); assert d.get("moodle_admin_user") and d.get("moodle_admin_pass"); print("admin credentials present for", d["moodle_admin_user"])'

Open MoodleURL in a browser and sign in as admin with that password.

High availability behavior and toggles

  • Web tier: the Auto Scaling group runs across both Availability Zones behind the load balancer. If a node fails its health check, the group replaces it. Web nodes are stateless, so no user data is lost.
  • DbMultiAz: yes runs an Aurora Serverless v2 writer plus a reader in a second Availability Zone. no runs a single instance for lower cost.
  • EfsHighAvailability: yes uses Regional EFS with a mount target in each Availability Zone. no uses One Zone EFS for lower cost.
  • EnableWAF: attaches AWS managed rule groups to the load balancer to filter common web exploits.

Sessions and cache run on ElastiCache Serverless for Valkey, which is multi-AZ by design. A user signed in on one node continues seamlessly on another.

Scaling and cron

Adjust DesiredCapacity, MinSize and MaxSize to size the web tier, or attach Auto Scaling policies to scale on load.

A single dedicated cron node runs moodle-cron.timer. The web nodes have the cron timer disabled, so scheduled tasks run exactly once per minute regardless of how many web nodes are in service.

HTTPS and a custom domain

For production, supply CertificateArn (an ACM certificate in the deployment Region) and DomainName. The load balancer then serves HTTPS and redirects HTTP, and Moodle is configured with the matching site address and proxy settings.

Create a DNS record that points your domain at the AlbDnsName output.

Security

  • The stack deploys into a dedicated VPC with public, private application and isolated data subnets across two Availability Zones.
  • SSH is restricted to your AdminCidr. The database, EFS and cache accept traffic only from the web tier security group.
  • Database and administrator credentials are generated at deploy time and stored in AWS Secrets Manager.
  • IAM instance roles are least-privilege (AWS Systems Manager core only), no long-lived keys are used, and IMDSv2 is enforced.
  • AWS WAF managed rules protect the public endpoint when enabled.

Backups and maintenance

  • Aurora takes automated backups, and you can take manual snapshots at any time. EFS stores data redundantly within the Region.
  • To move to a newer Moodle AMI version, update the stack with the new ImageId. The Auto Scaling group rolls the web nodes.
  • Application data is never stored on the web nodes, so you can replace them freely.

Support

This product includes 24/7 technical support from SecureAnyCloud by email and chat. Support covers deployment, scaling, upgrades, plugin configuration and database administration. Contact sales@secureanycloud.com.

Deploy on AWS

Launch Moodle HA on AWS with 24/7 support from SecureAnyCloud.

View on Marketplace

Need Help?

Our support team is available 24/7.

sales@secureanycloud.com

Deploy on AWS