下午三点,客户说网站白屏。你 SSH 上去,第一个念头是 wp plugin deactivate --all。这条命令会顺手停掉购物车、支付和登录——插件冲突修好了,营业也中断了一次。
下面这套做法的目标是:让“停用后的结果”只出现在你自己的浏览器里,访客看到一个完整可用的站。
先把影响面划出来
同一个站上同时存在三类请求:你自己的调试请求、正常访客的浏览和下单、机器发来的请求(支付回调、定时任务、搜索引擎)。后两类一旦被停用波及,就是事故。
所以排查动作要满足:只对带令牌的请求生效,令牌不能是猜得到的字符串,开关不能依赖后台(白屏时你进不去后台)。
用 mu-plugin 做请求级开关
放在 wp-content/mu-plugins/aum-safe-mode.php。mu-plugin 自动加载、后台停不掉,正好适合当逃生开关。令牌写在 wp-config.php 里:
define('AUM_SAFE_TOKEN', '一串随机字符,别用短词');
<?php
/**
* Plugin Name: Safe Mode Toggle
* Description: 只对带令牌的请求停用指定插件或临时切换主题,其他访客不受影响。
*/
define('AUM_SAFE_PLUGINS', [
'woocommerce/woocommerce.php',
]);
function aum_safe_mode_on(): bool {
if (!defined('AUM_SAFE_TOKEN') || AUM_SAFE_TOKEN === '') {
return false;
}
$token = isset($_GET['aum_safe']) ? (string) $_GET['aum_safe'] : '';
return $token !== '' && hash_equals(AUM_SAFE_TOKEN, $token);
}
add_filter('option_active_plugins', function ($plugins) {
if (!aum_safe_mode_on()) {
return $plugins;
}
return array_values(array_diff($plugins, AUM_SAFE_PLUGINS));
}, 99);
add_filter('option_template', 'aum_safe_mode_theme');
add_filter('option_stylesheet', 'aum_safe_mode_theme');
function aum_safe_mode_theme($value) {
return aum_safe_mode_on() ? 'your-default-theme' : $value;
}
用法:https://example.com/?aum_safe=<令牌>&cb=12345。带令牌时插件被摘掉,普通访客的请求完全走原样。
两个细节:多站点环境下活动插件存在 site_option_active_plugins,要把同一个回调挂到那个 filter 上;your-default-theme 换成站上已经装好的主题目录名,写一个不存在的目录会直接白屏。
缓存和 OPcache 会让结果骗你
停用没生效,多半是缓存层在骗你,代码这边没问题。
页面缓存和 CDN:带 query string 的请求可能命中缓存规则,也可能被 CDN 忽略 query 直接回缓存。测试时加随机参数,并确认响应头里有回源标记。
TOKEN='你的令牌'
curl -sSI "https://example.com/?aum_safe=$TOKEN&cb=$RANDOM" \
| grep -iE '^(HTTP/|cache-control|age|vary|x-cache)'
持久对象缓存:active_plugins 属于 alloptions,直接改数据库时缓存里还是旧值,改完执行 wp cache flush。
OPcache:改了 PHP 文件但 validate_timestamps 关掉时,文件不会被重新编译。改完 reload php-fpm,或者在 CLI 里 opcache_reset()。这一步漏掉,你会对着一份没生效的代码调半天。
白屏进不了后台:先看致命错误落在哪个文件
先别动插件,先把错误显示出来。wp-config.php:
define('WP_DEBUG', true);
define('WP_DEBUG_LOG', true); // 写到 wp-content/debug.log
define('WP_DEBUG_DISPLAY', false); // 不要把错误打到页面上
然后看 php-fpm 或 nginx 的 error log 里 Fatal error 的栈顶路径。路径里带 wp-content/plugins/<目录名>/,基本就锁定了,不用一个个停插件试。
有 SSH 的话,用单次请求验证,不改全站状态:
wp --skip-plugins=woocommerce plugin list --status=active
wp --skip-plugins=woocommerce eval 'echo "ok\n";'
注意 wp plugin deactivate <slug> 是全局生效的,只在确认影响可接受时才用;排查阶段用 --skip-plugins 更合适。
判断是插件、主题还是核心
看致命错误落在哪个目录,比逐个变量好使:
- 落在
wp-includes/或wp-admin/,且最近没人动过插件 → 先怀疑核心文件被改过,或者 PHP 扩展、内存限制的问题。 - 落在
wp-content/themes/→ 主题。用上面那个开关临时切主题,同一个请求里验证。 - 落在
wp-content/plugins/或mu-plugins/→ 插件,文件名都在栈里写着。
还有一种不是致命错误的情况:页面能打开但样式错乱、表单提交后无反应。这类多半是前端资源冲突,看浏览器 Console 的报错,再去对照那一屏加载了哪些插件的脚本。
停用之后,数据会不会丢
停用不等于卸载:wp_options、自定义表、post meta 都还在,重新启用一般能接上。风险在这几处:
- 卸载钩子。有些插件在卸载时删表删配置,停用不会触发,但如果你顺手点了“删除”,就没了。
- 队列里没消费完的任务。停用后这些任务不会自己跑,恢复后要手动补。
- 重新启用后的数据结构升级。版本跨度大时插件会自己跑升级流程,先备份再启用。
停用前存两样东西:
wp option get active_plugins --format=json > active-plugins.json
wp db export before-debug.sql
数组顺序就是加载顺序,恢复时按原样写回。用 wp plugin activate 一个个装回去,顺序变了也可能出问题。
停掉支付和表单之后,业务怎么继续
这一条得提前和业务方说好,不能等出事再商量:
- 关掉下单入口,保留浏览和订单查询,页面上明说“暂停接单”。
- 表单停用期间给一个兜底:静态页面上的邮箱、电话,或者另一个不依赖该插件的表单。
- 支付回调在停用期间到达的,先原样落库。网关通常会重发,恢复后按幂等键重放,别让通知在你修好之前过期。
按请求把日志对齐
故障发生的时间点只有一个,日志却分散在 PHP、Web 服务器、插件自身和浏览器里。用一个请求 id 把它们串起来:
function aum_req_id(): string {
static $id = null;
if ($id === null) {
$id = $_SERVER['HTTP_X_REQUEST_ID'] ?? bin2hex(random_bytes(8));
}
return $id;
}
add_filter('http_request_args', function ($args, $url) {
$args['headers']['x-request-id'] = aum_req_id();
return $args;
}, 10, 2);
排查时把时间戳统一成 UTC,在浏览器 Network 里找到那个请求,去 access log 里对同一秒的那一行,再看同一时刻的 PHP error log。三边时间对上了,因果链才成立。
回滚窗口和恢复确认
回滚要按顺序做完这几步:写回 active_plugins、wp cache flush、清页面缓存、reload php-fpm,最后再验证一遍。少一步就会出现“改回去了但还是坏的”。
验证别只看首页返回 200,跑一遍关键路径:登录、加购、结账(用测试支付方式)、表单提交、REST 根路径是否可达。
curl -sS -o /dev/null -w '%{http_code}\n' https://example.com/wp-json/
REST 根路径返回 200,说明 rewrite 和权限层没有被拦住——有些安全类插件会顺手把它一起拦掉,而支付回调和第三方对接正好都走这条路。
如果只是想知道自己站现在有哪些明显毛病(HTTPS、首页响应、robots、sitemap 这些),可以先跑一遍 站点体检,再决定要不要动插件。