直接在Nginx层用fastcgi_cache缓存PHP动态响应,是解决博客类系统高并发最轻量有效的手段:它不依赖插件、不改PHP代码,将PHP生成的HTML缓存在本地磁盘,后续请求毫秒返回;核心在于精准缓存“逻辑动态、内容静态”的伪静态页(如WordPress文章页),并严格绕过登录态、参数页等不安全场景。

直接在 Nginx 层缓存 PHP 动态响应,是解决博客类系统高并发最轻量、最有效的手段之一。它不依赖插件、不改动 PHP 代码,把“生成好的 HTML”存在本地磁盘,下次请求直接返回,毫秒级响应。关键不是“能不能分动静”,而是“动态内容要不要缓、怎么缓才安全可靠”。
fastcgi_cache 不是替代动静分离,而是增强它
很多人误以为动静分离 = 把 .js/.css/.png 交给 Nginx 直接 serve,把 .php 甩给 PHP-FPM —— 这只是基础。真正的瓶颈常在“伪静态页面”:比如 WordPress 的 /2024/05/my-post/,URL 看似静态,背后仍是 PHP 查询数据库、执行模板、拼出 HTML。这类请求占博客流量大头,却反复消耗 PHP 和 MySQL。
fastcgi_cache 正是为这类“逻辑动态、内容静态”的页面而生。它把 FastCGI 响应(即 PHP 输出的完整 HTML)缓存下来,后续相同请求不再触达 PHP,Nginx 自己就发出去了。
- 缓存对象:PHP-FPM 返回的 HTTP 响应体 + 响应头(如 Content-Type、Last-Modified)
- 不缓存什么:Set-Cookie、X-Accel-Redirect、非 200/301/302 响应(除非显式配置)
- 和传统动静分离互补:静态资源走 root + expires;动态伪静态页走 fastcgi_cache;真正需登录/个性化的内容再绕过缓存
必须写对的四段核心配置
缓存不生效?90% 出在配置顺序或语义错误。以下代码需按位置严格放置:
① 在 http{} 块顶部定义缓存区(全局唯一,不可放 server 内):
fastcgi_cache_path /var/cache/nginx/fastcgi levels=1:2 keys_zone=WORDPRESS:256m inactive=1d max_size=2g;说明:
– /var/cache/nginx/fastcgi 必须提前创建,且属主为 nginx 运行用户(如 www-data)
– keys_zone=WORDPRESS:256m 表示内存中可存约 25 万条缓存 key,够中小博客用
– inactive=1d 指 1 天内没被访问过的缓存自动清理,比 expire 更实用
② 在 http{} 或 server{} 中设置缓存键与忽略策略:
fastcgi_cache_key "$scheme$request_method$host$request_uri";fastcgi_ignore_headers Cache-Control Expires Set-Cookie;
说明:
– 缓存键要排除会变的变量(如 $args 可能含 utm_source,导致重复缓存)
– 务必忽略 Set-Cookie,否则带登录态的首页可能被缓存并返回给其他用户(严重安全风险)
生产级WordPress REST API集成,用于管理文章、页面、媒体、WooCommerce产品、Elementor内容、SEO元数据、ACF及JetEngine字段
③ 在 location ~ \.php$ 块中启用缓存(重点!不能漏):
fastcgi_cache WORDPRESS;fastcgi_cache_valid 200 301 302 1h;
fastcgi_cache_use_stale error timeout updating http_500;
add_header X-Cache $upstream_cache_status;
说明:
– fastcgi_cache_valid 必须明确指定状态码,不能只写 “any 1h”
– updating 参数允许后台更新缓存时,仍把旧内容返回客户端(防雪崩)
– X-Cache 是调试神器,响应头里看到 HIT/MISS/EXPIRED 就知道哪步卡住了
④ 关键:绕过缓存的场景要主动控制
不是所有 PHP 请求都该缓存。以下情况需跳过:
- 含查询参数的 URL(如 ?s=xxx 搜索、?preview=true 预览)→ 用 if 判断并 set $skip_cache 1
- 已登录用户 → 检查 cookie 中是否有 wordpress_logged_in_ 字符串
- POST 请求 → 默认不缓存,但需确认没被 proxy_pass 或 rewrite 意外转成 GET
示例逻辑(放在 server 块中):
set $skip_cache 0;if ($request_method = POST) { set $skip_cache 1; }
if ($query_string != "") { set $skip_cache 1; }
if ($http_cookie ~* "wordpress_logged_in") { set $skip_cache 1; }
fastcgi_cache_bypass $skip_cache;
fastcgi_no_cache $skip_cache;
验证与调优要点
配完别急着压测,先做三件事:
- curl -I https://yoursite.com/any-post/ 看响应头是否有 X-Cache: HIT,且没有 Set-Cookie 或 Cache-Control: no-cache
- ls -lh /var/cache/nginx/fastcgi/ 看目录下是否生成了文件(注意权限,若为空,大概率是路径未创建或 nginx 用户无写入权)
- 打开 WordPress 后台,发一篇新文章,立刻访问其链接 → 应为 MISS;刷新一次 → 变为 HIT;等 1 小时后再次访问 → 若仍 HIT,说明 inactive 未生效,检查时间同步或配置位置
性能提示:
– 缓存路径建议放在 SSD 分区,避免机械盘 IO 成瓶颈
– max_size 设为磁盘可用空间的 70%,留余地防止写满导致 Nginx 报错
– 博客类站点,inactive=30m 比 1d 更稳妥,内容更新频繁时更及时
常见失效原因速查
如果始终是 MISS 或缓存目录空:
- 上游 PHP 脚本输出了 Cache-Control: no-cache 或 Expires: Thu, 01 Jan 1970 00:00:01 GMT → 必须在 Nginx 里用 fastcgi_ignore_headers 忽略,并用 add_header 覆盖(但注意:add_header 不作用于 304/4xx 响应,加 always 参数)
- location 匹配不到 .php 请求 → 检查是否用了 try_files @fallback 导致请求被重写,绕过了 fastcgi_pass
- fastcgi_pass 指向的是 127.0.0.1:9000,但 PHP-FPM 实际监听 /run/php/php-fpm.sock → 地址不匹配,请求失败,自然无法缓存
- SELinux 或 AppArmor 启用中,禁止 Nginx 写入缓存目录 → 临时 setenforce 0 测试,确认后调整策略

















