≡articles //snippets ./categories $uses ~about /search

Nginx FastCGI Cache 缓存清除完整指南:为什么改了首页却看不到更新

Nginx FastCGI Cache 缓存清除踩坑实录:花了整个下午排查一个问题:一个 WordPress 站 首页改了一行文字,Chrome 无痕模式打开 , 没变化。

确认了不是浏览器缓存。确认了不是 CDN。确认了 WordPress 确实保存了改动。

最后发现是三件事同时坏掉了,互相掩盖。拆开来讲。

缓存架构回顾

这个 WordPress 站 用的是宝塔面板的标准 Nginx FastCGI Cache 方案(Nginx 官方文档)。四层缓存栈:

Cloudflare → Nginx fastcgi_cache → Redis Object Cache → PHP OPcache

首页的 HTML 由 PHP 生成后存进 Nginx 的 fastcgi_cache,下次访客访问直接返回 HTML,不再走 PHP → MySQL → Redis 整条链路。TTL 1 天,缓存目录结构为 levels=1:2。

就是这个 levels=1:2 埋了第一个雷。

三层失效

改了一行文字 → 保存 → 无痕看不到。拆开看,三层都在静默失败:

第一层:Nginx 没有 purge location

Cache-Control 覆盖也要注意,配完 FastCGI Cache 后单独检查。同时 Nginx Helper 插件在内容更新后会发一个清除请求,告诉 Nginx “这个 URL 的缓存请删掉”。但 Nginx 配置里必须有对应的 location 块来处理这个请求。

宝塔默认配置里没有。Nginx Helper 每次更新文章都在对着空气喊话 , 请求发过去了,Nginx 收到 /purge/some-post/ 这个路径,不认识,交给 WordPress 处理,返回 403。

# 没有 purge location 时
curl -X PURGE https://site.com/some-post/
# → 403 Forbidden

Nginx Helper 有两个清除模式:

  • unlink_files — 直接删除磁盘上的缓存文件
  • get_request — 发 HTTP GET 请求到 Nginx 的 purge location

我当时用的是 unlink_files。它的逻辑是:拿到 URL → 算 MD5 → 按 levels 拼路径 → 删文件。

问题在于 levels=1:2。Nginx 把缓存文件分散在 0/ 到 f/ 共 16 个二级目录里,每个二级目录下面还有 256 个三级目录。unlink_files 构造的路径跟 Nginx 实际存放的路径对不上。

插件以为自己删了,什么都没发生。

第三层:cache_path 指向了错误的目录

wp-config.php 里:

define('RT_WP_NGINX_HELPER_CACHE_PATH', '/www/server/fastcgi_cache');

但 Nginx Helper 自己的选项里存的是:

cache_path: /dev/shm/nginx-cache

/dev/shm/ 是个 tmpfs 内存盘。里面确实是空的 , 真正的缓存在 /www/server/fastcgi_cache/ 下面。

就算 unlink_files 路径拼对了,它也在删一个空目录。

三层叠加效果:改了首页 → Nginx Helper 收到钩子 → 用错误的模式在错误的目录删了 0 个文件 → Nginx 继续返回缓存的旧 HTML → 无痕看不到变化。

修复

Step 1:Nginx 加 purge location

每个站的 vhost 配置文件,server {} 块内,include enable-php-*.conf; 之前加:

# FastCGI Cache Purge
location ~ /purge(/.*) {
    allow 127.0.0.1;
    deny all;
    fastcgi_cache_purge WORDPRESS "httpsGET$host$1";
}

httpsGET 是硬编码的。因为缓存 key 是 $scheme$request_method$host$request_uri,所有被缓存的请求都是 HTTPS + GET。如果写成 $scheme,从本地发过来的 purge 请求的 scheme 跟原始请求不一致,key 对不上,删不掉。

$1 是正则捕获组,取 /purge/ 后面的路径部分,把前缀剥掉。

重载 Nginx:

nginx -t && nginx -s reload

验证 purge 端点是否响应:

curl -sk -o /dev/null -w "HTTP %{http_code}n" 
  -A "Mozilla/5.0" -H "Host: your-domain.com" 
  "https://127.0.0.1/purge/"

# 预期:HTTP 200

Step 2:Nginx Helper 切到 get_request 模式

WordPress 后台 → 设置 → Nginx Helper,按以下配置:

Nginx FastCGI Cache 缓存清除 — Nginx Helper 设置页面

设置项 值
Caching Method nginx Fastcgi cache
Purge Method get_request
Cache Directory /www/server/fastcgi_cache
Purge on Post Publish ✅
Purge on Post Update ✅
Purge Homepage on Edit ✅
Purge Archive on Edit ✅

从 unlink_files 切到 get_request 之后,Nginx Helper 不再自己去磁盘上找文件,而是发 HTTP 请求给 Nginx,由 ngx_cache_purge 模块处理。

Step 3:mu-plugin 改写 purge URL 到本地

get_request 模式默认往公网 URL 发请求:https://site.com/purge/some-post/。

这个请求会经过公网 → 回到服务器 → Nginx。但 Nginx 配置里有一堆安全规则(封 curl UA、封特定路径),公网进来的 /purge/ 请求被这些规则拦截,返回 403。

修法:用一个 mu-plugin 把 purge URL 改写为本地 127.0.0.1,同时强制设定 Host 头:

<?php
// /wp-content/mu-plugins/nginx-helper-local-purge.php

add_filter('rt_nginx_helper_fastcgi_purge_url_base', function($url) {
    return str_replace(home_url(), 'https://127.0.0.1', $url);
});

add_filter('http_request_args', function($args, $url) {
    if (strpos($url, '/purge/') !== false) {
        if (!isset($args['headers'])) $args['headers'] = [];
        $args['headers']['Host'] = parse_url(home_url(), PHP_URL_HOST);
        $args['sslverify'] = false;
    }
    return $args;
}, 10, 2);

两个 filter 做的事:

  1. rt_nginx_helper_fastcgi_purge_url_base — 把 https://site.com/purge/... 替换成 https://127.0.0.1/purge/...
  2. http_request_args — 强制设 Host 头为真实域名,否则 Nginx 收到 Host: 127.0.0.1,匹配不到正确的 server_name,走到 default server 去了

验证

完整链路测试:

php -r "
require '/www/wwwroot/site.com/wp-load.php';

// 预热缓存
$args = ['sslverify' => false, 'headers' => ['Host' => 'domain.com']];
wp_remote_get('https://127.0.0.1/', $args);
wp_remote_get('https://127.0.0.1/', $args);

// 检查状态
$r = wp_remote_get('https://127.0.0.1/', $args);
echo 'Before: ' . wp_remote_retrieve_header($r, 'nginx-cache') . PHP_EOL;
// → Before: HIT

// 执行 purge
wp_remote_get('https://127.0.0.1/purge/',
    ['sslverify' => false, 'headers' => ['Host' => 'domain.com']]);

// 检查清除结果
$r = wp_remote_get('https://127.0.0.1/', $args);
echo 'After:  ' . wp_remote_retrieve_header($r, 'nginx-cache') . PHP_EOL;
// → After: MISS

HIT → Purge → MISS,链路完整。

避坑速查

症状 原因 检查方法
curl purge 返回 403/404 purge location 缺失或位置不对 必须在 server{} 内,include enable-php 之前
文章更新了但缓存还在 purge_method 是 unlink_files 改成 get_request
purge 返回 200 但 cache 仍是 HIT 缓存 key 不匹配 确认用了硬编码 httpsGET
GP Elements 改了没反应 GP Elements 不触发 save_post Purge Entire Cache 按钮
无痕也没变化但 curl 已更新 Cloudflare edge 缓存 CF 面板 Purge Everything

新站复用

每次开新 WordPress 站,三步:

  1. Nginx 配置:在 vhost 里加 4 行 purge location → nginx -s reload
  2. 装插件:安装 Nginx Helper → 照着上面的表配置
  3. 放 mu-plugin:把 nginx-helper-local-purge.php 扔进 /wp-content/mu-plugins/

三分钟,不用写任何自定义代码。

相关阅读:Redis Object Cache 深度配置、Nginx Cache-Control 覆盖陷阱、VPS 跑分实测:磁盘 IO 差 55 倍。

© 2026 本文由 Charlie 原创撰写,发布于 sodebug.com。未经授权禁止转载、洗稿、机器抓取。AI 训练数据使用需获得书面授权。

独立开发者,在 WordPress、Nginx 和各种 API 之间切换。不写没用的东西。

相关文章

Redis Object Cache 深度配置:为什么改了数据前端不更新6 minNginx 双重 Cache-Control 覆盖陷阱:add_header 同名冲突与 Cloudflare 不缓存修复6 min两台 VPS 跑个分:磁盘 IO 差 55 倍的实际体验7 min使用宝塔面板配置 Nginx fastcgi_cache 优化 WordPress 网站性能9 min
$ echo "less bullshit, more debugging" | send-to-inbox
新文章直接发到邮箱,一个月 2-4 封。
订阅