CDN 缓存一直 MISS:一个 session_start 让整页缓存全废(2026 实操)

页面缓存插件装了,CDN 也开了,文章页还是每次都往源站跑。后台缓存设置翻了一遍没毛病,CDN 控制台规则显示命中,命中的结果永远是 MISS。

这种问题一般不在插件,也不在 CDN 规则,而在响应头里躺着的 no-store。浏览器和边缘节点看到这个词,规矩就是不存,你把 TTL 调到一个月也没用。查它的办法很简单,一条 curl。

前置条件

SSH 能登服务器、有 wp-content/mu-plugins 的写权限,站点装了任意一款页面缓存插件(本站是 Cache Enabler,WP Super Cache、LiteSpeed Cache 同理),CDN 后台能手动刷新缓存。主题文件不用动,官方版和付费版都不用改。

这套做法只适合以匿名访客为主的博客、资讯站、文档站。带购物车、会员中心、按 cookie 出不同价格的站,整页缓存本来就不该随便开,硬上会出更麻烦的事。

先分清是没存,还是存了没命中

绕过 CDN 直连源站,看 PHP 到底发了什么:

curl -sk --resolve www.你的站.com:443:127.0.0.1 -I https://www.你的站.com/

本站的真实输出:

HTTP/1.1 200 OK
Set-Cookie: PHPSESSID=7ua25n1pjhhf83i8d6tat095sb; path=/
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache

1981 年那个过期时间是条线索,跟 Pragma: no-cache 一起出现,来源基本只有一个,后面讲。

再走一遍边缘:

curl -sI https://www.你的站.com/
# cache-control: no-cache
# x-site-cache-status: MISS

x-site-cache-status 是阿里云 ESA 的头,Cloudflare 叫 cf-cache-status,EdgeOne 用 x-cache。字段名不一样,看的值一样:MISS 是次次回源,HIT 才算真缓存。同一台机器上静态资源返回 cache-control: max-age=43200,说明链路是通的,单卡在 HTML 这一层。

no-store 的四个来源

  • PHP 会话:session.cache_limiter 默认是 nocache,只要有一次 session_start(),PHP 就会补上 no-store、1981 的 Expires 和 Pragma
  • WordPress 的 nocache_headers():登录态、密码保护页、预览页会调用它,只发 Cache-Control,不带 1981 那两行
  • 主题或插件手写的 header():多见于「防缓存」的土办法,一行 header('Cache-Control: no-store') 能把整站打死
  • CDN 规则本身:HTML 被排在缓存规则之外,或规则顺序被后面的兜底绕过截走

前两个的区分方法很土但好使:有 1981 的 Expires 加 Pragma,是会话;只有 Cache-Control,去搜 nocache_headers。

找那个开 session 的地方

cd /www/wwwroot/你的站
grep -rn "session_start" --include=*.php wp-content/themes wp-content/plugins

本站命中三行,都在主题的一个文件里:

wp-content/themes/zibll/inc/functions/zib-theme.php:255:        @session_start();
wp-content/themes/zibll/inc/functions/zib-theme.php:297:        @session_start();
wp-content/themes/zibll/inc/functions/zib-theme.php:2033:        @session_start();

打开第 255 行看上下文,函数挂在 wp_loaded,条件是 _pz('replace_wp_login') && !is_user_logged_in()。也就是说每个未登录访客进来,服务器都会给他开一个 session 文件,顺手把 no-store 写进响应头。缓存插件再努力也白费,它拿到的是一个明确写着「不许存」的响应。

关掉 limiter,别改主题文件

改主题文件的下场是升级一次就没了,用 mu-plugin 挂在最前面:

<?php
// wp-content/mu-plugins/session-cache-headers.php
add_action('muplugins_loaded', function () {
    if (!is_admin() && !is_user_logged_in()) {
        ini_set('session.cache_limiter', '');
    }
});

cache_limiter 给空字符串,表示不发送任何会话缓存头,会话机制本身还在,主题存登录 key 不受影响。挂 muplugins_loaded 是为了赶在主题的 wp_loaded 之前;如果 grep 出来的调用位置更早,还得往前提。只对未登录访客生效这句别删,后台和登录态继续保持 no-store,否则迟早撞上缓存串号。

验证

我在同一台机器上放了个三行的 php 探针,只做 session_start() 然后输出一句话,测完就删。默认情况下头是 no-store, no-cache, must-revalidate 加 1981 的 Expires 加 Pragma;加了那行 ini_set 之后,三个头全部消失,Set-Cookie: PHPSESSID 还在。这说明问题就在 limiter,不在别处。

修完清一遍页面缓存,再让边缘回源一次,看三个地方:

  1. curl -sI https://你的站.com/ 连续两次,第二次的 x-site-cache-status 应该是 HIT
  2. 页面 HTML 末尾能搜到 cache enabler 的注释,说明本地缓存文件生成了
  3. 带登录 cookie 再请求一次,响应头必须还是 no-store

第 3 条最容易被跳过,串号事故全出在这儿。

几个坑

Set-Cookie: PHPSESSID 这行本身就会让一部分 CDN 拒绝缓存 HTML,头修干净了还是 MISS,就回头查这条,必要时让边缘忽略这个 cookie。

边缘 TTL 没到,源站清了也白清。插件只清本地文件,边缘要单独刷新,ESA 用 CLI 刷、Cloudflare 用 API 或后台,别指望插件顺手带上。

session 文件会一直堆在 /www/server/php/81/tmp/,用 ls sess_* | wc -l 数一下,路径按自己的环境来,清之前先确认没有在线用户。实在不想留,可以在 php.ini 里调 session.gc_maxlifetime,改完 reload php-fpm。

回滚最省事,删掉那个 mu-plugin 文件,头立刻回到原样,站点行为一点不变。

缓存这事的排法和上次查爬虫限速、配 Redis 对象缓存是一个路子:先看响应头,再动配置,别凭感觉调参数。页面缓存管的是 HTML,对象缓存管的是数据库查询(那篇在这里),两层都通了,1.9G 内存的小机器也能扛住突发流量。

© 版权声明
THE END
喜欢就支持一下吧
点赞14 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容