在反向代理架构中,Nginx常常承担缓存中间层角色。当客户端请求静态资源时,Nginx先检查本地proxy_cache是否命中,未命中才向后端上游服务器回源。回源得到的响应头部里通常包含Expires和Cache-Control,这两个字段直接决定了该响应能在代理层保留多久。如果Nginx没有正确读取并利用这些过期时间,就会在很短的周期内反复回源,既浪费CPU也占用后端带宽。

理解回源过期时间的第一个要点是HTTP协议本身的优先级。按照规范,如果响应同时带有Cache-Control的max-age指令和Expires,Cache-Control具有更高优先级。Nginx在解析上游响应时,也是先尝试从Cache-Control提取max-age,若缺失再退回到Expires绝对时间。很多开发者误以为只要后端发了Expires就万事大吉,但当框架默认追加了Cache-Control: no-cache时,Nginx会立刻判定为不可缓存,导致回源无法被复用。
另一个容易忽略的点是时区与时钟同步。Expires使用的是HTTP-date绝对时间,依赖代理机和源站系统时钟一致。若两台机器存在明显偏移,Nginx计算剩余生存时间(TTL)就会出现负数或异常放大,表现为缓存过早失效或迟迟不回源。因此运维上必须保证NTP时间同步,同时在Nginx侧通过指令显式控制,而不是完全信任上游头。
proxy_cache_valid与上游头部的优先级冲突
Nginx提供了proxy_cache_valid指令,允许运维在不依赖后端头部的情况下,按响应状态码设定缓存时长。例如proxy_cache_valid 200 302 10m;表示对200和302响应强制缓存十分钟。这条指令的优先级高于Expires和Cache-Control中的过期设定吗?答案是否定的。实际逻辑是:当上游响应明确携带可识别的Cache-Control或Expires,Nginx以它们计算TTL;只有当这些头部缺失或无效时,proxy_cache_valid才作为兜底生效。
这种机制带来一个常见坑:后端某次发布删掉了Cache-Control,只留了Expires,而Expires时间设为早已过去的时间点。Nginx解析后发现过期,便不缓存,即便你配置了proxy_cache_valid 200 1h也无法补救,因为Expires被判定为有效但已过期。要规避这类问题,可以使用proxy_ignore_headers主动忽略上游的Expires,让兜底规则接管。
下面这段配置展示了如何忽略Expires并依赖本地有效期:
location /static/ {
proxy_pass http://backend;
proxy_cache mycache;
# 忽略上游Expires和Set-Cookie,避免干扰缓存
proxy_ignore_headers Expires Set-Cookie;
# 状态码200和304缓存一小时
proxy_cache_valid 200 304 1h;
proxy_cache_key $scheme$proxy_host$request_uri;
}
上述配置中,即便后端返回Expires: Thu, 01 Jan 1970 00:00:00 GMT,Nginx也会因为忽略该头部而采用proxy_cache_valid定义的一小时。这在迁移老系统或对接不规范接口时非常实用,但也要注意不能盲目忽略Cache-Control,否则可能缓存本应私有的用户数据。
回源时Expires的透传与重写策略
除了决定自身缓存行为,Nginx还可控制向下游客户端透传哪些头部。默认情况下,proxy模块会把上游的Expires原样发给浏览器。若你已在Nginx侧忽略了上游Expires并重写了缓存规则,却不修改发给客户端的值,就会造成浏览器端过期时间与代理层不一致,引发客户端频繁直连Nginx,看似命中实则重复校验。
通过proxy_hide_header与add_header组合,可以重写客户端看到的过期信息。例如先隐藏上游Expires,再用expires指令按相对时间生成新的绝对时间。这样保证边缘节点与终端对新鲜度判断统一,减少不必要的条件请求。
示例代码如下:
location /assets/ {
proxy_pass http://backend;
proxy_cache mycache;
proxy_ignore_headers Expires;
proxy_hide_header Expires;
# 向客户端响应添加一小时过期
expires 1h;
proxy_cache_valid 200 1h;
}
这里expires 1h是Nginx自身指令,会生成Cache-Control: max-age=3600和对应的Expires头部下发给浏览器。由于上游Expires被隐藏,客户端只会看到代理生成的策略,二者不会冲突。对于混合云场景,若源站经常变动头部格式,这种重写方式能显著降低排错成本。
利用变量与map实现精细化过期控制
当不同URI需要不同过期时间时,写多个location块会显得笨重。Nginx的map指令配合变量,可以基于请求路径或上游头部动态计算缓存时长。比如对图片宽松缓存一天,对API响应仅缓存十秒,都可通过变量赋值给proxy_cache_valid引用的自定义变量来实现。
需要注意的是,proxy_cache_valid不能直接使用复杂变量做状态码映射,但可以用变量控制时间值。结合proxy_cache_bypass与proxy_no_cache,还能根据Cookie或参数跳过缓存,避免把带Expires的公开资源错误地用于私密请求。
map $request_uri $cache_ttl {
default 10m;
~*.(jpg|png)$ 1d;
~*/api/ 5s;
}
server {
location / {
proxy_pass http://backend;
proxy_cache mycache;
proxy_ignore_headers Expires;
proxy_cache_valid 200 $cache_ttl;
}
}
该配置利用map把不同资源类型映射到$cache_ttl变量,再传给proxy_cache_valid。由于忽略了上游Expires,所有过期判断收归Nginx统一治理。实践中建议配合proxy_cache_lock使用,防止回源风暴:当多个请求同时未命中,只放一个去后端,其余等待,进一步提升效率。
总体来看,Nginx回源中的Expires处理并非简单透传,而是涉及协议优先级、指令覆盖、头部重写与变量控制的多层逻辑。理清这些关系,才能构建稳定高效的缓存层。
Nginxproxy_cacheExpires修改时间:2026-08-14 06:39:31