[Moodle Migration][Phase 5] Staging

Goal: land the restored 4.0.9 on a clean Ubuntu 24.04 VPS, lock it down, serve it at a staging hostname with HTTPS, and make it safe for client testing.

Decisions

  • Web: Nginx
  • DB: MariaDB 11.4 LTS (local only)
  • PHP: dual β€” 8.0 (runtime for 4.0.x / 4.2), 8.2 (ready for 5.1)
  • Hostname: moodle.newroadgi.com (A β†’ VPS IP)
  • Staging safety: disable mail, block bots, cron via cron.d

System firewall

sudo ufw allow OpenSSH
sudo ufw --force enable
sudo ufw allow 'Nginx Full'
sudo ufw status

MariaDB 11.4 (local-only) + safe defaults

curl -LsS https://r.mariadb.com/downloads/mariadb_repo_setup | \
  sudo bash -s -- --mariadb-server-version=11.4
sudo apt update && sudo apt -y install mariadb-server
sudo systemctl enable --now mariadb

# local-only + utf8mb4
sudo tee /etc/mysql/mariadb.conf.d/60-moodle.cnf >/dev/null <<'EOF'
[mysqld]
bind-address = 127.0.0.1
character-set-server = utf8mb4
collation-server      = utf8mb4_unicode_ci
innodb_file_per_table = 1
EOF
sudo systemctl restart mariadb

# harden & create db/user
sudo mariadb-secure-installation
sudo mariadb <<'SQL'
CREATE DATABASE moodle DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'moodleuser'@'localhost' IDENTIFIED BY 'REDACTED_STRONG_PW';
GRANT ALL PRIVILEGES ON moodle.* TO 'moodleuser'@'localhost';
FLUSH PRIVILEGES;
SQL

Quick checks

mariadb --version
ss -ltnp | grep 3306              # 127.0.0.1:3306 only
sudo mariadb -e "SHOW VARIABLES LIKE 'character_set_server';"
sudo mariadb -e "SHOW VARIABLES LIKE 'collation_server';"

Web roots & ownership

sudo mkdir -p /var/www/moodle /var/www/moodledata
# unpack your backups into those folders (flatten if nested)
# then:
sudo chown -R www-data:www-data /var/www/moodle /var/www/moodledata
sudo chmod 750 /var/www/moodledata

Nginx server block (PATH_INFO aware)

sudo tee /etc/nginx/sites-available/moodle >/dev/null <<'EOF'
server {
  listen 80;
  server_name moodle.newroadgi.com;
  root /var/www/moodle;           # (later moved to /var/www/moodle/public when we reached 5.1)
  index index.php;
  client_max_body_size 128m;

  # ACME challenge (Let’s Encrypt)
  location ^~ /.well-known/acme-challenge/ {
    default_type "text/plain";
    try_files $uri =404;
  }

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

  # Single PHP handler (PATH_INFO aware)
  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.0-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; }
}
EOF

sudo ln -sf /etc/nginx/sites-available/moodle /etc/nginx/sites-enabled/moodle
sudo rm -f /etc/nginx/sites-enabled/default
sudo nginx -t && sudo systemctl reload nginx

Moodle config (restore)

Edit /var/www/moodle/config.php (from your backup) and confirm:

$CFG->dbtype    = 'mariadb';
$CFG->dbname    = 'moodle';
$CFG->dbuser    = 'moodleuser';
$CFG->dbpass    = 'REDACTED_STRONG_PW';
$CFG->dbhost    = 'localhost';
$CFG->prefix    = 'mdlf2_';   // your actual prefix
$CFG->wwwroot   = 'http://moodle.newroadgi.com';  // switched to https after TLS
$CFG->dataroot  = '/var/www/moodledata';
$CFG->noemailever = true;     // staging: suppress all email

TLS (Let’s Encrypt) & switch to HTTPS

sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/bin/certbot
sudo certbot --nginx -d moodle.newroadgi.com --redirect

# then update config.php
$CFG->wwwroot = 'https://moodle.newroadgi.com';

Cron (per-minute, staging)

echo '* * * * * www-data /usr/bin/php8.0 /var/www/moodle/admin/cli/cron.php >/dev/null 2>&1' | \
  sudo tee /etc/cron.d/moodle
sudo chmod 644 /etc/cron.d/moodle
sudo systemctl restart cron

(Optional) quick ops bundle

sudo mkdir -p /root/ops-backup
sudo cp /etc/mysql/mariadb.conf.d/60-moodle.cnf /root/ops-backup/
sudo bash -c 'dpkg -l > /root/ops-backup/packages.txt'
DATE=$(date +%F)
sudo tar -czf /root/ops-backup-$DATE.tgz -C /root ops-backup
sudo cp /root/ops-backup-$DATE.tgz ~/
sudo chown "$USER:$USER" ~/ops-backup-$DATE.tgz