[Moodle Migration][Phase 7] PHP 8.2 switch, 5.1 upgrade

Goal: move the upgraded 4.2.11 site to PHP 8.2, hop to 5.1+, adopt the new /public webroot, and leave cron/HTTPS solid.

Decisions

  • Runtime PHP: 8.2 (5.1 requires ≄8.2)
  • DB: MariaDB 11.4 LTS (already in place)
  • Web: Nginx (single PATH_INFO PHP block; no duplicates)
  • Webroot after 5.1: /var/www/moodle/public
  • Staging safety: $CFG->noemailever = true stays on

Prereqs (sanity)

# make sure 8.2 has the required setting and extensions
echo 'max_input_vars = 5000' | sudo tee /etc/php/8.2/cli/conf.d/50-moodle.ini >/dev/null
echo 'max_input_vars = 5000' | sudo tee /etc/php/8.2/fpm/conf.d/50-moodle.ini >/dev/null
sudo systemctl restart php8.2-fpm
php8.2 -i | grep -i max_input_vars
php8.2 -m | egrep -i 'mbstring|curl|zip|gd|intl|xml|xmlreader|dom|sodium' || true

Put site into maintenance

sudo -u www-data php8.2 /var/www/moodle/admin/cli/maintenance.php --enable

Code swap to 5.1 (git)

# keep an instant rollback
sudo mv /var/www/moodle /var/www/moodle-40211

# fetch 5.1.x
sudo git clone -b MOODLE_501_STABLE --depth=1 https://github.com/moodle/moodle.git /var/www/moodle

# carry over config, fix ownership
sudo cp /var/www/moodle-40211/config.php /var/www/moodle/config.php
sudo chown -R www-data:www-data /var/www/moodle

Composer vendors (required for git installs)

We ran Composer under PHP 8.2 CLI to avoid distro Composer quirks with PHP 8.0.

cd /var/www/moodle
sudo -u www-data php8.2 /usr/bin/composer install --no-dev --prefer-dist --optimize-autoloader

Switch Nginx to PHP 8.2 & adopt /public webroot

5.1 requires serving from /public. Replace the vhost with a clean config: single PHP block, PATH_INFO-aware, 80→443 redirect, ACME intact.

sudo tee /etc/nginx/sites-available/moodle >/dev/null <<'EOF'
# HTTPS vhost
server {
  listen 443 ssl;
  server_name moodle.newroadgi.com;

  root /var/www/moodle/public;
  index index.php;
  client_max_body_size 128m;

  # TLS (Certbot-managed files)
  ssl_certificate     /etc/letsencrypt/live/moodle.newroadgi.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/moodle.newroadgi.com/privkey.pem;
  include /etc/letsencrypt/options-ssl-nginx.conf;
  ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

  # App routing
  location / {
    try_files $uri $uri/ /index.php?$query_string;
  }

  # Single PHP handler (PATH_INFO aware) on PHP 8.2
  location ~ \.php(/|$) {
    fastcgi_split_path_info ^(.+\.php)(/.*)$;
    set $path_info $fastcgi_path_info;
    try_files $fastcgi_script_name $fastcgi_script_name/ =404;

    fastcgi_pass unix:/run/php/php8.2-fpm.sock;
    include fastcgi_params;

    fastcgi_param PATH_INFO       $path_info;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    fastcgi_param DOCUMENT_ROOT   $document_root;

    fastcgi_read_timeout 300;
  }

  # Basic hardening
  location ~* ^/(?:\.git|\.hg|\.svn|vendor|node_modules)/ { deny all; }
  location ~* /(config\.php|composer\.(json|lock))        { deny all; }
  location ~ /\.                                           { deny all; }
}

# HTTP vhost -> HTTPS + ACME
server {
  listen 80;
  server_name moodle.newroadgi.com;
  root /var/www/moodle/public;

  # ACME http-01
  location ^~ /.well-known/acme-challenge/ {
    default_type "text/plain";
    try_files $uri =404;
  }

  # everything else to HTTPS
  location / { return 301 https://$host$request_uri; }
}
EOF

sudo nginx -t && sudo systemctl reload nginx

Run the 5.1 upgrader (PHP 8.2)

sudo -u www-data php8.2 /var/www/moodle/admin/cli/upgrade.php
# answer Y → success

Post-upgrade tidy

# purge caches
sudo -u www-data php8.2 /var/www/moodle/admin/cli/purge_caches.php

# bring site back
sudo -u www-data php8.2 /var/www/moodle/admin/cli/maintenance.php --disable

Cron on PHP 8.2 (clear “cron not running” / “adhoc queue” warnings)

# flip cron to 8.2 and restart cron daemon
sudo sed -i 's|/usr/bin/php8\.0|/usr/bin/php8\.2|' /etc/cron.d/moodle
sudo systemctl restart cron

# run once now and re-check
sudo -u www-data php8.2 /var/www/moodle/admin/cli/cron.php
sudo -u www-data php8.2 /var/www/moodle/admin/cli/checks.php | tail -n 20

Theme

  • Keep stock (Boost/Boost Union) for now (cleanest).
  • If restoring a custom theme, copy from .../theme/<yourtheme> into /var/www/moodle/theme/<yourtheme>, run upgrade, purge caches, then select it in Site administration → Appearance → Theme selector. Make sure it’s 5.1-compatible.

Rollback (fast)

# if you had to roll back code only
sudo mv /var/www/moodle /var/www/moodle-501
sudo mv /var/www/moodle-40211 /var/www/moodle
sudo nginx -t && sudo systemctl reload nginx

Contraints and fixes

  • Composer blew up under PHP 8.0 with AnsiColorMode parse error → Run Composer using PHP 8.2 CLI: sudo -u www-data php8.2 /usr/bin/composer install ...

  • “Moodle 4.4 or later requires at least PHP 8.1 (currently 8.0.30)” after switching to 8.2 → Certbot’s 443 vhost still referenced php8.0-fpm.sock. Fix: sed -i 's|php8.0-fpm.sock|php8.2-fpm.sock|g' /etc/nginx/sites-available/moodle → reload Nginx.

  • Broken CSS over HTTPS → There were two PHP blocks; the simple location ~ \.php$ { ... } captured requests and broke PATH_INFO. Fix: delete the simple block and keep only the PATH_INFO block (see config above).

  • “The Moodle root directory must not be publicly accessible” after 5.1 → 5.1 expects webroot at /public. Fix: set root /var/www/moodle/public; for both 80 and 443; reload Nginx.

  • Environment CRITICALs under PHP 8.2 → Missing max_input_vars=5000 in 8.2 (we had set it only for 8.0). Fix: add to /etc/php/8.2/{cli,fpm}/conf.d/50-moodle.ini, restart php8.2-fpm, re-run checks.

  • Cron warnings (“not run in N minutes”, “adhoc queue old”) → Cron still used PHP 8.0. Fix: switch /etc/cron.d/moodle to php8.2, restart cron, run once manually.

  • Theme changed after code swaps → Custom theme wasn’t copied (by design for a clean hop) or not 5.1-compatible. Fix: stay on stock, or install a 5.1-ready theme later.