WordPress 500 错误与内存超限:从 1G 内存到并发优化
500 Internal Server Error 不是一种错误,是一类。它只说明服务器在处理请求时异常终止了,具体是 PHP 致命错误、执行超时、内存打满还是权限不对,全都要另外找。所以看到 500,先去翻日志,不要先去改代码。
这篇按「先定位来源、再拆两层内存限制、最后算并发」的顺序写。1G 内存的机器能撑多少并发,是问得最多也最容易算错的问题,放在中间单独讲。
500 的四种来源
先把范围缩小。500 出现在什么范围、什么时候,比错误本身更有信息量。
| 现象 | 常见原因 | 处理 |
|---|---|---|
| 所有页面 500,包括 /wp-admin/ | PHP 致命错误、wp-config.php 语法错、切换 PHP 版本后扩展缺失 | 看 error_log 首行;回退 PHP 版本;用 php -l 检查语法 |
| 只有个别页面 500 | 单页内存超限、插件在该页执行了重查询、脚本超时 | 调 memory_limit 与 max_execution_time,再查慢查询 |
| 间歇性 500,刷新就好 | PHP-FPM 进程打满、MySQL 连接数占满、上游超时 | 看 php-fpm 日志里的 max_children 告警 |
| 上传图片或导入数据时 500 | 内存、超时、上传体积三重限制同时触发 | 三项一起调,只调一项会继续失败 |
| 刚改完 .htaccess 就 500 | 伪静态规则语法错误,或模块没加载 | 先备份再删掉 .htaccess,让 WordPress 重建 |
先拿到真实报错
三个地方看:wp-content/debug.log(要先在 wp-config.php 开 WP_DEBUG 和 WP_DEBUG_LOG)、主机面板的 error_log(一般在站点根目录或 logs/)、nginx 的 error.log 与 php-fpm 的慢日志。三条日志的时间戳能对上,才能拼出完整链路。只有 500 没有日志,通常说明错误发生在 PHP 启动之前,比如 php-fpm 进程起不来。
tail -n 100 /www/wwwlogs/example.com.error.log
grep -iE "fatal|memory|timeout" /www/wwwroot/example.com/wp-content/debug.log | tail -n 50
日志里看到「Allowed memory size of 134217728 bytes exhausted」,就是内存;看到「Maximum execution time of 30 seconds exceeded」,是超时;看到「Permission denied」或者「failed to open stream」,是权限或磁盘满。三种的处理方式完全不同,先认清再动手。
两层内存限制,只改一个没用
WordPress 的内存有两个闸门,很多人只改了一个,所以会出现「明明调到 512M 还是报 256M」。第一层是 php.ini 的 memory_limit,这是硬上限,PHP 进程不能超过。第二层是 WordPress 通过 WP_MEMORY_LIMIT 调的 ini_set,它只能往低了调,不能突破硬上限。
| 层级 | 配置位置 | 作用范围 | 常见坑 |
|---|---|---|---|
| PHP 硬上限 | php.ini 的 memory_limit | 整个 PHP 进程 | 改了 php.ini 没重启 php-fpm,配置不生效 |
| WordPress 前台 | wp-config.php 的 WP_MEMORY_LIMIT | 前台请求 | 写得比 php.ini 大,实际还是按 php.ini 走 |
| WordPress 后台 | WP_MAX_MEMORY_LIMIT | wp-admin 与 cron | 只调前台,后台导入仍然白屏 |
| 单脚本临时提升 | ini_set( ‘memory_limit’, ‘512M’ ) | 当前请求 | 主机禁用 ini_set 时静默失败 |
; php.ini
memory_limit = 256M
max_execution_time = 120
upload_max_filesize = 64M
post_max_size = 64M
// wp-config.php
define( 'WP_MEMORY_LIMIT', '256M' );
define( 'WP_MAX_MEMORY_LIMIT', '512M' );
改完不要凭感觉,去「工具 → 站点健康 → 信息 → 服务器」看实际生效值。共享主机常用 disable_functions 禁掉 ini_set,这种情况 WP_MEMORY_LIMIT 写了也无效,只能找主机商提,或者换到能改 php.ini 的环境。
1G 内存到底能撑多少并发
先纠正一个常见误解:1G 不是「同时服务 1G 的请求」,而是「所有 PHP 进程加起来不能超过 1G」。每个 PHP-FPM 进程处理一个请求,峰值内存在 60M 到 150M 之间浮动,取决于插件数量和当前页面。所以并发数是「可用内存除以单进程峰值」,不是「内存除以页面大小」。
可用内存也不是 1024M,要扣掉系统、MySQL、Redis、nginx:
总内存 1024M
- 系统与 nginx 约 150M
- MySQL(含缓冲池) 约 300M
- Redis(可选) 约 64M
= 留给 PHP-FPM 约 500M
| 单进程峰值 | 1G 机器(可用 500M)的并发 | 典型场景 |
|---|---|---|
| 60M | 约 8 个 | 纯展示站,少量插件,无页面构建器 |
| 100M | 约 5 个 | 装了缓存、SEO、表单插件 |
| 150M | 约 3 个 | WooCommerce 或页面构建器 |
| 200M 以上 | 2 个以内 | 电商 + 多语言 + 一堆插件 |
也就是说 1G 的机器真实并发只有 3 到 8 个。但静态资源走 CDN、页面走缓存之后,单次访问从「执行 PHP」变成「直接返回 HTML」,同样 1G 能扛的日 PV 可以从几千涨到几万。所以内存不够的第一反应不该是升配,而是先看缓存命中率。这一点在 速度优化 里说了很多遍,仍然是最划算的一步。
对应的 php-fpm 配置:pm 用 dynamic,pm.max_children 按上表设,别设成 50。设大了不是更快,而是一旦并发上来,所有进程互相挤,OOM Killer 先把 MySQL 杀掉,表现就是「网站 500,数据库连不上」。
pm = dynamic
pm.max_children = 6
pm.start_servers = 2
pm.min_spare_servers = 2
pm.max_spare_servers = 4
pm.max_requests = 500
pm.max_requests 设 500 的作用是让进程处理一定请求数后自动重启,防止内存泄漏累积。有些插件确实会漏内存,这个参数能兜住一部分。
内存大户:图片处理和插件
按经验排,吃内存的顺序是:图片生成缩略图、大型导入导出、页面构建器渲染、多语言插件、安全扫描插件。一张 6000×4000 的 JPEG 解码成位图大约要 96M(宽 × 高 × 4 字节),再叠加 GD 或 Imagick 的中间副本,单张就能冲到 200M 以上。批量重新生成缩略图时一个请求处理几十张,必然超限。
处理办法是分批。用 WP-CLI 的 media regenerate 加 –only-missing,或者一次只处理固定几个 ID。上传前把图片压到 2000px 宽以内,比事后调内存有用得多。
wp media regenerate --only-missing --yes
wp media regenerate 1201 1202 1203 --yes
插件侧的重点是「每个请求都跑的东西」:自动翻译、相关文章、访问统计、安全扫描。这些如果写了没走索引的查询,或者每次请求都拉全表,内存和 CPU 一起涨。用 Query Monitor 看页面查询数和峰值内存,超过 80 次查询、峰值超过 120M 的页面要单独优化。重复功能的插件(两个缓存、两个 SEO)先删一个,这类叠加的消耗最没有意义。
升级服务器之前,先按这个顺序做
大部分人一遇到 500 就加内存,加完管一个月,然后照旧。正确的顺序是从「减少请求」开始,最后才动硬件:
- 开页面缓存,把首页和文章页静态化,命中率做到 80% 以上。这一步收益最大,通常能把 PHP 请求砍掉八成。
- 图片和静态资源走 CDN,顺便开 WebP,带宽和并发一起降。
- 清理数据库:修订版本、垃圾评论、过期 transient、孤儿 postmeta。这几张表膨胀后,连后台列表页都要吃上百 M,清理方法见 数据库优化。
- 关掉不用的插件,尤其是功能重叠的。每少一个插件,单请求内存和查询数都会降一点。
- 把 PHP 升到 8.2 或 8.3,同配置下吞吐通常比 7.4 高两成以上,内存也更省。升级前先在测试环境跑一遍,老插件在 8.x 上报 deprecated 是常态。
- 加对象缓存(Redis),减轻 MySQL 压力,间接降低单请求耗时和内存占用。
- 最后才是加内存或换机器。走到这一步还没解决,说明瓶颈确实在硬件。
顺序反了会怎样:先升到 4G,插件还是那堆,缓存还是没开,一个月后照样 500,只是把故障推迟了一个月,成本却已经付了。
防复发
- 把 php.ini 的 memory_limit 和 WP_MEMORY_LIMIT 写成一致的值,避免「以为调了其实没调」。
- 监控里加三条:PHP-FPM 的 max_children 触顶次数、MySQL 连接数、502/500 比例。有告警比事后翻日志强。
- 每次装插件前后各跑一次 Query Monitor,对比查询数和内存,涨得离谱的直接换掉。
- 图片上传尺寸在主题或插件里限制死,编辑上传 8000px 原图的习惯要改。
- 500 出现时先看时间点,对上「刚更新了什么」,八成能直接定位。
如果 500 伴随数据库报错,比如 MySQL server has gone away、Table is marked as crashed,先看 数据库报错处理,那属于另一条线,单加内存解决不了。如果页面直接白屏没有状态码,按 白屏排查 的顺序走。