Step by Step Installation Guide for Red Hat Compatible Servers
Development Database Installation
This guide has been tested on Red Hat compatible servers Alma 9 and Rocky Linux 9 server with sudo or root access. All commands on this page are text based so there is no need for a desktop with a graphical interface. Each step will tell you what you need to do and then give the commands that will accomplish that step. You can copy the entire coloured step or a line at a time but be careful with the word wrap, some commands will extend over multiple lines.
Apache or Nginx are given as options for a webserver. Install one or the other, not both. Both are good options, with Apache having good community support while Nginx may be better suited to busier sites.
Update and install base packages
Moodle requires a web server, database and the php scripting language as described in https://moodledev.io/general/releases/5.1#server-requirements.
# Set your domain name or IP address as a temporary variable as it will be needed several times in the installation
PROTOCOL="http://"
read -p "Enter the web address (without the http:// prefix, eg domain name mymoodle123.com or IP address 192.168.1.1.): " WEBSITE_ADDRESS
# Update all packages
sudo dnf -y update
# Some clones have php 8.3 ready to install, others may require additional repositories. This block checks existing repositories for php version.
dnf list php-8* 2>/dev/null | grep -q 8.3 || {
echo "PHP 8.3 not found, installing Remi repo and setting up PHP 8.3";
sudo dnf install -y https://rpms.remirepo.net/enterprise/remi-release-$(rpm -E %{rhel}).rpm;
sudo dnf module reset php -y;
sudo dnf module enable php:remi-8.3 -y;
}
sudo dnf -y install php php-fpm php-cli php-curl php-zip php-gd php-xml php-intl php-mbstring php-xmlrpc php-soap php-bcmath php-exif php-ldap php-mysqlnd
# Some clones have MariaDB 10.11 ready to install, others may require additional repositories bedore installing this version.
dnf list mariadb* 2>/dev/null | grep -q 10.11 || {
echo "MariaDB 10.11 not found, setting up MariaDB repo and installing MariaDB 10.11";
curl -LsS https://downloads.mariadb.com/MariaDB/mariadb_repo_setup | sudo bash -s -- --mariadb-server-version="mariadb-10.11";
}
sudo dnf install -y mariadb-server mariadb-backup
# Enable MariaDB to start at boot
sudo systemctl enable mariadb
# Start MariaDB now
sudo systemctl start mariadb
sudo dnf -y install unzip nano graphviz git clamav ghostscript rsync
Obtain Moodle code using git
git is a version control system that is the easiest way to download the latest Moodle code and unpack it into a directory. It also makes keeping up to date with patches much easier.
# Clone to the Moodle code folder
sudo git clone -b v5.1.0 https://github.com/moodle/moodle.git /var/www/html/moodle
#sudo git clone -b v5.1.0 https://github.com/moodle/moodle.git /var/www/html/moodle
sudo chown -R apache:apache /var/www/html/moodle
Install a Webserver
Apache or Nginx are given as options for a webserver. Choose Option 1 or Option 2, not both. Both are good choices, with Apache having good community support while Nginx may be better suited to busier sites.
Option 1: Install Apache as your Webserver
# Install Apache HTTP server
sudo dnf -y install httpd
# Enable Apache to start at boot
sudo systemctl enable httpd
# Start Apache now
sudo systemctl start httpd
# Update PHP.ini settings for Apache. This could also be done with a text editor.
PHP_INI_APACHE="/etc/php.ini"
# Set max_input_vars to 5000
sudo sed -i 's/^\s*;*\s*max_input_vars\s*=.*/max_input_vars = 5000/' $PHP_INI_APACHE
# Set post_max_size to 256M
sudo sed -i 's/^\s*post_max_size\s*=.*/post_max_size = 256M/' $PHP_INI_APACHE
# Set upload_max_filesize to 256M
sudo sed -i 's/^\s*upload_max_filesize\s*=.*/upload_max_filesize = 256M/' $PHP_INI_APACHE
# Create Moodle Apache virtual host file
# Contains fallback with r.php required by the router.
sudo tee /etc/httpd/conf.d/moodle.conf > /dev/null <<EOF
<VirtualHost *:80>
ServerName $WEBSITE_ADDRESS
ServerAlias www.$WEBSITE_ADDRESS
DocumentRoot /var/www/html/moodle/public
<Directory "/var/www/html/moodle/public">
Options FollowSymLinks
AllowOverride None
Require all granted
DirectoryIndex index.php index.html
# Enable fallback routing for URLs not matching files/directories
FallbackResource /r.php
</Directory>
# PHP-FPM 8.3 FastCGI handler via Unix socket
<FilesMatch "\.php$">
SetHandler "proxy:unix:/run/php-fpm/www.sock|fcgi://localhost/"
</FilesMatch>
# Log files
ErrorLog /var/log/httpd/error.log
CustomLog /var/log/httpd/access.log combined
</VirtualHost>
EOF
sudo systemctl reload httpd
sudo systemctl restart php-fpm
Option 2: Install Nginx as your Webserver
Do not install Nginx if you have already installed Apache!
# Install Apache httpd and PHP 8.3 FPM from Remi repository
sudo dnf -y install httpd php83-php-fpm
# Enable Apache modules
# RHEL Apache does not use a2enmod, load required modules by default or via config if needed (proxy_fcgi generally available on RHEL)
# Create Moodle Apache site config for PHP-FPM and fallback routing
sudo tee /etc/httpd/conf.d/moodle.conf > /dev/null <<EOF
<VirtualHost *:80>
ServerName $WEBSITE_ADDRESS
ServerAlias www.$WEBSITE_ADDRESS
DocumentRoot /var/www/html/moodle/public
<Directory /var/www/html/moodle>
Options FollowSymLinks
AllowOverride None
Require all granted
DirectoryIndex index.php index.html
# Enable fallback routing for URLs not matching files/directories
FallbackResource /r.php
</Directory>
<FilesMatch \\.php$>
SetHandler "proxy:unix:/run/php-fpm/www.sock|fcgi://localhost/"
</FilesMatch>
ErrorLog /var/log/httpd/moodle-error.log
CustomLog /var/log/httpd/moodle-access.log combined
</VirtualHost>
EOF
# Enable and start Apache httpd
sudo systemctl enable --now httpd
# Enable and start PHP-FPM 8.3 (from Remi, service name usually php83-php-fpm)
sudo systemctl enable --now php83-php-fpm
# Adjust PHP settings in both FPM and CLI php.ini
php_ini_fpm="/etc/opt/remi/php83/php-fpm.d/www.conf"
php_ini_fpm_main="/etc/opt/remi/php83/php.ini"
php_ini_cli="/etc/opt/remi/php83/php.ini"
# Update php.ini for PHP-FPM (main one)
sudo sed -i 's/^\s*;\?\s*max_input_vars\s*=.*/max_input_vars = 5000/' "$php_ini_fpm_main"
sudo sed -i 's/^\s*post_max_size\s*=.*/post_max_size = 256M/' "$php_ini_fpm_main"
sudo sed -i 's/^\s*upload_max_filesize\s*=.*/upload_max_filesize = 256M/' "$php_ini_fpm_main"
# Update php.ini for CLI
sudo sed -i 's/^\s*max_input_vars\s*=.*/max_input_vars = 5000/' "$php_ini_cli"
sudo sed -i 's/^\s*post_max_size\s*=.*/post_max_size = 256M/' "$php_ini_cli"
sudo sed -i 's/^\s*upload_max_filesize\s*=.*/upload_max_filesize = 256M/' "$php_ini_cli"
# Reload PHP-FPM to apply changes
sudo systemctl reload php83-php-fpm
# Reload Apache to apply site config
sudo systemctl reload httpd
Obtain and set up composer
cd /var/www/html/moodle
CACHE_DIR="/var/www/.cache/composer"
sudo mkdir -p "$CACHE_DIR"
sudo chown -R apache:apache "$CACHE_DIR"
sudo chmod -R 750 "$CACHE_DIR"
sudo php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
sudo php composer-setup.php
sudo mv composer.phar /usr/local/bin/composer
sudo chmod +x /usr/local/bin/composer
sudo -u apache COMPOSER_CACHE_DIR="$CACHE_DIR" /usr/local/bin/composer install --no-dev --classmap-authoritative
#sudo -u apache COMPOSER_CACHE_DIR="$CACHE_DIR" composer install --no-dev --classmap-authoritative
sudo chown -R apache:apache vendor
sudo chmod -R 755 /var/www/html/moodle
Specific Moodle requirements
Moodle needs to store files in a specific directory accessible only by the web server. It also needs to change some php configuration settings and set up a cron task. This could be done using a text editor but echo and sed, a text replacement tool, simplifies the process.
# Create the moodledata directory outside your web server's document root
sudo mkdir -p /var/www/moodledata
# Set the webserver as the owner and group recursively ( for both the files and contents)
sudo chown -R apache:apache /var/www/moodledata
# Set the moodledata directory permissions so only the web server can read, write, and access them.
sudo find /var/www/moodledata -type d -exec chmod 700 {} \;
# Set the moodledata file permissions so only the web server can read and write them.
sudo find /var/www/moodledata -type f -exec chmod 600 {} \;
# Call the cron.php in the moodle admin directory to run every minute.
echo "* * * * * /usr/bin/php /var/www/html/moodle/admin/cli/cron.php >/dev/null" | sudo crontab -u apache -
# Check cron is running
# sudo grep CRON /var/log/cron | grep apache
Create Database and User
Moodle needs a database and a user with full privileges
# Create a random password for the user who will access the moodle database
MYSQL_MOODLEUSER_PASSWORD=$(openssl rand -base64 6)
# Create a new database named moodle with the utf8mb4 character set and utf8mb4_unicode_ci collation.
sudo mysql -e "CREATE DATABASE moodle DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
# Create a new MySQL user moodleuser with a password
sudo mysql -e "CREATE USER 'moodleuser'@'localhost' IDENTIFIED BY '$MYSQL_MOODLEUSER_PASSWORD';"
# Grant privileges on the moodle database to the moodleuser to allow localhost access.
sudo mysql -e "GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, CREATE TEMPORARY TABLES, DROP, INDEX, ALTER ON moodle.* TO 'moodleuser'@'localhost';"
# Reloads privilege tables
sudo mysql -e "FLUSH PRIVILEGES;"
Firewall
# Add HTTP service permanently to the public zone
sudo firewall-cmd --zone=public --add-service=http --permanent
sudo setsebool -P httpd_can_network_connect 1
# Reload firewall to apply the configuration
sudo firewall-cmd --reload
sudo semanage fcontext -a -t httpd_sys_rw_content_t '/var/www/moodledata(/.*)?'
sudo restorecon -Rv /var/www/moodledata
sudo restorecon -Rv /var/www/html/moodle
Configure Moodle from the command line
The last step can be done using the browser but we have all the information needed to complete the installation
# Set a random 6 character psasword
MOODLE_ADMIN_PASSWORD=$(openssl rand -base64 6)
# Allow the install procedure to write a file to the Moodle directory
sudo chmod -R 0777 /var/www/html/moodle
# Use the Moodle install procedure to create the config.php configuration file and prepare the database for the new Moodle site
sudo -u apache /usr/bin/php /var/www/html/moodle/admin/cli/install.php --non-interactive --lang=en --wwwroot="$PROTOCOL$WEBSITE_ADDRESS" --dataroot=/var/www/moodledata --dbtype=mariadb --dbhost=localhost --dbname=moodle --dbuser=moodleuser --dbpass="$MYSQL_MOODLEUSER_PASSWORD" --fullname="Generic Moodle" --shortname="GM" --adminuser=admin --summary="" --adminpass="$MOODLE_ADMIN_PASSWORD" --adminemail=please@changeme.com --agree-license
echo "Moodle installation completed successfully. You can now log on to your new Moodle at $PROTOCOL$WEBSITE_ADDRESS as admin with $MOODLE_ADMIN_PASSWORD and complete your site registration"
echo "Remember to change the admin email, name and shortname using the browser in your new Moodle"
# Restrict permissions to the Moodle directory and files
sudo find /var/www/html/moodle -type d -exec chmod 755 {} \;
sudo find /var/www/html/moodle -type f -exec chmod 644 {} \;
Prepare Development Database for Production
A production database contains student submissions. Extra steps are needed to ensure student data is secure from unauthorized access and safeguard against data loss.
Check Permissions
Permissions are used by Linux to control read, write and execute for directories and folders
# Reset the permissions on /var/www/html/moodle directories to read, write and execute for the webserver, read and execute for group and others
sudo find /var/www/html/moodle -type d -exec chmod 755 {} \;
# Reset the permissions on /var/www/html/moodle files to read, write for the webserver, read only for group and other
sudo find /var/www/html/moodle -type f -exec chmod 644 {} \
Set a root password on the database
# Run the mariadb-secure-installation script to strengthen security by setting a root password, removing anonymous users, disabling remote root login, deleting the test database, and reloading privileges.
# Don't forget to write down the root database in a safe place.
sudo mariadb-secure-installation
Configure and enable a firewall
# Enable and start the firewalld service immediately, so the firewall is active now and on system boot
sudo systemctl enable --now firewalld
# Get the default firewall zone and store it in the variable ZONE
ZONE=$(sudo firewall-cmd --get-default-zone)
# Permanently allow SSH service in the default zone (opens TCP port 22)
sudo firewall-cmd --zone=$ZONE --add-service=ssh --permanent
# Permanently allow HTTP service in the default zone (opens TCP port 80)
sudo firewall-cmd --zone=$ZONE --add-service=http --permanent
# Permanently allow HTTPS service in the default zone (opens TCP port 443)
sudo firewall-cmd --zone=$ZONE --add-service=https --permanent
# Reload firewalld to apply all the permanent changes immediately
sudo firewall-cmd --reload
Convert http to the encrypted https protocol
NOTE: A domain name is required to get https in this step. Obtain one if you haven't already done so.
Using HTTPS over HTTP on a Moodle site provides the critical security benefits of encrypted data transmission and compliance with privacy regulations and some features in Moodle may not function properly or may be blocked if the site is using a HTTP connection.
Apache version
# Install EPEL repository which contains Certbot packages (if not installed)
sudo dnf install epel-release -y
# Install Certbot and the Apache plugin on RHEL
sudo dnf install certbot python3-certbot-apache -y
# Run Certbot with the Apache plugin to obtain and install SSL certificate
sudo certbot --apache
# Check the Apache configuration is valid
sudo apachectl configtest
# Reload Apache to apply any configuration changes made by Certbot
sudo systemctl reload httpd
Nginx version
# Update package metadata
sudo dnf update -y
# Install EPEL repository if not already installed (provides Certbot packages)
sudo dnf install epel-release -y
# Install Certbot and the Nginx plugin
sudo dnf install certbot python3-certbot-nginx -y
# Run Certbot with the Nginx plugin and follow prompts to obtain SSL certs and configure Nginx
sudo certbot --nginx
# Test the new Nginx configuration for syntax errors
sudo nginx -t
# Reload Nginx to apply configuration changes without interrupting connections
sudo systemctl reload nginx
You must update references after changing your protocol.
#Replace oldsitehost ( ie http://yourdomain.com) with newsitehost ( ie https://yourdomain.com)
cd /var/www/html/moodle
sudo php admin/tool/replace/cli/replace.php --search=//oldsitehost --replace=//newsitehost --shorten --non-interactive
SSH keys
An SSH key consists of a public and private key pair used to securely authenticate using the Secure Shell (SSH) protocol without needing a password. The key is much longer and more complex than typical passwords, making them extremely resistant to brute-force attacks and guessing. Only the public key is copied (id_rsa.pub); never share your private key. For Windows Users:
- Open Settings > Apps & Features > Optional Features. Look for "OpenSSH Client" in the list
- If it's not installed, click "Add a feature," find "OpenSSH Client," and install it.
Linux Users
- Open a terminal on your home machine (not your server)
# Generate the SSH key pair by running the key generation (without sudo so the keys are stored in ~/.ssh/)
ssh-keygen
# Press Enter to accept the default key save location (usually C:\Users\your_username\.ssh\id_rsa for Windows users, ~/.ssh\id_rsa for Linux)
# When prompted, optionally enter a secure passphrase for extra protection or press Enter to leave it empty.
# Copy your public key to the remote server's ~/.ssh/authorized_keys file, permissions will be sett automatically
ssh-copy-id user@server_address
# Try to log in, It should not ask you for a password.
Remove SSH root access
For security, you should always log in as a user with elevated privileges (a sudo user). You should never login as root on a production server and you should remove root SSH access from your server.
# If you don't already have a sudo user, as root, create a new user 'user1' with home directory and prompt for password
# Enter and confirm a password for user1. User details are optional
adduser user1
# Add 'user1' to the sudo group for administrative privileges
usermod -aG sudo user1
Check you can ssh into your server as your new sudo user! Leave another terminal open until confirmed.
# Backup sshd_config before modifying
sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak
# Open the ssh config file
sudo nano /etc/ssh/sshd_config
# Change the line containing PermitRootLogin to PermitRootLogin no
# Restart SSH service to apply changes
sudo systemctl restart ssh
Keep RHEL compatible distributions up to date
On RHEL based systems, automatic upgrades are managed using the dnf-automatic package.
sudo dnf install dnf-automatic
#Edit the /etc/dnf/automatic.conf as root to configure.
sudo nano /etc/dnf/automatic.conf
Set:
- upgrade_type = security
- emit_via = motd
- email_to = root
- apply_updates = yes
# Enable and start the systemd timer to check for updates automatically
# Default is daily
sudo systemctl enable --now dnf-automatic.timer
Logged in /var/log/dnf.rpm.log
SQL Backups
Daily automated date marked sql backups should be set up on a production Moodle. Dumps can be large and need to be deleted periodically.
# Create MySQL backup user and grant privileges to lock tables (better security than using root or moodleuser roles)
BACKUP_USER_PASSWORD=$(openssl rand -base64 6)
mysql <<EOF
CREATE USER 'backupuser'@'localhost' IDENTIFIED BY '${BACKUP_USER_PASSWORD}';
GRANT LOCK TABLES, SELECT ON moodle.* TO 'backupuser'@'localhost';
FLUSH PRIVILEGES;
EOF
# Create a configuration file for root with the backup user credentials (to avoid the password being visible in the SQL backup call)
cat > /root/.my.cnf <<EOF
[client]
user=backupuser
password=${BACKUP_USER_PASSWORD}
EOF
# Set the configuration file permissions to read and write for root only
chmod 600 /root/.my.cnf
# Setup backup directory with read, write and execute permissions for root only
mkdir -p /var/backups/moodle && chmod 700 /var/backups/moodle && chown root:root /var/backups/moodle
# Set up two daily cron jobs, one to dump the database and the other to remove dumps more than a set number of days old (hardcoded to 7)
(crontab -l 2>/dev/null; echo "0 2 * * * mysqldump --defaults-file=/root/.my.cnf moodle > /var/backups/moodle/moodle_backup_\$(date +\%F).sql") | crontab -
(crontab -l 2>/dev/null; echo "0 3 * * * find /var/backups/moodle -name \"moodle_backup_*.sql\" -type f -mtime +7 -delete") | crontab -