news 2026/11/4 5:06:54

Moodle生产环境部署:Nginx反代、HTTPS与排错调优实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Moodle生产环境部署:Nginx反代、HTTPS与排错调优实战

第一次把 Moodle 从本地虚拟机搬到公网,绝大多数人都会经历同一个心理落差:安装向导一路点完,页面正常跳出来了,觉得大功告成,结果换个设备、换个网络打开就白屏,或者卡在登录页反复跳转。问题不在 Moodle 本身,而在于“本地跑通”和“生产环境可对外访问”之间隔着好几层东西——运行时参数、数据目录权限、反向代理的协议识别、证书链、端口收敛,每一项单独看都不复杂,串起来就会互相打架。

Moodle 生产环境部署这件事,本质上是把一个 PHP 单体应用完整地交付到公网:让它能扛住并发、能安全地暴露 443、能在后台持续跑定时任务、能在出问题的时候快速回滚。这篇文章我按自己实际走过一遍的顺序来写,从机器规格怎么定,到 Nginx 配置里哪几行是命门,再到上线后最常撞上的五个报错怎么定位。适合已经会用 Linux 命令、但没独立交付过完整 Web 应用的朋友,也适合已经在运维岗位、需要接手一套现成 Moodle 站点的人对照排查。

1. 一套 Moodle 生产环境部署到底包含多少工作项

1.1 安装向导跑通只完成了不到一半

很多人对“部署”的理解停在浏览器里那个绿色对勾上。但生产环境的标准不一样,它要满足四个额外条件,我习惯把它们叫做四道坎。

第一道坎是运行时匹配。Moodle 对 PHP 版本和扩展有硬性要求,缺一个intl或者sodium,安装向导会直接中断,而且报错信息往往只告诉你“某扩展缺失”,不会告诉你为什么需要它。intl管的是多语言和本地化格式,成绩单、日期、货币都依赖它;sodium管的是密码学相关的加密操作,登录票据和部分令牌会用到。这两个扩展在很多精简版系统镜像里默认是没装的。

第二道坎是目录与权限。Moodle 的代码目录和数据目录必须分开,数据目录还不能放在 Web 根目录下面。这不是洁癖,是硬性安全要求——moodledata里存着所有用户上传的作业、课件、头像、缓存,如果它能被 URL 直接访问,等于把所有学员资料挂在公网上任人下载。

第三道坎是站点地址一致性。Moodle 在数据库里把wwwroot写死了,一旦你安装时用的是http://IP,后面换成域名和 HTTPS,站点会疯狂重定向,因为它认为当前访问的地址不是“自己”。这个坑后面第 4 章会详细拆。

第四道坎是证书与端口。公网访问必须走 HTTPS,而 HTTPS 需要域名、需要证书、需要 Nginx 正确传递协议头给 PHP。这三者任何一环断了,表现都是登录页反复跳或者样式全丢。

1.2 机器规格与磁盘规划的实际数字

规格这块我不太喜欢给“标准答案”,因为使用强度差异太大。但可以给一个按活跃用户量估算的参考区间,这个区间是基于“普通课程站点、以文字和 PDF 为主、少量短视频”的假设:

规模活跃用户CPU内存系统盘数据盘
小型500 以内2 核4 GB40 GB100 GB 起
中型500 至 30004 至 8 核8 至 16 GB60 GB按每人 1 至 2 GB 估
大型3000 以上8 核以上16 GB 以上独立对象存储或独立存储节点

真正容易被低估的是磁盘。moodledata的增长曲线是阶梯式的,平时几乎不动,一到期末集中交作业、上传录屏,几天就能涨几十 G。我的做法是把moodledata单独挂一块盘,或者直接挂网络存储,这样扩容的时候不用动系统盘,也不会因为根分区满了导致 MySQL 写不进去、整站 500。

还有一条经验:如果预算允许,把数据库和 Web 拆到两台机器上。倒不是为了性能,主要是为了备份和升级的时候能独立操作——升级 Moodle 代码的时候数据库可以继续在线,回滚也只需要回滚代码目录。

2. 环境选型:为什么我最终放弃了“一键集成包”

2.1 PHP 版本、扩展清单与 php.ini 的定档

一开始图省事,我用过那种把 Nginx、PHP、MySQL 打包在一起的集成面板。小规模试用确实快,但它的问题在生产环境会被放大:组件版本被锁死、配置文件被面板覆盖、日志位置不透明。最要命的是升级,Moodle 大版本升级经常要求 PHP 跟着升一档,集成包要么不给你升,要么升完把面板自己的东西搞崩。

所以后来我一律手工装。版本选择上,先看 Moodle 分支的官方兼容表,主流对应关系大致是这样:

Moodle 分支推荐 PHP数据库
4.1 至 4.28.0 至 8.2MariaDB 10.6+ / MySQL 8.0
4.3 至 4.48.1 至 8.3MariaDB 10.6+ / MySQL 8.0
4.5 及以后8.2 至 8.3MariaDB 10.11+ / MySQL 8.0

提示:具体版本对照一定要以你下载的那个 Moodle 分支自带的版本说明为准,别照抄表格。选一个比最低要求高一档的 PHP 小版本,既留了余量,又不会撞上太新的兼容问题。

扩展清单我记在一张便签上,装机时逐项核对:intl、mbstring、curl、gd、xml、zip、mysqli、soap、sodium、openssl、bcmath、redis。其中gd必须带 JPEG 和 PNG 支持,否则头像和缩略图生成会失败;zip影响课件包的导入导出。

php.ini里几个参数必须改,默认值几乎都不够用:

memory_limit = 512M max_execution_time = 300 post_max_size = 512M upload_max_filesize = 512M max_input_vars = 5000 max_input_time = 300

post_max_size和upload_max_filesize一定要设成一致的值,不一致会出现“上传 50M 的文件,转圈转到 100% 然后报错”这种极其迷惑的现象。max_input_vars从默认的 1000 提到 5000,是因为课程设置表单字段特别多,超过限制会导致部分设置项静默丢失——你以为保存成功了,其实只存了一半。

2.2 数据库与缓存的搭配逻辑

数据库我在 MariaDB 和 MySQL 之间犹豫过,最后选 MariaDB,理由很朴素:它的默认配置更偏向小内存机器,且mysqldump行为更稳。两者对 Moodle 都完全够用,不存在谁快谁慢的明显差异,别在这上面纠结太久。

字符集必须是utf8mb4,排序规则用utf8mb4_unicode_ci。老教程里常见的utf8在 MariaDB 里其实是三字节的,遇到学员姓名里的特殊字符或者表情符号会直接截断,而且这种错误在写入时不报,读出来才发现是乱码。

innodb_buffer_pool_size是最值钱的一个参数,按物理内存的 40% 到 50% 给。8 GB 内存的机器给 4 GB,剩下的留给 PHP-FPM 和系统。这个参数的逻辑是让热数据尽量待在内存里,少走磁盘 IO。压测时把缓冲池从 1 GB 调到 4 GB,课程列表的首屏加载时间能降一半以上,效果比调 PHP 明显得多。

max_allowed_packet从默认的 16M 提到 128M,否则导入大课件包或者批量备份恢复时会断连。

缓存我坚定地用 Redis,让它同时承担两件事:PHP 会话存储和 Moodle 内部的应用缓存。会话放到 Redis 之后,多台 Web 节点可以共享登录状态,后面想横向扩容不用改代码;应用缓存放 Redis 比默认的文件缓存快一个数量级,因为文件缓存每次都要走 stat 系统调用,Redis 是纯内存操作。安装方式就是系统包管理器装一个 Redis,把bind改成127.0.0.1,maxmemory设成 1 GB 左右,淘汰策略用allkeys-lru。

2.3 Web 服务器与 PHP-FPM 的进程模型

Nginx 和 Apache 都能跑 Moodle,我选 Nginx,原因不是性能,是配置可读性好、反向代理场景下调试方便。Moodle 根目录下有一堆真实存在的文件和目录,需要“先找文件、找不到再交给 PHP”这种路由规则,Nginx 的try_files表达起来很直观。

PHP 和 Nginx 之间的连接方式,我选 Unix socket 而不是 TCP 127.0.0.1:9000。差距不算巨大,但 socket 少走一层网络栈,高并发下延迟更稳定,而且不会因为端口占用引发莫名其妙的连接失败。socket 路径一般是/run/php/php8.2-fpm.sock,具体看你装的版本号。

pm.max_children这个值必须算,不能拍脑袋。算法很简单:可用内存除以单个 PHP 进程的常驻内存。

假设机器 8 GB,MySQL 拿走 4 GB 缓冲池加自身开销约 1 GB,系统和其他进程留 1 GB,剩 2 GB 给 PHP。Moodle 的 PHP 进程单个大概占 60 到 100 MB,按 80 MB 算:

2048 MB / 80 MB ≈ 25

那就设pm.max_children = 25。设太大的后果是内存被吃满触发 OOM Killer,MySQL 可能被随机杀掉,表现是整站突然连不上数据库,重启又好了,然后过几天再重演。这种间歇性故障最难查,根因往往就是进程数给多了。

3. 系统底座落地:从裸机到能跑 install.php

3.1 系统初始化与安全基线

拿到新机器,第一件事不是装软件,是把基础项固定下来。时区设成 UTC,这一点很多人有异议,说站点在国内为什么不用东八区。因为 Moodle 内部所有时间戳按 UTC 存,显示时按用户时区转换,服务器本地时区设成什么其实不影响显示。统一用 UTC 的好处是日志、数据库时间、定时任务时间三者对得上,排查问题时不用心算。

时间同步必须开,chronyd或systemd-timesyncd二选一,确保服务在跑。时间漂移会导致 HTTPS 证书校验失败、OAuth 登录令牌失效、日程提醒错乱,这些症状看起来毫无关联,根因却是同一个。

防火墙只开三个端口:22、80、443。80 保留是因为证书签发和 HTTP 跳转要用,其余一律拒绝。数据库端口 3306、Redis 端口 6379 绝对不能对公网开放,这两个是自动化扫描的头号目标。数据库配置里bind-address = 127.0.0.1,Redis 配置里bind 127.0.0.1 ::1并且protected-mode yes,双保险。

还要提醒一句:如果系统开了 SELinux 或 AppArmor,Nginx 访问moodledata目录会被拦。表现是安装到最后一步报“数据目录不可写”,但用ls -l看权限完全正常。这种时候要看审计日志,ausearch -m avc或dmesg | grep -i denied,然后用chcon或者给目录打上正确的上下文标签,别急着把 SELinux 关掉。

3.2 代码与数据目录的分离布局

目录结构我固定成两个:

/var/www/moodle 代码目录 /var/moodledata 数据目录

代码目录的所有者是www-data(Debian 系)或nginx(RHEL 系),权限 755,文件 644。数据目录权限 750,所有者同样是 Web 用户,但需要粘滞位,因为 Moodle 会在里面创建大量子目录:

sudo mkdir -p /var/moodledata sudo chown -R www-data:www-data /var/moodledata sudo chmod -R 750 /var/moodledata sudo chmod 2777 /var/moodledata

上面最后一条2777看起来和750矛盾,其实是两步:先按常规权限收紧,再对数据目录根设置组粘滞位,保证 Web 用户在里面创建的子目录可以被同一组的进程正常读写。Moodle 的config.php里有一个$CFG->directorypermissions参数,默认02777,和这里的设置是对应的。

代码目录我是 Git 拉取而不是下压缩包,好处是升级和回滚都是git checkout一个命令,而且能随时git diff确认有没有人偷偷改过核心文件。

3.3 数据库初始化与最小权限账户

建库建账户别用 root。Moodle 只需要一个库的完全权限,不需要全局权限:

CREATE DATABASE moodle DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE USER 'moodleuser'@'127.0.0.1' IDENTIFIED BY '替换成强密码'; GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER, CREATE TEMPORARY TABLES, LOCK TABLES ON moodle.* TO 'moodleuser'@'127.0.0.1'; FLUSH PRIVILEGES;

注意主机名写127.0.0.1而不是localhost,因为localhost在部分配置下会走 socket 连接,而 Moodle 的dbhost配的是 IP,两者对不上会认证失败。这种错配的报错信息通常只说“访问被拒绝”,不会告诉你是主机名匹配的问题。

表前缀默认mdl_,保持默认就行。多站点共用一个库的时候才需要改,单站点改了反而增加心智负担。

4. config.php 里的几个字段决定了你能不能上线

4.1 wwwroot、dataroot、sslproxy 的联动关系

这是我们第一次部署时卡了整整一个下午的地方。现象是:浏览器打开域名,页面开始无限跳转,最后报ERR_TOO_MANY_REDIRECTS。

根因是这样:请求到了 Nginx,Nginx 用 HTTPS 接的,但把它转给 PHP-FPM 的时候,PHP 侧看到的$_SERVER['HTTPS']是空的、SERVER_PORT是 80。Moodle 判断“我现在被 HTTP 访问了”,于是把用户重定向到 HTTPS 地址;浏览器发出 HTTPS 请求,Nginx 又传给 PHP 一个“HTTP”的视图,Moodle 又重定向一次。死循环。

解决分两半。Nginx 侧要显式传递协议头:

location ~ [^/]\.php(/|$) { fastcgi_pass unix:/run/php/php8.2-fpm.sock; fastcgi_index index.php; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param HTTPS on; fastcgi_param HTTP_X_FORWARDED_PROTO https; }

PHP 侧在config.php里加一行:

$CFG->sslproxy = true;

这一行告诉 Moodle:“我前面有个反向代理,它在帮我做 TLS 终结,你别自己判断协议,听代理的。”

核心字段的完整样子大致如下:

<?php unset($CFG); global $CFG; $CFG = new stdClass(); $CFG->dbtype = 'mariadb'; $CFG->dblibrary = 'native'; $CFG->dbhost = '127.0.0.1'; $CFG->dbname = 'moodle'; $CFG->dbuser = 'moodleuser'; $CFG->dbpass = '替换成强密码'; $CFG->prefix = 'mdl_'; $CFG->dboptions = ['dbcollation' => 'utf8mb4_unicode_ci']; $CFG->wwwroot = 'https://lms.example.com'; $CFG->dataroot = '/var/moodledata'; $CFG->admin = 'admin'; $CFG->directorypermissions = 02777; $CFG->sslproxy = true; require_once(__DIR__ . '/lib/setup.php');

wwwroot必须和浏览器地址栏里看到的完全一致,包括协议、域名、有没有末尾斜杠。末尾斜杠是最隐蔽的一个,https://lms.example.com/和https://lms.example.com在 Moodle 眼里是两个不同的地址,会导致部分 AJAX 请求跨域被拦。

如果站点已经装好了才发现wwwroot写错,不用重装,直接改config.php里的值,然后再进数据库改一下mdl_config表里wwwroot那条记录,最后清一次缓存即可。

4.2 定时任务的配置方式

Moodle 有一个核心机制:几乎所有异步的事情都靠定时任务驱动——邮件发送、论坛通知、成绩计算、备份清理、注册同步。这个任务如果不跑,站点看起来一切正常,但学员收不到任何通知,备份也不会自动生成。

配置方式是在 Web 用户的 crontab 里加一行,注意必须用 Web 用户身份跑,用 root 跑会生成一堆 root 所有者的文件,后续 Web 进程写不进去:

sudo crontab -u www-data -e
* * * * * /usr/bin/php /var/www/moodle/admin/cli/cron.php > /var/log/moodle-cron.log 2>&1

每分钟一次,这是官方建议的频率。日志重定向到单独文件很关键,出问题时能第一时间看到是哪个任务在报错。我曾经遇到过一个第三方插件在定时任务里抛异常,导致后面的任务全部被跳过,而站点管理界面只显示“定时任务运行正常”,因为 cron.php 本身退出码是 0。去翻日志才看到某一行的堆栈。

邮件通道建议直接配 SMTP 插件,填外部邮件服务的账号,别依赖本机的sendmail。本机发信在现代邮件环境下几乎必然进垃圾箱,因为缺少 SPF、DKIM 记录,而且很多服务商直接拒收家宽和机房的 25 端口。

4.3 安装引导的正确打开方式

代码放好、权限配好、数据库建好、config.php写好后,浏览器访问域名会自动跳到安装向导。这一步反而最省心,一路下一步就行。

但有两点要注意。第一,安装过程中会填写管理员账号,密码策略默认要求挺高,如果填了弱密码它会拒绝,而提示信息在页面顶部容易被忽略,让人以为是卡住了。第二,安装完成后不要立刻把config.php的权限放开,保持 640 或者 440,所有者为 Web 用户,确保里面的数据库密码不会被其他系统用户读到。

安装完成后第一件事是进“站点管理 → 开发 → 调试”把调试信息打开一档,让错误显示到日志里。生产环境不要开到“开发者”级别,那会把所有 SQL 和函数调用栈打到页面上,既泄露信息又拖慢速度。开到“正常”就行,PHP 的错误日志路径在php.ini里指到/var/log/php-fpm/moodle-error.log,方便单独看。

5. Nginx 反代与证书:把站点真正推到公网

5.1 站点配置里必须逐行确认的几段

配置文件我按功能分段来写,每一段都对应一个具体的坑。

server { listen 80; server_name lms.example.com; return 301 https://$host$request_uri; } server { listen 443 ssl; http2 on; server_name lms.example.com; root /var/www/moodle; index index.php; ssl_certificate /etc/letsencrypt/live/lms.example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/lms.example.com/privkey.pem; ssl_protocols TLSv1.2 TLSv1.3; client_max_body_size 512M; client_body_timeout 300s; add_header X-Content-Type-Options nosniff; add_header X-Frame-Options SAMEORIGIN; location / { try_files $uri $uri/ /index.php?$query_string; } location ~ [^/]\.php(/|$) { fastcgi_pass unix:/run/php/php8.2-fpm.sock; fastcgi_index index.php; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param HTTPS on; fastcgi_param HTTP_X_FORWARDED_PROTO https; fastcgi_read_timeout 300s; fastcgi_buffers 16 32k; fastcgi_buffer_size 64k; } location ~* \.(?:css|js|svg|gif|png|jpg|jpeg|woff2?|ttf)$ { expires 30d; access_log off; try_files $uri =404; } location ~ /\.(?!well-known) { deny all; } location ~ ^/(cache|localcache|temp|trashdir|sessions)/ { deny all; } }

client_max_body_size 512M必须和php.ini里的post_max_size、upload_max_filesize对齐。这三处只要有一处小,大文件上传就在那一层被拦,而且 Nginx 拦下来返回的是 413,页面上可能只显示“上传失败”,不看日志根本不知道是哪一层拒的。

fastcgi_read_timeout 300s是给长任务留的。生成课程备份、导出成绩、批量导入用户这些操作会跑几十秒甚至几分钟,默认 60 秒会被 Nginx 掐断,页面上显示 504,但后台任务其实还在跑,造成状态不一致。

最后两个location是安全兜底。禁止访问所有点开头的隐藏文件(保留证书验证用的.well-known),禁止直接访问 Moodle 的缓存和临时目录。这些目录里可能有会话文件或者序列化的数据,直接暴露风险很大。

5.2 域名解析与证书自动续期

域名解析这块没什么技术含量,A 记录指向服务器公网 IP,TTL 先设 300 秒方便调试,稳定运行后再改成 3600。如果套了 CDN,要把回源协议设成 HTTPS,并且在 Nginx 里信任 CDN 传来的X-Forwarded-For,否则日志里全是 CDN 节点的 IP,查问题时完全对不上人。

证书我用免费签发工具,命令就一条:

sudo certbot --nginx -d lms.example.com --redirect --agree-tos -m admin@example.com

它会自动改 Nginx 配置、加跳转规则、装续期定时器。签发完成后可以模拟一次续期验证:

sudo certbot renew --dry-run

这一步千万别省。我遇到过一次续期失败,原因是 80 端口的 HTTP 跳转规则把验证请求也 301 到 HTTPS 了,而续期验证必须走 80 明文。免费证书有效期只有 90 天,续期挂了不会立刻有感知,等到第 90 天整站证书过期,浏览器直接拦截页面,那时候就是紧急事故了。所以后续我加了一个监控项,每天检查证书剩余天数,低于 20 天就报警。

5.3 上线前的链路验证清单

配置写完不要直接宣布上线,按这个顺序验一遍,能省掉大量“到底哪一层出问题”的猜谜:

验证项命令或方式期望结果
HTTP 跳 HTTPScurl -I http://lms.example.com返回 301,Location 指向 https
HTTPS 可达curl -I https://lms.example.com返回 200 或 303
证书链完整openssl s_client -connect lms.example.com:443 -servername lms.example.com显示 verify return code: 0
静态资源浏览器打开页面,看 Network 里 css/js无 404,无 403
大文件上传后台上传一个 200M 文件进度条走到 100% 且提示成功
数据目录不可访问curl -I https://lms.example.com/moodledata/返回 403 或 404
定时任务tail -f /var/log/moodle-cron.log每分钟有新输出,无异常堆栈

最后一项特别值得盯。定时任务是唯一一个“不跑也不会立刻出问题”的组件,所以最容易被忽略。

6. 上线后的调优、备份与排错

6.1 OPcache 与缓存的联合调优

Moodle 的代码量很大,一个请求会加载几百个 PHP 文件,不走 OPcache 的话每次都要重新编译,CPU 会被白白吃掉。

opcache.enable=1 opcache.memory_consumption=256 opcache.interned_strings_buffer=32 opcache.max_accelerated_files=20000 opcache.validate_timestamps=1 opcache.revalidate_freq=60

max_accelerated_files给到 20000 是有讲究的,默认 10000 装不下 Moodle 加插件之后的文件数,超出的部分不会被缓存,表现为“只有部分页面快,部分页面慢”。validate_timestamps=1加revalidate_freq=60是保守选择,好处是改代码后一分钟内自动生效,坏处是每分钟检查文件时间戳有一点开销。如果站点完全稳定、只按计划发布版本,可以关掉验证,手动 reload PHP-FPM 来生效,性能会再好一点。

Moodle 内部的应用缓存(MUC)要切到 Redis。入口在“站点管理 → 插件 → 缓存 → 缓存实例”,新增一个 Redis 实例,填上127.0.0.1:6379,然后把“应用”类型的缓存映射过去。也可以在config.php里声明:

$CFG->cachestores = [ 'redis' => [ 'name' => 'redis', 'plugin' => 'redis', 'configuration' => [ 'server' => '127.0.0.1', 'port' => 6379, 'prefix' => 'muc_', 'serializer' => 1, ], ], ];

注意:这个配置数组的字段名会随版本小幅变化,写之前对照一下当前版本的 Redis 缓存插件说明,别直接复制粘贴就上生产。

会话同样切到 Redis:

$CFG->session_handler_class = '\core\session\redis'; $CFG->session_redis_host = '127.0.0.1'; $CFG->session_redis_port = 6379; $CFG->session_redis_prefix = 'mdlsess_'; $CFG->session_redis_acquire_lock_timeout = 120;

配完之后不同浏览器窗口的登录状态会立刻变得一致,而且重启 PHP-FPM 不会把所有人踢下线。

6.2 备份、升级与回滚的操作顺序

备份这件事,我吃过一次亏。当时只备份了数据库,觉得代码和文件都在服务器上、随时能重新拉。结果某次升级把插件目录搞乱了,代码仓库里没有插件的第三方代码,重新拉主程序根本起不来。从那以后我固定成三件套一起备:

# 数据库,用单事务保证一致性 mysqldump --single-transaction --quick --routines \ -u moodleuser -p moodle > /backup/moodle-$(date +%F).sql # 代码目录 tar czf /backup/moodle-code-$(date +%F).tar.gz -C /var/www moodle # 数据目录,用 rsync 增量,别用 tar,文件太多太慢 rsync -a --delete /var/moodledata/ /backup/moodledata/

备份文件一定要做一次恢复演练。没恢复过的备份只能叫“以为有的备份”。演练很简单:另起一台机器,把三件套还原,改config.php里的域名和数据库连接,看能不能正常登录、能不能打开一门课的附件。

升级的标准顺序是先备份、再进维护模式、再更新代码、再跑升级向导、最后退出维护模式:

sudo -u www-data /usr/bin/php /var/www/moodle/admin/cli/maintenance.php --enable cd /var/www/moodle && git fetch && git checkout 目标版本标签 sudo -u www-data /usr/bin/php /var/www/moodle/admin/cli/upgrade.php --non-interactive sudo -u www-data /usr/bin/php /var/www/moodle/admin/cli/purge_caches.php sudo -u www-data /usr/bin/php /var/www/moodle/admin/cli/maintenance.php --disable

升级向导那一步如果用浏览器跑,遇到超时会留下“升级到一半”的状态,非常麻烦。所以我固定用命令行版,跑完会明确告诉你成功还是失败。

6.3 五个高频报错的定位路径

排错的核心思路是“先确定是哪一层出的问题”。我的习惯顺序是:Nginx 错误日志 → PHP-FPM 慢日志和错误日志 → Moodle 自己的错误日志 → 数据库慢查询。大部分问题在前两层就能定位。

现象最可能的根因定位方式
页面白屏无任何输出PHP 致命错误被隐藏看php-fpm日志,临时提高display_errors
重定向次数过多wwwroot与实际地址不符或sslproxy未开检查config.php两个字段
上传大文件失败三层限制中有一层值偏小对照 Nginx、php.ini、Moodle 站点设置三处
定时任务不执行crontab 用户身份不对或 PHP 路径错手动sudo -u www-data php cron.php试跑
安装插件提示目录不可写权限或安全模块拦截ls -l看属主,再看 SELinux 审计日志

白屏这个最典型。默认的display_errors = Off,任何语法错误或者内存溢出都表现为一个空白页,什么提示都没有。定位方法是先看 PHP 错误日志,如果日志也是空的,那就临时在config.php顶部加两行把错误打开:

ini_set('display_errors', 1); error_reporting(E_ALL);

这样页面会直接打印出文件和行号。看到之后立刻删掉这两行,别留在生产配置里。

另外提一个很隐蔽的问题:磁盘满。moodledata撑爆分区之后,MySQL 写不进去,Moodle 会报数据库错误;同时日志也写不进去,导致错误日志是空的。所以“日志是空的但确实出问题了”,先df -h看一眼,往往就是磁盘。

再分享一个我个人在实际操作中的体会:每次动完配置,不要靠记忆判断有没有生效,用nginx -t校验语法、用systemctl reload而不是restart、用curl -I看返回头。这三步花不了一分钟,但能挡掉大部分“改完没生效、以为改错了方向”的无效排查。我先在地的一台机器上就因为在 Nginx 配置里写错了一个分号,reload直接失败但命令没报错,导致后面半个小时都在怀疑 PHP 那边的问题。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/11/4 5:06:21

美业数字化转型:四大效率引擎与实战陷阱解析

1. 美业效率革命的行业背景过去三年间&#xff0c;美业门店的经营成本平均上涨了23%&#xff0c;而客单价仅提升8%——这组来自《2023中国生活美容行业发展报告》的数据&#xff0c;正在倒逼行业进行一场静默的效率革命。我走访过37家不同规模的美容院后发现&#xff0c;那些活…

作者头像 李华
网站建设 2026/11/4 5:05:38

pkg-config工具详解:Linux开发依赖管理实战

1. pkg-config 工具概述pkg-config 是 Linux/Unix 系统下用于管理编译和链接参数的经典工具&#xff0c;它通过查询 .pc 文件来获取库的编译选项、链接选项和版本信息。这个看似简单的小工具&#xff0c;在实际开发中却能解决令人头疼的依赖管理问题。我第一次真正体会到 pkg-c…

作者头像 李华
网站建设 2026/11/4 5:05:30

AI工程落地的四大技术拐点:MaaS、端侧推理、代码熵减与缓存基建

1. 这不是新闻通稿&#xff0c;是AI工程师的实操日志&#xff1a;四条消息背后的工程水位线今天刷技术群和GitHub Trending的时候&#xff0c;我手里的咖啡凉了三次——不是因为消息多&#xff0c;而是每一条都精准踩在当前AI工程落地的几个关键瓶颈上。GPT-6家族补齐&#xff…

作者头像 李华
网站建设 2026/11/4 5:05:30

DB2异机恢复:归档日志、CLIENT_NAME与restore避坑指南

简介&#xff1a;面向需要借助NetBackup完成DB2数据库异机恢复的DBA与运维人员&#xff0c;这份文档围绕DB2 Agent配置、数据库参数调整、备份脚本编写及db2.conf策略设定展开&#xff0c;涵盖Agent配置、参数设置、备份脚本、db2.conf策略及归档日志处理等模块&#xff0c;重点…

作者头像 李华
网站建设 2026/11/4 5:05:11

免费自学PPT制作:十类优质网站与八页汇报实操路径

做演示文稿这件事&#xff0c;我见过太多人是从“搜模板”入门的&#xff0c;最后也停在“搜模板”。模板换了一百套&#xff0c;版式还是歪的&#xff0c;字号还是乱的&#xff0c;讲的时候还是照着念。问题不在天赋&#xff0c;而在于自学路径选错了&#xff1a;把素材站当成…

作者头像 李华
网站建设 2026/11/4 5:02:59

ArcGIS栅格数据开三次方运算全1值问题解析

1. 问题现象与背景解析最近在ArcGIS平台进行栅格数据分析时&#xff0c;遇到了一个看似简单却令人困惑的现象&#xff1a;对栅格数据进行开三次方运算后&#xff0c;结果图层的最小值和最大值都变成了1。这种情况在遥感指数计算、地形因子处理等场景中尤为常见&#xff0c;直接…

作者头像 李华