IT

Nginx 静态资源缓存配置:expires 与 Cache-Control 实操指南

AI智能摘要

Nginx 里 expires 和 Cache-Control 到底怎么配才不打架?这篇按文件类型给出可直接抄的 location 写法,附 map 集中管理版本号的进阶方案,还整理了 add_header 覆盖、immutable 误用、CDN 二次缓存这几个常见坑,最后用 curl 验证响应头是否生效。

先搞清 expires 和 Cache-Control 的关系

结论先行:expires 是 Nginx 的便捷指令,它最终会生成 Expires 和 Cache-Control: max-age 两个响应头;而 add_header Cache-Control 是手动直接写头。两者同时出现时,浏览器以 Cache-Control 为准,Expires 只是给老客户端的兜底。所以静态资源缓存的核心就是一条:给不同后缀的文件设置合理的 max-age。

基础配置:按文件类型分别设过期时间

在 server 块里用 location 匹配后缀,是最常用也最稳的做法:

server {
    listen 80;
    server_name example.com;
    root /var/www/html;

    # 图片、字体、媒体:内容基本不变,缓存一年
    location ~* .(gif|jpg|jpeg|png|webp|svg|ico|woff2?|ttf|eot)$ {
        expires 365d;
        add_header Cache-Control "public, immutable";
        access_log off;
    }

    # CSS/JS:用文件名哈希做版本控制,同样缓存一年
    location ~* .(css|js)$ {
        expires 365d;
        add_header Cache-Control "public, immutable";
    }

    # HTML 不缓存,保证入口页及时更新
    location ~* .(html|htm)$ {
        expires -1;
        add_header Cache-Control "no-cache, no-store, must-revalidate";
    }
}

注意:expires -1 会生成 Cache-Control: no-cache,表示“可缓存但每次要回源校验”。如果你希望 HTML 完全不落盘,用上面 add_header 里的 no-store。

用 map 做统一版本号(推荐进阶写法)

如果不想在每个 location 里重复写时间,可以用 map 按后缀集中管理,改一处即可全站生效:

http {
    map $sent_http_content_type $cache_time {
        default                     off;
        ~image/                     365d;
        ~font/                      365d;
        ~text/css                   365d;
        ~application/javascript     365d;
        ~text/html                  -1;
    }

    server {
        listen 80;
        server_name example.com;
        root /var/www/html;

        location / {
            expires $cache_time;
        }
    }
}

这里用 $sent_http_content_type 匹配 MIME 类型,比裸后缀更准确,但要注意它依赖上游返回的 Content-Type,纯静态文件由 Nginx 自己根据 mime.types 判断,通常没问题。

验证与常见坑

配置改完先测试再重载:

nginx -t
nginx -s reload

用 curl 看响应头,确认 Cache-Control 和 Expires 是否符合预期:

curl -I https://example.com/static/app.css

几个容易踩的坑:

  • add_header 会覆盖父级:如果在 server 块写了 add_header,子 location 里再写会替换掉父级那一组,不是叠加。建议把缓存头只放在需要的 location 里。
  • 带哈希的文件名才敢用 immutable:immutable 表示浏览器在过期前绝不发校验请求。如果文件名固定(如 style.css),千万别加,否则用户永远拿不到新版本。
  • CDN 会再缓存一层:源站设了 max-age=31536000,CDN 节点也照此缓存。更新文件时要么改文件名,要么去 CDN 控制台刷新,光 reload Nginx 没用。
  • expires 与 add_header 同时写 Cache-Control 会冲突:Nginx 可能输出两个 Cache-Control 头,浏览器行为不可预测。二选一,或者用 expires 生成基础值、不再手动加 Cache-Control。

最后给个实用建议:CSS/JS 走构建工具生成带哈希的文件名,配 expires 365d; add_header Cache-Control "public, immutable";;HTML 走 no-cache 或很短的 max-age。这样既最大化利用缓存,又能让每次发版立即生效。

发表评论