Guide to Setup a Moodle website in Google Cloud Platform (GCP) with HTTPS
Tags: googlecloud, moodle, cloud, setup
Published: 2021-06-25 18:14:14

**Moodle** has been great for supporting many schools, campuses, and even companies in digitalized education system. Moodle is quite stable to be deployed by IT teams or individual for their organizations, simple to customized. It can be deployed offline or online depending on needs and situation. There are _many third-party software installers_ that can ship and deploy Moodle almost instantly, most of them are _managed by another third-party hosting services_.

While it's pretty quick and simple to start, but at some point it's becoming difficult to maintain and keep up-to-date. We may **not have a full control of the server** that hosts our Moodle, hence we can't setup some new requirements needed or do advanced tinkering. We may **overprovision resources** that host the Moodle application, when most of the time it's always in **underutilization**. We also may **have corrupted Moodle installation** from those third-party tools, that we don't notice at first but then it's showing everywhere. We also may forget at some point to check **whether our website is using a secure protocol (HTTPS) or not**. Yeah, lot of risks, but it's not the end of the world.

In this post, I will guide you **how to setup a Moodle LMS website hosted in a Compute Engine (CE) instance in Google Cloud Platform (GCP)**. The application will be secured using free and open source SSL certificate service so we don't have to pay if we don't want to.

## Post Updates

- 30 June 2021: Modify cron steps as we only setup using PHP 7.4 and add opinion about scaling Moodle
- 29 June 2021: Add more reference and set server time zone

## Assumptions 🤔
Below is the list of environment and app version used in this guide (at the time this post out):
- Moodle 3.11+
- PHP 7.4.20 and 8.0
- MySQL 8
- Ubuntu 20.04 LTS

While it's based on my experience, please note that you may adjust some steps to suit your needs. Another disclaimer is that the steps I'm going to show can be subjective, so I make some assumptions:
- You already have an active Billing Account set for your organization (or your personal Google Account).
- You are already prepared to pay for using GCP resources and you aware that pay-per-use model needs extensive monitoring of resource usage to avoid overpricing.
- You already have a DNS Records management set elsewhere outside GCP (eg. cPanel user account).
- You already have control of a school/campus domain name.
- You are trying to setup your Moodle LMS to be accessible on a subdomain or domain.
- Your user size won't be too big to handle, hopefully (approx. 10-100 concurrent users).
- You already have familiar knowledge of GCP, their policies, and techniques.

## Overview 📑

There are 5 main steps to setup everything, listed as below:
1. [Provision a Compute Engine instance](#step-1)
2. [Install server environments](#step-2)
3. [Make our subdomain/domain secure](#step-3)
4. [Get Moodle through Git](#step-4)
5. [Setup Moodle specific configurations](#step-5)

## Step 1: Provision a Compute Engine instance <a name="step-1"></a>
First things first, you have to **create a CE instance in GCP**. It's pretty easy and straightforward. You may want to use `gcloud shell` commands, but I prefer to use the Cloud Console web interface.

As for initial setup, you can start with as low as below specifications:
- Region/Zone: choose the **nearest possible one** from your place (the price may be higher, but it provides lower latency). Our instance is **zonal resource**.
- Machine configuration:
  - Machine family: **General Purpose**
  - Series: **E2**
  - Machine type: **e2-micro** (1 shared-core up to 2 vCPUs and 1 GB memory which only costs around **US$11.14 per month on full run**)
- Boot disk:
  - Operating system: **Ubuntu**
  - Version: **latest LTS possible** (choose 20.04 LTS as of the time I wrote this post)
  - Boot disk type: **SSD persistent disk**
  - Size: **10 GB** (only costs around **US$2.21 per month**)
- Firewall: **Check both Allow HTTP traffic and Allow HTTPS traffic**
- Under **Networking** tab in advance configuration, click on one of the **Network interfaces** (should be **default** at first). You may want to use **Static External IP instead of Ephemeral**.
- (Optional) SSH keys: Provide your public SSH key(s) generated from your PC. Make sure your private SSH key(s) is secured in your user directory. Although it's optional, but I use this to provide access to the VM directly from my Terminal without bother opening Google Cloud Console in browser.

_Noted that this specification is the lowest possible one and hopefully cost-effective. You may choose higher resources, but **it's a good thing to start small when you are working in the cloud**._

## Step 2: Install server environments <a name="step-2"></a>

Please make note of your **instance's external IP address**.

If you choose to provide SSH key(s) to the instance, then you can open your terminal and try to connect through it with this command (I'm using Windows Terminal btw):
```shell
ssh <your_username>@<external_ip> -i <full/path/to/your/private/keys>
```
Otherwise, you can click on SSH button inline with instance name in **Compute Engine > VM Instances table**. This will open a new browser window that showing terminal access to the instance.

### Set server time zone

Before we continue further, we need to make sure that our server time zone matches with ours. We can check via below command.

```shell
timedatectl status
```

If it shows different time zone than yours, then you need to change it. We can check available time zones with below command.

```shell
timedatectl list-timezones
```

Find your time zone (mine is Asia/Jakarta, which is UTC+7), and then execute command below to set your time zone.

```shell
sudo timedatectl set-timezone <your time zone>
```

At this point, I reboot the machine, although the server's time zone already changed. Just to make sure, of course.

### Environment preparations

Now we are going to install environments needed, based on the official guide for installing Moodle in Ubuntu ([check it here](https://docs.moodle.org/311/en/Step-by-step_Installation_Guide_for_Ubuntu)). I will show you here anyway.

Update Ubuntu components
```shell
sudo apt-get update
sudo apt-get upgrade
```
Add apt repository to track updates for PHP
```shell
sudo add-apt-repository ppa:ondrej/php
sudo apt-get update
```
Install Apache and MySQL
```shell
sudo apt install apache2 mysql-client mysql-server php7.4 libapache2-mod-php7.4
```
Run MySQL secure installation. You'll be asked for a new ROOT password using options of validation (choose 1 = MEDIUM). Then you'll just confirm following questions with Y (yes). This has to be done to make your MySQL server as secure as possible by disabling remote root access and provide strict validation when creating passwords.
```shell
sudo mysql_secure_installation
```
Install additional software or dependencies. Please be aware that we are going to install components for PHP 7.4.
```shell
sudo apt install graphviz aspell ghostscript php7.4-pspell php7.4-curl php7.4-gd php7.4-intl php7.4-mysql php7.4-xml php7.4-xmlrpc php7.4-ldap php7.4-zip php7.4-soap php7.4-mbstring
```
Restart Apache server to make sure all components are loaded successfully.
```shell
sudo service apache2 restart
```
Install Git (for Step 4).
```shell
sudo apt install git
```

### Check listening ports

We need to check whether port 80 (not secure) and 443 (secure) is listening from your machine by running this command.
```shell
sudo ss -tulwn
```
If port 443 is not listed, we need to install ssl module for Apache.
```shell
sudo a2enmod ssl
```
Restart Apache once again
```shell
sudo service apache2 restart
```
Now check again all listening ports. Port 443 should be listed as LISTEN.
```shell
sudo ss -tulwn
```

## Step 3: Make our subdomain/domain secure <a name="step-3"></a>
Now we need to make sure that our subdomain (or main domain) uses HTTPS. That means, we need an SSL certificate. We will use the free way to obtain that.

First, in your DNS records manager, **add or modify two A records** just like below. By doing this, we are going to make sure that our subdomain/domain will pinpoint to your machine external IP address. In order (Name, TTL, Type, Record):
- example.com, 14400, A, (your external IP)
- www.example.com., 14400, A, (your external IP)

Then, we are going to install **Certbot**, **a free**, open source software tool for automatically using **Let’s Encrypt** certificates on manually-administrated websites to enable HTTPS ([check it here](https://certbot.eff.org/)).

You can follow the specific instructions through [this link](https://certbot.eff.org/lets-encrypt/ubuntufocal-apache) for our case (Ubuntu 20.04 and Apache).

If you follow all the Certbot instructions and get a successful result, then you should be able to access your subdomain/domain using HTTPS. Now that's easy and under your control.

## Step 4: Get Moodle through Git <a name="step-4"></a>
Git is what is called a "version control system". By using git, it will much easier down the road to update the Moodle core application. We will use /opt directory for this installation.
```shell
cd /opt
```
Download the Moodle code and index.
```shell
sudo git clone git://git.moodle.org/moodle.git
```
⚠ **Warning**: Because we are using **e2-micro** (as I was), this command will somehow make our machine disk write and read to be at peak for several minutes (if not hours) and your SSH access might be interrupted because of timeout. Take your time, sip a cup of tea ☕. It will be finished anytime soon.

Then, change directory into the downloaded Moodle folder.
```shell
cd moodle
```

Retrieve a list of each branch available. Press Q to quit seeing.
```shell
sudo git branch -a
```

Corresponding with Moodle convention on branch naming, we will use the latest Moodle version (as the time of writing, it's Moodle 3.11). Tell git which branch to track or use.
```shell
sudo git branch --track MOODLE_311_STABLE origin/MOODLE_311_STABLE
```

Check out the Moodle version specified.
```shell
sudo git checkout MOODLE_311_STABLE
```

## Step 5: Setup Moodle specific configurations <a name="step-5"></a>

### Copy installation to web root
Now we have our Moodle installation, we need to copy that to the webroot (it's usually under `/var/www/html`). Then we need to create `moodledata` directory to store Moodle-generated files. We should change both directories' permissions accordingly.
```shell
sudo cp -R /opt/moodle /var/www/html/
sudo mkdir /var/moodledata
sudo chown -R www-data /var/moodledata
sudo chmod -R 777 /var/moodledata
sudo chmod -R 0755 /var/www/html/moodle
```

### Change web root entrypoint
At this point, we should have PHP 7.4 and MySQL 8 running. And also, our Moodle application is now accessible through `https://(your domain)/moodle`. This is quite annoying. We need to change the web root config so that our user can access through `https://(your domain)`.
```shell
sudo nano /etc/apache2/sites-available/000-default.conf
```

Now change in the line of `DocumentRoot /var/www/html` to `DocumentRoot /var/www/html/moodle`. Save the file by presing Ctrl+X > confirm with Y > Enter to save with default file name (we are using Nano text editor).

Restart the Apache server.
```shell
sudo service apache2 restart
```

### Create new database and user
We need to create a user for our Moodle database. Open MySQL as root, use your MySQL root password from Step 2.
```shell
sudo mysql -u root -p
```

Now we are in MySQL shell.
Create database.
```shell
mysql> CREATE DATABASE moodle DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
```

Create user. Use your username and password of your choice.
```shell
mysql> CREATE USER 'moodledude'@'localhost' IDENTIFIED BY 'passwordformoodledude';
```

Grant some privileges to the user.
```shell
mysql> GRANT SELECT,INSERT,UPDATE,DELETE,CREATE,CREATE TEMPORARY TABLES,DROP,INDEX,ALTER ON moodle.* TO 'moodledude'@'localhost';
```

Quit the shell.
```shell
mysql> quit;
```

### Update opcache and some PHP configuration

Before we continue, we should update some opcache and PHP configuration according to Moodle docs. Please review the required settings for:
- [opcache configuration](https://docs.moodle.org/311/en/OPcache#Configuration)
- [PHP configuration](https://docs.moodle.org/311/en/PHP)

For **opcache config file**, you can open from this location (use `ls` to check opcache.ini name file:
```shell
cd /etc/php/7.4/apache2/conf.d/
ls
sudo nano 10-opcache.ini
```

For **PHP config file**, you can open from this location:
```shell
cd /etc/php/7.4/apache2/
ls
sudo nano php.ini
```

Please update all required settings before we continue. Note that from my case, I don't need to uncomment extension sections in `php.ini`. They are already enabled since component installation at Step 2.

After you finished, restart the Apache server to load the new settings.
```shell
sudo service apache2 restart
```

### Continue installation from web interface

Now we can continue our Moodle installation from its web interface. Before that, change the permissions on `/var/www/html/moodle` directory so the installation can succeed smoothly.
```shell
sudo chmod -R 777 /var/www/html/moodle
```

Open your browser and access `https://(your domain)` and follow the prompts.
- Change path of moodledata to: **/var/moodledata**
- Change database type to: **mysqli**
- Change database settings:
  - Host server: **localhost**
  - Database: **moodle**
  - User: **moodledude** (the user you created when setting up the database)
  - Password: **passwordformoodledude** (the password for the user you created)
  - Tables Prefix: **mdl_**
- Environment checks. At this point every thing should be in green **OK**. If there are any warnings, let me know.
- Next and next and confirm installations.

At this point, installation should finished successfully (just wait). Follow next prompts for creating Admin user and website configuration.

### Change system path
Now Moodle is already up and running. We need to update some little configuration a little more.
Navigate to **Site Administration > Server > System Paths**. Input path settings like the following:
- Path to du: **/usr/bin/du**
- Path to aspell: **/usr/bin/aspell**
- Path to dot: **/usr/bin/dot**

Save your changes.

### Setup cron job for Moodle
As required by Moodle, we need to setup a cron job (as mentioned in [this documentation](https://docs.moodle.org/311/en/Cron)). We can check the PHP CLI configuration using below command.

```shell
php -i | grep php.ini
```
Make sure that the PHP version used by CLI is the same with our initial installation, which is 7.4.

Now we add our cron job by using crontab.
```shell
crontab -u www-data -e
```
At first, you'll be asked which editor to use. Choose nano. Then, add the following at the end of the opened file.
```
* * * * * /usr/bin/php  /var/www/html/moodle/admin/cli/cron.php >/dev/null
```
Save your changes. Now our Moodle has its cron job running every 1 minute.

### Don't forget to revert Moodle directory permissions
Run this command to revert `/var/www/html/moodle` permissions.
```shell
sudo chmod -R 0755 /var/www/html/moodle
```

### Monitor the usage

Don't forget to monitor the instance usage and cost. **Simple but important**.

## Known Issues 🤯

- If you try to access VM directories via Visual Studio Code, you may encounter lag and sometimes timeout. In my case, the disk IOPS and I/O usage is full for several minutes and hours when the issue happens. Do you have any ideas on this?
  **Solution**: We have to scale up our machine type.
- Certbot may fail to give you SSL certificate if you don't properly setup DNS records and open port 443.
- Certbot may fail to renew SSL certificate on first check, even we already have a proper setup. Try executing the certbot command again.

## Next steps ⏩

Below is the list of what you can do further:
- Start installing plugins that suit your needs.
- Scale up your instance by turning it off first then change its machine type and disk size. Restart it whenever you are ready.
- Setup specific Firewall policies for your subnetworks.
- Avoid directory ownership mismanagement. You don't want to accidentally allow unintended access and authorization that can alter your Moodle source.
- Use external database authentication and enrolment plugins to speed up administration routines. I will cover this in the upcoming post.
- If you follow Moodle installation steps from official docs, pay attention on `clamav` component. This antivirus will cause hourly huge spikes in CPU utilization to process virus database. You may want to scale up your instance if you want to make use of it.

## Opinion 💭

- There is "official" Docker image of Moodle, but it's solely used for development purposes, not production.
- You may think of using Kubernetes architecture to get autohealing and autoscaling features, but that may bring a lot of technical difficulties to setup. Always start simple.
- As stated by Moodle docs, Moodle LMS itself can be clustered (or at least scaled out). However, it's not necessary and will require complex architecture work to do so. Instead, Moodle LMS can be scaled up by just increasing memory, CPU, and disk. In this way, of course, Moodle is at higher risk of downtime since our single VM only exists in a single zone in a region.
- Actually, there is a way to separate Moodle source app, moodledata, and the database so it can be scaled out. But that will incur higher cost than we expected. For example, GCP itself allows use of Cloud Storage to be mounted for VMs using FUSE. The idea is to use it as a place for moodledata. But we will be charged with Cloud Storage pricing plus its performance may not significantly sufficient for Moodle to store and process its data (lower performance than SSD and limited concurency capability).

## Conclusion 🏁

That is a hell lot of steps to install Moodle in GCP. But, we already established our very own Moodle installation. **We have full control over resources and that is the main point and a good thing!** We only pay for what we use (or provision to be exact), we can make use of cloud flexibility to scale up or scale down, and we can integrate with other features GCP offers.

## Further reading 📚

Please review these articles to enrich your knowledge:
- [Moodle Documentation](https://docs.moodle.org)
- [Moodle Performance Recommendations as of 3.11](https://docs.moodle.org/311/en/Performance_recommendations)
- [Google Cloud Documentation](https://cloud.google.com/docs)
- [Google Cloud Pricing Calculator](https://cloud.google.com/products/calculator) (yeah, it's not a reading, but it's also important tool to estimate your cloud cost in GCP)

📣 Please leave a comment, suggestions, or any feedback on this matter. If you found this post useful, please share to others and make people happy.
😉 If you need further assistance, I'm happy to help you. Contact me anytime.
😎 If you need a hand for your related projects, I'm also happy to collaborate. Contact me then. Quick!

Thank you for reading this lengthy post. I appreciate that a lot 😊