WordPress 页面缓存零 PHP:WP Super Cache + nginx 静态直出-第三维度MokIII

先说效果

改成 nginx 静态直出后,缓存命中的请求完全不会进入 PHP-FPM,一个 worker 都不占。对和别的站共用同一个 PHP-FPM 池的机器(比如宝塔上一台机器放好几个站),这是最直接的提速手段。

代价是:多了一段 nginx 配置要维护,而且它有两个不讲道理的坑(见下文)。

一、先搞清楚 WP Super Cache 的两种模式

WP Super Cache 缓存的「内容」是一样的,区别只在谁来把缓存吐给浏览器:

模式命中缓存时发生什么
PHP 模式(默认)请求进 PHP → advanced-cache.php 判断命中 → 直接输出缓存文件。省掉了数据库和模板渲染,但仍然占用一个 PHP-FPM worker
mod_rewrite 模式nginx 发现缓存文件存在 → 直接 rewrite 到那个静态文件返回,完全不进 PHP

所以「缓存已经开了」不等于「已经最优」——默认的 PHP 模式,每个缓存命中都在吃 worker。

二、第一步:确认 WPSC 已启用,并切到 mod_rewrite

看 wp-content/wp-cache-config.php,关键几行:

$cache_enabled       = true;
$super_cache_enabled = true;
$wp_cache_mod_rewrite = 1;     // 1 = 允许 mod_rewrite 静态直出
$cache_max_time      = 3600;   // 缓存过期时间(秒)
$wp_cache_clear_on_post_edit = 1;  // 编辑/发布文章时自动清缓存

$wp_cache_mod_rewrite = 1 也可以在后台「设置 → WP Super Cache → 高级」里勾选。改完记得确认缓存目录可写,缓存文件长这样:

wp-content/cache/supercache/<你的域名>/<URI>/index-https.html
  • https 站点是 index-https.html,http 站点是 index.html,后缀别写错;
  • <URI> 是去掉首尾斜杠的路径,首页的缓存文件直接放在域名目录下。

三、第二步:让 nginx 直接返回缓存文件

在站点的 nginx 配置里加一段(宝塔可以放在 vhost/nginx/extension/<域名>/ 下的独立文件里):

set $wpsc_uri $request_uri;

# 以下任一条件成立就跳过静态缓存,回落到 PHP(与插件自身规则保持一致)
if ($request_method = POST)                      { set $wpsc_uri "NOCACHE"; }
if ($query_string != "")                         { set $wpsc_uri "NOCACHE"; }
if ($request_uri ~* "/wp-admin/|/xmlrpc.php|wp-.*\.php|/feed/|index.php|sitemap(_index)?.xml|/wp-json/") { set $wpsc_uri "NOCACHE"; }
# 登录用户、评论过的人、有密码的页面一律不缓存
if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_no_cache|wordpress_logged_in") { set $wpsc_uri "NOCACHE"; }

if (-f $document_root/wp-content/cache/supercache/$http_host/$wpsc_uri/index-https.html) {
    rewrite ^(.*)$ /wp-content/cache/supercache/$http_host/$wpsc_uri/index-https.html break;
}
# 如果你的站还有 http 访问(没有强制 https),把这个 http 分支也加上
if (-f $document_root/wp-content/cache/supercache/$http_host/$wpsc_uri/index.html) {
    rewrite ^(.*)$ /wp-content/cache/supercache/$http_host/$wpsc_uri/index.html break;
}

如果你有会员中心、表单页这类必须实时的页面,再加一条按路径排除:

if ($request_uri ~* "^/(sign|uc|edit)(/|$|\?)") { set $wpsc_uri "NOCACHE"; }

上面这三个路径是我自己的会员中心 / 用户页 / 草稿编辑页,换成你自己不能缓存的路径。

注意 $wpsc_uri 用的是 $request_uri(含路径),带尾斜杠的 URI 会被拼成 <路径>//index-https.html,实际能命中的是不带尾斜杠的目录形式——所以请求 /about/ 时可能回落 PHP。这不是配置错误,WordPress 本身也会把 /about/ 301 到 /about。

四、两个必须知道的坑

坑 1:rewrite ... break 会终止整个 server 级 rewrite 脚本

nginx 会把 vhost 主文件以及它 include 的所有 server 级指令编译成同一个 rewrite 脚本。break 的语义是终止这个脚本剩余部分——不是"只跳出当前 if"。

后果:如果你的缓存直出规则排在反爬/拦截规则前面,只要命中了缓存,后面的规则(例如「某些 UA 返回 403」)会被整体跳过。

我实际的教训:把文件命名为 02-wpsc-static.conf 时,对已生成缓存的 URI(比如首页),AhrefsBot / SemrushBot 全部 200(没被拦);调整 include 排序、让静态直出规则排在最后,才恢复 403。

做法:把这个配置文件放在 extension 目录里排序最后的位置,include 顺序不要动。

坑 2:改了这个文件必须回归自检

改完跑两条命令,确认拦截和直出都还正常:

# 期望 403(反爬规则仍生效)
curl -sk -o /dev/null -w '%{http_code}\n' -A 'AhrefsBot/7.0' 
     --resolve www.mokiii.com:443:127.0.0.1 https://www.mokiii.com/

# 期望 200(正常访客仍能拿到页面)
curl -sk -o /dev/null -w '%{http_code}\n' 
     --resolve www.mokiii.com:443:127.0.0.1 https://www.mokiii.com/

五、怎么确认你真的命中了静态文件

两个判据,一起用最准:

# 1) 看响应头:PHP 模式会下发 cache-control: max-age=3, must-revalidate
#    命中静态文件时不会出现这个头,而是带 last-modified / etag
curl -skD - -o /dev/null https://www.mokiii.com/ | grep -iE 'HTTP/|cache-control|last-modified|etag'

# 2) 比字节:响应体应与缓存文件逐字节一致
curl -sk https://www.mokiii.com/ -o /tmp/live.html
md5sum /tmp/live.html wp-content/cache/supercache/www.mokiii.com/index-https.html

注意第 2 条的陷阱:有些 CDN(比如阿里云的边缘安全加速)会在边缘注入脚本(例如 RUM 监控),此时 md5 会不一致,但差异只在那一行注入代码。发现不一致时先看差异内容,别急着判定缓存没生效。

六、回滚

  1. 删掉那段 nginx 配置(或整个 extension 文件),nginx -t && nginx -s reload;
  2. 把 wp-cache-config.php 里的 $wp_cache_mod_rewrite 改回 0。

回滚后缓存仍然有效,只是退回 PHP 模式——不会因为回滚就变得"没缓存",这点可以放心。


一句话总结:WP Super Cache 默认只是"省数据库和模板",切到 mod_rewrite 再配 nginx 直出才是"连 PHP 都省"。但那段 rewrite ... break 的排序是硬要求,改完必须回归自测。