Образование за последние десять лет совершило тихий, но решительный поворот в сторону цифровых форматов. Школы и университеты, корпоративные центры обучения, репетиторские проекты, языковые курсы - все они столкнулись с необходимостью держать материалы, задания, тесты и общение между преподавателями и учащимися в едином месте, доступном из любой точки. Готовых решений на рынке предостаточно, но большинство из них либо стоит как новый автомобиль ежемесячно, либо привязывает к чужой инфраструктуре с непонятной судьбой ваших данных.
Moodle (расшифровывается как Modular Object-Oriented Dynamic Learning Environment) - свободная платформа управления обучением, написанная на PHP. Это не просто площадка для размещения файлов лекций, а полноценная LMS-система с десятками возможностей. Здесь и онлайн-классы с присутствием преподавателя, и создание тестов с автопроверкой, и сдача работ через файловую систему с антиплагиатом, и форумы для обсуждения тем, и вики-разделы для совместной работы над материалами, и журналы оценок с гибкими формулами расчёта итогов. Получился тот самый швейцарский нож для образования, которым успешно пользуются от маленьких частных школ до огромных университетов с десятками тысяч студентов.
Ниже разобран полный путь установки на Rocky Linux 8 - от подготовки брандмауэра до открытия первой страницы платформы в браузере. Параллельно настраиваются PHP 8.0 через Remi-репозиторий, MySQL для хранения данных, Nginx как веб-сервер, Let's Encrypt для шифрования трафика и SELinux для дополнительной защиты.
Подготовительные требования и базовое окружение для развёртывания учебной платформы
Чтобы пройти этот путь без сюрпризов, нужен сервер на Rocky Linux 8 со свежей установкой. На AlmaLinux 8 и CentOS 8 те же команды работают без изменений - все три дистрибутива идейные наследники RHEL и ведут себя практически одинаково.
Понадобится валидное доменное имя, направленное A-записью на IP сервера. В этом руководстве используется условный домен moodle.example.com - его нужно заменить на свой реальный. Без домена не получится выпустить TLS-сертификат через Let's Encrypt, а без сертификата современные браузеры будут громко ругаться на каждом шаге.
Доступ к серверу должен быть из-под обычного пользователя с правами sudo. Постоянная работа из-под root считается дурным тоном с давних времён, и для веб-проектов это особенно критично - любая компрометация PHP-приложения превращается в катастрофу для всей системы.
Обновляем пакеты до последних версий перед началом:
$ sudo dnf update
Ставим базовые утилиты, которые пригодятся в дальнейшем (часть из них может уже быть в системе):
$ sudo dnf install wget curl nano unzip yum-utils -y
Утилита wget понадобится для скачивания файлов, curl - для проверки HTTP-запросов, nano - удобный редактор для конфигов, unzip - для распаковки архивов, yum-utils - для расширенной работы с репозиториями (включая команду dnf module).
Открытие нужных портов в брандмауэре firewalld для веб-трафика
Rocky Linux из коробки использует firewalld - менеджер правил поверх netfilter, более удобный в работе, чем голый iptables. Для веб-приложения нужно разрешить два порта - 80 для обычного HTTP и 443 для HTTPS-трафика после подключения сертификата.
Проверяем состояние брандмауэра:
$ sudo firewall-cmd --state
running
Ответ running означает, что служба активна и обрабатывает правила. Если firewalld выключен (тогда вывод будет not running), его нужно сначала запустить через systemctl start firewalld.
Брандмауэр работает с зонами, и зона public используется по умолчанию для большинства интерфейсов. Смотрим, какие службы и порты в ней уже разрешены:
$ sudo firewall-cmd --permanent --list-services
Стандартный набор обычно выглядит так:
cockpit dhcpv6-client ssh
Cockpit - веб-консоль администрирования, dhcpv6-client - получение адресов по IPv6, ssh - удалённый вход. Отдельно HTTP и HTTPS не открыты, что и нужно поправить.
Открываем порты для веб-трафика:
$ sudo firewall-cmd --add-service=http --permanent
$ sudo firewall-cmd --add-service=https --permanent
Использование --add-service вместо --add-port удобнее, поскольку брандмауэр сам подставит нужные номера портов из своей базы. Флаг --permanent делает правило постоянным - без него правило исчезло бы после перезагрузки firewalld.
Применяем изменения:
$ sudo firewall-cmd --reload
После reload новые правила вступают в силу, и трафик до веб-сервера сможет добираться с любого внешнего адреса.
Установка системы контроля версий Git для последующего клонирования репозитория Moodle
Moodle распространяется через GitHub, и для скачивания исходников проще всего использовать Git. Это удобнее, чем скачивать архив - в любой момент можно подтянуть свежие правки и обновления через git pull, не повторяя всю установку заново.
Ставим Git одной командой:
$ sudo dnf install git
Пакет в репозитории Rocky Linux обычно достаточно свежий для всех нужд - сложные сценарии вроде LFS или подписанных коммитов нам тут не понадобятся. Простой git clone и git checkout - вот и всё, что нужно для работы с Moodle.
Установка PHP 8.0 из репозитория Remi и подключение всех необходимых модулей для работы платформы
Moodle - это PHP-приложение, и без подходящей версии PHP оно не запустится. В стандартном репозитории Rocky Linux 8 живёт PHP 7.2, что для современных версий Moodle уже недостаточно. Поэтому подключаем сторонний репозиторий Remi - один из самых уважаемых источников свежих версий PHP для RHEL-семейства.
Сначала подключаем EPEL - это базовый дополнительный репозиторий, который нужен в качестве зависимости для Remi:
$ sudo dnf install epel-release
Затем сам Remi:
$ sudo dnf install https://rpms.remirepo.net/enterprise/remi-release-8.rpm
Команда напрямую устанавливает RPM-пакет с сайта Remi, который в свою очередь добавляет нужные репозитории в систему.
Смотрим, какие потоки PHP доступны:
$ dnf module list php -y
В системе модулей dnf поток (stream) - это конкретная версия пакета. После добавления Remi список становится длиннее:
Rocky Linux 8 - AppStream
Name
Stream
Profiles
Summary
php
7.2 [d]
common [d], devel, minimal
PHP scripting language
php
7.3
common [d], devel, minimal
PHP scripting language
php
7.4
common [d], devel, minimal
PHP scripting language
php
8.0
common [d], devel, minimal
PHP scripting language
Remi's Modular repository for Enterprise Linux 8 - x86_64
Name
Stream
Profiles
Summary
php
remi-7.2
common [d], devel, minimal
PHP scripting language
php
remi-7.3
common [d], devel, minimal
PHP scripting language
php
remi-7.4
common [d], devel, minimal
PHP scripting language
php
remi-8.0
common [d], devel, minimal
PHP scripting language
php
remi-8.1
common [d], devel, minimal
PHP scripting language
Hint: [d]efault, [e]nabled, [x]disabled, [i]nstalled
Метка [d] рядом с 7.2 означает default - это та версия, которая встанет, если не указать поток явно. Нам нужна 8.0 из Remi, причём именно поток remi-8.0, поскольку он получает обновления от поддерживающего проект мейнтейнера, а не из ванильного потока Rocky Linux.
Сбрасываем текущий выбранный поток и активируем новый:
$ sudo dnf module reset php -y
$ sudo dnf module enable php:remi-8.0
Команда reset снимает текущий dfault (7.2), enable назначает новый поток основным.
Ставим PHP вместе со всем зоопарком модулей, которые нужны Moodle:
$ sudo dnf install graphviz aspell ghostscript clamav php-fpm php-iconv php-curl php-mysqlnd php-cli php-mbstring php-xmlrpc php-soap php-zip php-gd php-xml php-intl php-json php-sodium php-opcache
Список длинный, и каждый компонент тут не для красоты. Graphviz рисует графы и диаграммы, aspell проверяет орфографию, ghostscript обрабатывает PostScript и PDF, clamav сканирует загружаемые файлы на вирусы. Дальше идут расширения PHP - php-fpm для работы под Nginx через FastCGI, php-mysqlnd для подключения к MySQL, php-mbstring для корректной работы с многобайтными кодировками (это критично для русскоязычного контента), php-gd для обработки изображений, php-intl для интернационализации, php-opcache для кеширования байт-кода, и так далее.
Проверяем результат:
$ php --version
Ожидаемый вывод примерно такой:
PHP 8.0.21 (cli) (built: Jul
6 2022 10:13:53) ( NTS gcc x86_64 )
Copyright (c) The PHP Group
Zend Engine v4.0.21, Copyright (c) Zend Technologies
with Zend OPcache v8.0.21, Copyright (c), by Zend Technologies
Конкретный номер сборки будет отличаться - зависит от того, когда обновлялся пакет в Remi.
Открываем главный конфиг PHP:
$ sudo nano /etc/php.ini
Меняем размеры загружаемых файлов до 25 МБ - это нужно для работы с прикреплёнными к заданиям документами и видеоматериалами:
upload_max_filesize = 25M
post_max_size = 25M
Раскомментируем переменную max_input_vars (убирая точку с запятой в начале) и ставим значение 5000:
max_input_vars = 5000
Параметр определяет, сколько переменных может быть в одном POST-запросе. Дефолтное значение 1000 не хватает для крупных форм Moodle, особенно при работе с большими тестами или сложными настройками курсов.
Сохраняем файл через Ctrl + X и подтверждение Y.
Открываем конфиг PHP-FPM:
$ sudo nano /etc/php-fpm.d/www.conf
Находим строки user=apache и group=apache и меняем их на nginx:
...
; Unix user/group of processes
; Note: The user is mandatory. If the group is not set, the default user's group
;
will be used.
; RPM: apache user chosen to provide access to the same directories as httpd
user = nginx
; RPM: Keep a group allowed to write in log dir.
group = nginx
...
По умолчанию пакет PHP-FPM в Remi настроен под Apache, но мы будем использовать Nginx, поэтому права меняем на нужного пользователя.
Раскомментируем настройки для unix-сокета:
; Set permissions for unix socket, if one is used. In Linux, read/write
; permissions must be set in order to allow connections from a web server.
; Default Values: user and group are set as the running user
;
mode is set to 0660
listen.owner = nginx
listen.group = nginx
listen.mode = 0660
Эти параметры разрешают Nginx подключаться к сокету PHP-FPM. Без правильных прав на сокет связка между веб-сервером и PHP-обработчиком сломается на самом старте.
Закомментируем строку с listen.acl_users (она конфликтует с настройками владельца):
;listen.acl_users = apache,nginx
Сохраняем файл.
Передаём владение каталогом сессий PHP пользователю nginx:
$ chown -R nginx:nginx /var/lib/php/session/
Без этого PHP не сможет сохранять данные сессий, и пользователи будут вылетать из Moodle при каждом обновлении страницы.
Включаем автозапуск и стартуем PHP-FPM:
$ sudo systemctl enable php-fpm --now
Флаг --now одновременно запускает службу и прописывает её в автозапуск, экономя одну команду.
Установка MySQL и подготовка базы данных с отдельным пользователем для нужд Moodle
Moodle хранит всю свою информацию в базе данных - курсы, пользователей, оценки, форумы, тесты, файлы метаданных. Без надёжной СУБД платформа просто не функционирует. Ставим MySQL из стандартного репозитория Rocky Linux:
$ sudo dnf install mysql-server
Проверяем версию:
$ mysql --version
mysql
Ver 8.0.26 for Linux on x86_64 (Source distribution)
Восьмая версия MySQL - современный движок с хорошей поддержкой UTF-8, оконных функций, JSON-полей и многого другого, чего так не хватало в шестой и седьмой ветках.
Запускаем службу и включаем автозапуск:
$ sudo systemctl enable mysqld --now
Запускаем интерактивный скрипт настройки безопасности:
$ sudo mysql_secure_installation
Скрипт начнёт задавать вопросы. Первый - подключать ли компонент проверки паролей. Нажимаем Y и выбираем уровень 2 (STRONG), который требует минимум 8 символов с обязательным разнообразием:
VALIDATE PASSWORD COMPONENT can be used to test passwords
and improve security. It checks the strength of password
and allows the users to set only those passwords which are
secure enough. Would you like to setup VALIDATE PASSWORD component?
Press y|Y for Yes, any other key for No: (Press Y)
There are three levels of password validation policy:
LOW
Length >= 8
MEDIUM Length >= 8, numeric, mixed case, and special characters
STRONG Length >= 8, numeric, mixed case, special characters and dictionary
file
Please enter 0 = LOW, 1 = MEDIUM and 2 = STRONG: (Type 2)
Дальше нужно задать пароль root для MySQL:
Please set the password for root here.
New password:
Re-enter new password:
Пароль должен соответствовать выбранному уровню сложности - длинный, со смесью регистров, цифр и спецсимволов.
Дальше идут вопросы про базовую безопасность - на все отвечаем Y:
Do you wish to continue with the password provided?(Press y|Y for Yes, any other key for No) : (Press Y)
Remove anonymous users? (Press y|Y for Yes, any other key for No) : (Press Y)
Disallow root login remotely? (Press y|Y for Yes, any other key for No) : (Press Y)
Remove test database and access to it? (Press y|Y for Yes, any other key for No) : (Press Y)
Reload privilege tables now? (Press y|Y for Yes, any other key for No) : (Press Y)
Success.
All done!
Каждое из этих действий закрывает типичные дыры в безопасности - удаляются анонимные пользователи (которые могли подключаться без пароля), запрещается удалённый вход под root, удаляется тестовая база с публичным доступом.
Подключаемся к консоли MySQL:
$ sudo mysql
Создаём базу данных для Moodle с правильной кодировкой:
mysql > CREATE DATABASE moodledb DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
Кодировка utf8mb4 - это полноценный UTF-8, который поддерживает все символы Юникода, включая эмодзи и редкие иероглифы. Старая кодировка utf8 в MySQL на самом деле трёхбайтная и не охватывает всё пространство символов, так что использовать нужно именно utf8mb4.
Создаём пользователя базы (пароль YourPassword23! заменить на свой):
mysql > create user 'moodleuser'@'localhost' IDENTIFIED BY 'YourPassword23!';
Раздаём ему минимально необходимые права на базу:
mysql > GRANT SELECT,INSERT,UPDATE,DELETE,CREATE,CREATE TEMPORARY TABLES,DROP,INDEX,ALTER ON moodledb.* TO 'moodleuser'@'localhost';
Заметим, что не используется GRANT ALL - выданы только конкретные права, нужные для работы Moodle. Это принцип наименьших привилегий, который снижает потенциальный ущерб при компрометации приложения. Например, у moodleuser нет прав на создание новых пользователей или работу с системными таблицами MySQL.
Применяем изменения и выходим:
mysql > FLUSH PRIVILEGES;
mysql > exit
Установка свежей версии Nginx через официальный репозиторий разработчиков
Стандартный Rocky Linux несёт в репозитории относительно старую версию Nginx. Для современного веб-приложения хочется иметь свежую сборку с актуальной поддержкой HTTP/2 и TLS 1.3. Поэтому подключаем официальный репозиторий nginx.org.
Создаём файл для нового репозитория:
$ sudo nano /etc/yum.repos.d/nginx.repo
Содержимое:
[nginx-stable]
name=nginx stable repo
baseurl=http://nginx.org/packages/centos/$releasever/$basearch/
gpgcheck=1
enabled=1
gpgkey=https://nginx.org/keys/nginx_signing.key
module_hotfixes=true
[nginx-mainline]
name=nginx mainline repo
baseurl=http://nginx.org/packages/mainline/centos/$releasever/$basearch/
gpgcheck=1
enabled=0
gpgkey=https://nginx.org/keys/nginx_signing.key
module_hotfixes=true
В файле объявлены сразу два репозитория - стабильный (включён) и mainline (выключен по умолчанию). Стабильный получает только критические патчи и проверенные исправления, mainline - последние возможности и улучшения, но с большей вероятностью сюрпризов. Для боевого сервера разумнее держать stable, для тестов и экспериментов можно переключиться на mainline через enabled=1.
Параметр module_hotfixes=true разрешает поверх системных модулей подкладывать обновления из этого репозитория - без него dnf отказывался бы устанавливать пакет из-за конфликта с модулем nginx из основного репозитория.
Сохраняем файл и ставим Nginx:
$ sudo dnf install nginx
Проверяем версию:
$ nginx -v
nginx version: nginx/1.22.0
Свежая версия 1.22 поддерживает все современные возможности и безопасные настройки TLS, что и нужно для нашей задачи.
Клонирование репозитория Moodle с GitHub и подготовка структуры каталогов с правильными правами
Готовим каталог для веб-файлов:
$ sudo mkdir /var/www/html/moodle
Передаём владение текущему пользователю - это нужно, чтобы git clone мог писать в каталог:
$ sudo chown -R $USER:$USER /var/www/html/moodle
Переменная $USER подставляется автоматически - текущий залогиненный пользователь.
Переходим в каталог и клонируем репозиторий:
$ cd /var/www/html/moodle
$ git clone https://github.com/moodle/moodle.git .
Точка в конце команды важна - она говорит git клонировать в текущий каталог, а не создавать вложенную папку moodle.
Смотрим список доступных веток:
$ git branch -a
В выводе будут все ветки репозитория - main, MOODLE_400_STABLE, MOODLE_311_STABLE и многие другие. Каждая ветка соответствует определённой стабильной версии. На момент написания оригинала актуальной была MOODLE_400_STABLE - четвёртая ветка стабильного релиза.
Создаём локальную ветку с привязкой к удалённой:
$ git branch --track MOODLE_400_STABLE origin/MOODLE_400_STABLE
Переключаемся на неё:
$ git checkout MOODLE_400_STABLE
После checkout все файлы в каталоге обновятся до состояния выбранной ветки. Если в дальнейшем понадобится перейти на более свежую версию, достаточно повторить эти три команды с другим именем ветки.
Создаём отдельный каталог для пользовательских данных Moodle:
$ sudo mkdir /var/moodledata
Этот каталог Moodle использует для хранения загружаемых пользователями файлов, кеша, сессий и прочих рабочих артефактов. Он намеренно вынесен за пределы веб-каталога - так его содержимое недоступно напрямую через браузер, что защищает от случайной утечки приватных данных.
Раздаём правильные права:
$ sudo chown -R nginx /var/moodledata
$ sudo chmod -R 775 /var/moodledata
$ sudo chmod -R 755 /var/www/html/moodle
Каталог данных принадлежит пользователю nginx (от которого работает PHP-FPM) с правами 775. Сам код Moodle получает права 755 - чтение для всех, запись только владельцу. Это стандартная схема для веб-приложений на Linux.
Создание основного файла конфигурации Moodle с настройками базы данных и адреса сайта
Переходим в каталог Moodle:
$ cd /var/www/html/moodle
Копируем шаблон конфига и открываем для правки:
$ cp config-dist.php config.php
$ nano config.php
Находим блок настроек базы данных и заполняем его:
$CFG->dbtype
= 'mysqli';
// 'pgsql', 'mariadb', 'mysqli', 'auroramysql', 'sqlsrv' or 'oci'
$CFG->dblibrary = 'native';
// 'native' only at the moment
$CFG->dbhost
= 'localhost';
// eg 'localhost' or 'db.isp.com' or IP
$CFG->dbname
= 'moodledb';
// database name, eg moodle
$CFG->dbuser
= 'moodleuser';
// your database username
$CFG->dbpass
= 'YourPassword23!';
// your database password
$CFG->prefix
= 'mdl_';
// prefix to use for all table names
Параметр dbtype со значением mysqli указывает на использование MySQL через современный драйвер mysqli. Префикс mdl_ для таблиц - стандартное соглашение Moodle, оставляем как есть. Если на одном сервере MySQL крутятся несколько разных проектов в одной базе, префикс позволяет разделить их таблицы. Но в нашей схеме под Moodle выделена отдельная база, и префикс служит больше документационной цели.
Дальше прописываем адрес сайта и каталог данных:
$CFG->wwwroot
= 'https://moodle.example.com';
$CFG->dataroot
= '/var/moodledata';
Параметр wwwroot - это полный URL сайта, как его видят пользователи. Указано https - значит мы заранее предполагаем подключение TLS-сертификата на следующем шаге. Если оставить http и потом включить TLS, могут начаться проблемы с миксованным контентом и куками. Сохраняем файл.
Получение TLS-сертификата от Let's Encrypt и настройка автоматического продления
Современный веб без HTTPS - это анахронизм. Браузеры маркируют HTTP-сайты как небезопасные, а данные пользователей при передаче по обычному HTTP можно перехватить любым сниффером в сети. Решение - сертификат от Let's Encrypt, бесплатной службы сертификации.
Ставим Certbot - официальный клиент Let's Encrypt:
$ sudo dnf install certbot
Генерируем сертификат в режиме standalone:
$ sudo certbot certonly --standalone --agree-tos --no-eff-email --staple-ocsp --preferred-challenges http -m Адрес электронной почты защищен от спам-ботов. Для просмотра адреса в браузере должен быть включен Javascript. -d moodle.example.com
Разберём флаги по порядку. Параметр certonly означает только получение сертификата без автоматической настройки веб-сервера. --standalone запускает временный встроенный веб-сервер для прохождения проверки (порт 80 на это время должен быть свободен - Nginx ещё не запущен, так что всё в порядке). --agree-tos автоматически принимает условия использования. --no-eff-email отказывается от подписки на рассылку Electronic Frontier Foundation. --staple-ocsp включает OCSP stapling для ускорения проверки сертификата. --preferred-challenges http выбирает метод проверки через HTTP. Параметр -m задаёт email для уведомлений о проблемах с сертификатом, -d указывает домен.
После выполнения сертификат окажется в /etc/letsencrypt/live/moodle.example.com.
Генерируем параметры Diffie-Hellman:
$ sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 4096
Эта команда создаёт файл с параметрами для дополнительной защиты ключевого обмена. Длина 4096 бит - текущая рекомендация по криптостойкости. Команда работает медленно, иногда несколько минут - не пугайся.
Создаём каталог для проверок при автообновлении:
$ sudo mkdir -p /var/lib/letsencrypt
Создаём cron-задачу для автоматического обновления сертификата:
$ sudo nano /etc/cron.daily/certbot-renew
Содержимое:
#!/bin/sh
certbot renew --cert-name moodle.example.com --webroot -w /var/lib/letsencrypt/ --post-hook "systemctl reload nginx"
Сертификаты Let's Encrypt действуют 90 дней, и обновлять их нужно регулярно. Скрипт раз в день будет проверять, не подходит ли срок к концу, и при необходимости обновлять. Параметр --post-hook перезагружает Nginx после успешного обновления, чтобы новый сертификат вступил в силу без ручного вмешательства.
Делаем скрипт исполняемым:
$ sudo chmod +x /etc/cron.daily/certbot-renew
Файлы в /etc/cron.daily запускаются ежедневно по расписанию системного cron - это стандартный механизм Linux для регулярных задач.
Создание конфигурации виртуального хоста Nginx с поддержкой HTTPS и проксированием PHP
Создаём конфиг для нашего сайта:
$ sudo nano /etc/nginx/conf.d/moodle.conf
Содержимое:
# Redirect all non-encrypted to encrypted
server {
listen 80;
listen [::]:80;
server_name moodle.example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name moodle.example.com;
root
/var/www/html/moodle;
index
index.php;
ssl_certificate
/etc/letsencrypt/live/moodle.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/moodle.example.com/privkey.pem;
ssl_trusted_certificate /etc/letsencrypt/live/moodle.example.com/chain.pem;
ssl_session_timeout 1d;
ssl_session_cache shared:MozSSL:10m;
ssl_session_tickets off;
ssl_stapling on;
ssl_stapling_verify on;
ssl_dhparam /etc/ssl/certs/dhparam.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
access_log /var/log/nginx/moodle.access.log main;
error_log
/var/log/nginx/moodle.error.log;
client_max_body_size 25M;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ ^(.+\.php)(.*)$ {
fastcgi_split_path_info ^(.+\.php)(.*)$;
fastcgi_index index.php;
fastcgi_pass unix:/run/php-fpm/www.sock;
include /etc/nginx/mime.types;
include fastcgi_params;
fastcgi_param
PATH_INFO
$fastcgi_path_info;
fastcgi_param
SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
# Hide all dot files but allow "Well-Known URIs" as per RFC 5785
location ~ /\.(?!well-known).* {
return 404;
}
# This should be after the php fpm rule and very close to the last nginx ruleset.
# Don't allow direct access to various internal files. See MDL-69333
location ~ (/vendor/|/node_modules/|composer\.json|/readme|/README|readme\.txt|/upgrade\.txt|db/install\.xml|/fixtures/|/behat/|phpunit\.xml|\.lock|environment\.xml) {
deny all;
return 404;
}
}
Эта конфигурация заслуживает подробного разбора, потому что она содержит много продуманных настроек безопасности.
Первый блок server слушает 80-й порт и делает безусловный редирект на HTTPS через return 301. Это значит, что любая попытка зайти по обычному HTTP будет автоматически перенаправлена на защищённую версию.
Второй блок server отвечает за основной HTTPS-трафик. Параметр listen 443 ssl http2 включает SSL и HTTP/2 - современный протокол, который заметно быстрее HTTP/1.1 за счёт мультиплексирования запросов в одном соединении.
Блок ssl_* параметров - это набор настроек криптографии. Указаны пути к сертификату и ключу, включён OCSP stapling (ускоряет проверку сертификата на стороне клиента), отключены небезопасные TLS 1.0 и 1.1 - оставлены только современные TLS 1.2 и 1.3. Список ssl_ciphers содержит только сильные шифры с прямой секретностью (Forward Secrecy), что защищает старый трафик даже при компрометации ключа в будущем.
Параметр client_max_body_size 25M согласуется с настройками PHP - размер максимального запроса должен совпадать в обоих местах, иначе крупные загрузки могут отваливаться на полпути.
Блок location / реализует красивые URL Moodle через try_files. Если запрошенный путь не существует как файл или каталог, запрос отправляется в index.php с теми же параметрами.
Блок location с регуляркой для PHP перехватывает все запросы к .php файлам и проксирует их в PHP-FPM через unix-сокет. Это быстрее, чем работа через TCP-порт, и безопаснее (сокет недоступен по сети).
Два последних блока location - это защитные правила. Первый запрещает доступ ко всем файлам, начинающимся с точки (скрытые файлы вроде .git или .htaccess), но делает исключение для каталога .well-known, который нужен для проверок Let's Encrypt. Второй блокирует прямой доступ к внутренним файлам Moodle - composer.json, README, конфигам тестов и тому подобному. Без этого защитного правила злоумышленник мог бы через простой запрос узнать версию Moodle или другую чувствительную информацию.
Сохраняем файл и переходим к правке основного nginx.conf:
$ sudo nano /etc/nginx/nginx.conf
Перед строкой include /etc/nginx/conf.d/*.conf; добавляем:
server_names_hash_bucket_size
64;
Этот параметр увеличивает размер хеш-таблицы для хранения имён виртуальных хостов. Без него Nginx может ругнуться при загрузке конфига с длинным доменным именем. Сохраняем файл.
Проверяем синтаксис:
$ sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
Запускаем Nginx и включаем автозапуск:
$ sudo systemctl enable nginx --now
Если на этом шаге появляется ошибка вида nginx: [emerg] open() "/var/run/nginx.pid" failed (13: Permission denied) - значит сработали ограничения SELinux. Лечится так:
$ sudo ausearch -c 'nginx' --raw | audit2allow -M my-nginx
$ sudo semodule -X 300 -i my-nginx.pp
Первая команда читает журнал аудита SELinux, выбирает оттуда отказы, относящиеся к nginx, и через audit2allow генерирует политику my-nginx, которая эти отказы разрешает. Вторая команда устанавливает сгенерированную политику в систему.
Запускаем Nginx ещё раз:
$ sudo systemctl start nginx
Тонкая настройка SELinux для корректного доступа Moodle к своим каталогам
Rocky Linux наследует от RedHat жёсткую политику SELinux. Это вторая стена безопасности после стандартных Linux-прав, которая может неожиданно блокировать вполне легитимные действия. Без правильных контекстов Moodle не сможет писать файлы в свои каталоги, и платформа просто не запустится.
Прописываем правильные контексты для веб-каталога и каталога данных:
$ sudo semanage fcontext -a -t httpd_sys_rw_content_t '/var/www/html/moodle'
$ sudo semanage fcontext -a -t httpd_sys_rw_content_t '/var/moodledata'
$ sudo restorecon -Rv '/var/www/html/moodle'
$ sudo restorecon -Rv '/var/moodledata'
Контекст httpd_sys_rw_content_t означает, что файлы могут читаться и записываться веб-сервером. Без этого контекста SELinux разрешил бы только чтение, и любые операции записи (загрузка файлов, кеши, сессии) ломались бы.
Команды semanage добавляют правила в постоянную базу контекстов. Команды restorecon применяют новые правила к уже существующим файлам. Без второго шага текущее содержимое каталогов осталось бы со старыми контекстами, и правила работали бы только для новых файлов.
Включаем булевую опцию для сетевых подключений:
$ sudo setsebool -P httpd_can_network_connect on
Этот флаг разрешает веб-серверу делать исходящие сетевые соединения. Moodle использует это для подключения к базе данных, проверки обновлений, отправки писем через SMTP и многих других внешних взаимодействий.
Завершение установки через веб-интерфейс с настройкой администратора и главной страницы
Открываем браузер и переходим по адресу https://moodle.example.com. Если всё настроено правильно, появится приветственная страница установщика Moodle.
Нажимаем кнопку Continue. Установщик проверит системные требования - наличие нужных PHP-модулей, доступность базы данных, права на каталоги, корректность параметров php.ini. Если на каком-то пункте появится красная плашка, придётся вернуться назад и доустановить недостающее или поправить настройки.
После прохождения проверки нажимаем Continue ещё раз - начнётся длительный процесс создания таблиц в базе данных, заполнения начальных данных, установки модулей. На быстром сервере это занимает 2-5 минут, на медленном может растянуться до 15-20.
Когда установка завершится, появится форма создания учётной записи администратора. Здесь нужно ввести имя пользователя, пароль (с соблюдением требований сложности), email, имя и фамилию. Этот аккаунт получит полный контроль над платформой - все курсы, пользователи, настройки доступны через него.
После сохранения профиля откроется страница настроек главной страницы сайта - название проекта, краткое описание, основной язык, часовой пояс. Заполняем по своим нуждам и сохраняем.
В конце мастер предложит зарегистрировать сайт в реестре Moodle. Этот шаг можно пропустить через ссылку Skip - функциональность платформы от регистрации никак не зависит, регистрация нужна разве что для попадания в публичный каталог Moodle-сайтов.
После всех шагов открывается главный дашборд платформы. Отсюда можно создавать первые курсы, добавлять преподавателей и учащихся, настраивать категории, форматировать главную страницу под нужды конкретной организации.
Распространённые подводные камни и практические рекомендации для эксплуатации Moodle в боевых условиях
В копилку наблюдений из практики стоит добавить несколько моментов, на которых обжигаются почти все, кто впервые поднимает Moodle.
Первая частая проблема - производительность под нагрузкой. Moodle - тяжёлая платформа, и при большом числе одновременных пользователей она быстро упирается в ресурсы. Базовые настройки PHP-FPM рассчитаны на скромные нагрузки. Для серьёзной эксплуатации нужно подкручивать параметры pm.max_children, pm.start_servers, pm.min_spare_servers и pm.max_spare_servers в /etc/php-fpm.d/www.conf. Разумная отправная точка - 50 процессов на каждый гигабайт свободной памяти, дальше регулировать по реальной нагрузке.
Вторая популярная грабля - резервное копирование. Многие думают, что достаточно бэкапить базу данных через mysqldump. Это правда лишь наполовину - в /var/moodledata лежат все загруженные пользователями файлы, и без них восстановление превратится в катастрофу. Нужны два параллельных бэкапа - дамп MySQL и снимок каталога данных, причём желательно одновременные, чтобы избежать рассинхронизации.
Третий момент касается обновлений. Moodle получает новые версии регулярно - минорные раз в пару месяцев, мажорные раз в полгода. Использование git для установки превращает обновление в простую процедуру через git fetch и git checkout на новую ветку. Но перед каждым обновлением обязательно делать полный бэкап и тестировать на копии - иногда мажорные релизы ломают настройки или несовместимы со старыми плагинами.
Четвёртая тонкость - кеширование. Moodle поддерживает разные движки кеша - от файлового по умолчанию до Redis и Memcached. На нагруженных проектах файловый кеш быстро становится узким местом, и переход на Redis даёт прирост производительности в разы. Настраивается через раздел Site administration > Plugins > Caching > Configuration.
Пятый момент касается рассылки писем. По умолчанию Moodle пытается отправлять письма через локальный sendmail, которого в системе обычно нет. Гораздо надёжнее настроить SMTP-сервер через раздел Site administration > Server > Email > Outgoing mail configuration, указав параметры внешнего почтового сервиса. Без рабочей почты пользователи не смогут восстанавливать пароли и получать уведомления о новых сообщениях.
Где такая инсталляция пригодится в реальной жизни? Сценариев масса. Корпоративные центры обучения для онбординга новых сотрудников и повышения квалификации существующих. Школы, желающие держать материалы и задания в едином пространстве вне зависимости от удалённого формата. Языковые школы с большим числом учащихся и сложным графиком занятий. Университеты для дистанционных программ и заочного обучения. Профессиональные курсы и сертификационные центры. Музыкальные школы и творческие кружки, которые перешли в гибридный формат после эпохи массового перехода на онлайн.
Освоение Moodle даёт инженеру не просто навык установки одной платформы, а целый пласт практик веб-администрирования. Связка LEMP (Linux, Nginx, MySQL, PHP) с настройкой TLS, SELinux, автоматическим обновлением сертификатов и грамотным разделением прав - это базовая школа любого DevOps-специалиста. Те же концепции встречаются при развёртывании WordPress, Nextcloud, GitLab, Mediawiki и десятков других PHP-приложений. Тот, кто разобрался с установкой Moodle, без труда осваивает любую другую платформу со схожей архитектурой. И именно поэтому подобные проекты стоит делать своими руками хотя бы один раз - после этого облачные SaaS-решения перестают казаться магией и превращаются в понятные инженерные конструкции, которые можно собрать самостоятельно и оставить под полным контролем своей организации.