Nginx缓存与浏览器缓存协同:expires、Cache-Control与强刷不生效的原因

    发布时间:2026-09-20 05:01 更新时间:2026-09-20 05:01 阅读量:0

    很多站长在宝塔或自建 Nginx 上给静态资源加了 expires 之后,会遇到两种相反的困惑:一种是明明改了 CSS 和 JS,自己按 Ctrl+F5 强刷能看到新样式,访客却还在用旧版;另一种是强刷之后浏览器依然不发请求,直接拿本地缓存,让人以为 Nginx 配置没生效。这两个现象背后其实是同一件事:Nginx 只负责在响应头里下命令,真正决定用不用缓存的是浏览器。把 expires、Cache-Control 以及强刷的规则理清楚,缓存才可控。

    expires 与 Cache-Control 到底是什么关系

    在 Nginx 里写 expires 30d;,它实际上会做两件事:一是生成 Expires 响应头,值是一个绝对时间点;二是生成 Cache-Control: max-age=2592000 这样的相对秒数。相对时间不受客户端本地时钟影响,所以现代浏览器基本以 Cache-Control 为准,Expires 更多是给老客户端兜底。

    也就是说,你以为自己只配了一个 expires,实际上缓存策略已经通过 Cache-Control 下达了。反过来说,如果你在 Nginx 里又用 add_header Cache-Control ... 手动加了一条,就可能出现两个 Cache-Control 头,浏览器行为会变得不可预期。排查缓存问题时,第一件事是看响应头里到底有几条缓存指令。

    curl -I -H "Host: www.example.com" https://www.example.com/static/app.css
    

    关注返回头中的 Expires、Cache-Control、ETag、Last-Modified 四行

    如果返回头里出现两行 Cache-Control,就要检查站点配置和宝塔面板里是否重复添加了缓存规则,以实际配置文件和面板设置为准。

    强刷为什么不生效:三类常见原因

    第一类是强刷作用的范围有限。Ctrl+F5 或 Cmd+Shift+R 通常只对当前页面文档做强制校验,页面内通过 link、script 引入的子资源不一定会被一并重新请求。所以会出现 HTML 更新了、CSS 还是旧的这种“半新半旧”状态。这种情况下,改文件名或者加版本号查询串才是最稳的办法。

    第二类是资源被中间层缓存了。如果站点前面接了 CDN,浏览器强刷只能绕过浏览器本地缓存,绕不过 CDN 边缘节点。需要在 CDN 后台刷新对应 URL 或目录,具体刷新方式和生效时间以各家 CDN 官方文档为准。反过来,如果 CDN 回源时又拿到 Nginx 设置的长期缓存头,节点也会把旧文件长期留住。

    第三类是 Service Worker 或前端构建缓存。部分主题、PWA 插件会注册 Service Worker 接管请求,此时浏览器缓存面板里能看到缓存来源是 Service Worker 而不是 HTTP 缓存,清理方式也不同。遇到“怎么刷都不变”的站点,可以先在开发者工具的 Application 面板里注销 Service Worker 再验证。

    另外要提醒一句:强刷不是万能的测试手段。判断缓存是否按预期工作,应该看响应头和请求状态码,而不是只看页面长相。

    一套可落地的静态资源与 HTML 分开缓存写法

    思路很简单:带指纹的静态资源长缓存,HTML 不缓存或短缓存。这样文件内容变了文件名也变,用户自然拿到新版;入口 HTML 每次都回源校验,保证能引用到新文件名。下面是一段可以直接放进 server 块的示例,路径和匹配规则要按自己站点结构调整。

    # 带指纹的静态资源,长缓存
    location ~* \.(css|js|png|jpg|jpeg|gif|webp|svg|woff2?)$ {
        expires 30d;
        add_header Cache-Control "public, max-age=2592000, immutable";
        access_log off;
    }
    
    

    HTML 不缓存,每次回源校验

    location ~* \.(html?)$ { expires -1; add_header Cache-Control "no-cache"; }

    说明几点:immutable 告诉浏览器在有效期内连校验都省掉,只适合文件名带 hash 的资源;expires -1; 会输出 Cache-Control: no-cache,注意 no-cache 不是不缓存,而是每次都要向服务器确认,配合 ETag 或 Last-Modified 返回 304 时几乎不耗流量。no-store 才是完全不落盘,一般只用于含敏感信息的接口响应。

    改完配置后先做语法检查再平滑重载,避免直接重启影响在线访客,具体命令以服务器实际安装路径为准:

    nginx -t
    nginx -s reload
    

    验证时用 curl 看头部,再在浏览器开发者工具的 Network 面板里看 Size 一列:显示 “from disk cache” 或 “from memory cache” 说明命中了本地缓存;显示 304 说明走了协商缓存;显示 200 且带完整响应体说明是重新下载。三种状态对应三种不同的配置效果,比反复强刷直观得多。

    最后给一个日常操作建议:每次发布前端改动,优先改文件名或用构建工具生成 hash,而不是指望用户强刷;HTML 保持短缓存或不缓存,接口里的用户相关数据用 no-store,图片、字体、CSS、JS 这类不变内容才放心长缓存。缓存策略定好后写进站点配置并做一次 curl 回归,后面换服务器、接 CDN 时照着同一套头检查,能省掉大量“用户说没更新”的沟通成本。涉及 CDN、浏览器版本差异导致的细节表现,以官方文档和实际环境验证结果为准。

    继续阅读

    📑 📅
    WordPress后台上传图片报错:权限、临时目录与尺寸限制逐项定位 2026-09-20
    robots.txt写错导致整站被屏蔽:误写排查与sitemap配合 2026-09-20
    宝塔面板FTP连不上:Pure-FTPd被动端口与权限排查 2026-09-20
    WordPress邮件发不出去:wp_mail走SMTP还是sendmail与SPF/DKIM检查 2026-09-20
    宝塔面板SSL证书部署后HTTP/2没生效:协议开启、ALPN检查与Nginx版本差异 2026-09-19
    Nginx try_files 到底怎么走:root/alias 差异与伪静态失效排查 2026-09-21
    宝塔面板 open_basedir、禁用函数与上传目录协同配置 2026-09-21
    WordPress第三方资源拖慢首屏:定位与本地化处理 2026-09-21
    建站前期最容易踩的域名坑:泛解析、www并存与CNAME冲突 2026-09-21
    WordPress换主题后排版错乱:数据迁移避坑指南 2026-09-21