最简单可靠的方式是用 location 块 + expires 指令按后缀匹配,但需注意 expires 与 add_header Cache-Control 的优先级关系,后者会完全覆盖前者;应拆分为多个独立 location 块分别处理不同后缀,并统一用 add_header 精确控制 Cache-Control,避免混用导致覆盖。

直接结论:用 location 块 + expires 指令按后缀匹配是最简单、最可靠的方式,但必须注意 expires 和 add_header Cache-Control 的优先级关系,否则缓存头可能被意外覆盖。
按文件后缀写多个 location 块是最稳妥的做法
不要试图在一个 location 里用正则穷举所有类型再分支判断——Nginx 不支持 if-else,硬套 if 会引入不可靠行为。正确方式是拆成独立的 location 块,让匹配更清晰、执行更确定:
-
location ~* \.(js|css|woff2|ttf|eot)$→ 长期缓存(1年),加immutable -
location ~* \.(png|jpg|jpeg|gif|svg|ico)$→ 中期缓存(1周) -
location ~* \.(html|htm|php|json)$→ 禁用浏览器缓存(expires -1)
每个块内只做一件事:匹配、设 expires、补 Cache-Control。顺序无关,Nginx 会按最长匹配优先选择 location。
expires 和 add_header Cache-Control 别混用且不加协调
expires 会自动设置 Expires 头和基础 Cache-Control: max-age=...,但它不生成 immutable 或 public 等语义字段;而 add_header 是完全覆盖式的——只要写了,就会把整条 Cache-Control 替换掉,不管 expires 是否已生效。
- 想用
immutable?必须显式写add_header Cache-Control "public, immutable" - 只写
expires 1y,不写add_header→ 浏览器收到max-age=31536000,但没immutable,仍可能发条件请求 - 同时写了
expires 1y和add_header Cache-Control "no-cache"→ 后者生效,前者被忽略
所以推荐统一用 add_header 控制 Cache-Control,expires 仅作辅助(比如兼容老客户端),或干脆不用。
开发环境临时禁用缓存别依赖 if
有人想通过 if ($args ~ v=dev) { expires -1; } 动态降级,这在大多数 Nginx 版本中是危险的:if 在 location 内部行为不稳定,容易导致缓存头丢失或配置错乱。
- 更安全的做法:用不同 server 块或不同配置文件区分 dev/prod
- 或者全局加一句
add_header Cache-Control "no-store, must-revalidate",仅用于本地调试 - 真要参数控制,改用
proxy_cache_bypass(仅限反向代理场景),而不是动响应头
记住:expires 是为“确定不变”的资源设计的,动态开关不是它的使用场景。
验证缓存头是否生效得看真实响应,别信配置文件顺序
改完配置 reload 后,用 curl -I 直接请求对应后缀的资源,例如:
curl -I https://example.com/app.abc123.js
检查返回头中是否有:
Cache-Control: public, max-age=31536000, immutable-
Expires时间是否比当前时间多一年
如果看到 Cache-Control: max-age=0 或缺失 immutable,大概率是某个更高优先级的 location 或 server 块里的 add_header 覆盖了你写的那条。Nginx 的头继承规则比想象中更隐蔽,尤其是当 root、alias、try_files 等指令混用时。











