直接用$http_user_agent做Vary会导致缓存爆炸,应通过map指令将UA归类为有限稳定类别(如ios、android),再以该变量值参与Vary和proxy_cache_key构建,并用add_header Vary "Device-Type" always;确保所有响应携带。

为什么直接用 $http_user_agent 做 Vary 会出问题
直接写 add_header Vary User-Agent; 看似合理,但实际会导致缓存爆炸:每个手机型号、浏览器版本、甚至小版本更新(如 Chrome/124.0.6367 → 124.0.6368)都生成独立缓存条目。CDN 或 Nginx 自身缓存可能在几小时内积压数万碎片,命中率骤降,反而拖慢响应。
用 map 指令做机型归类再 Vary
必须把千差万别的 User-Agent 映射为有限、稳定、语义明确的类别值,再基于该值 Vary。Nginx 的 map 指令是唯一可控方式:
- 在
http块顶层定义映射,避免重复编译正则 - 匹配顺序很重要:把高优先级规则(如 iOS、Android)放前面,兜底用
default "desktop"; - 值必须固定且无空格,否则
proxy_cache_key构建失败
示例配置:
map $http_user_agent $device_type {
"~*iPhone|iPad|iPod" "ios";
"~*Android" "android";
"~*Windows Phone" "wp";
"~*Firefox.*Mobile" "firefox_mobile";
default "desktop";
}
然后在对应 location 中使用:
add_header Vary "Device-Type"; proxy_cache_key "$scheme$request_method$host$request_uri$http_origin$device_type";
Vary 值必须和 proxy_cache_key 中变量严格对齐
只加 add_header Vary Device-Type 不够,客户端和 CDN 需要明确知道“这个响应依赖于 Device-Type 的值”。同时,Nginx 缓存键里也必须包含 $device_type,否则缓存条目不隔离——安卓用户可能拿到 iOS 缓存的 HTML。
-
Vary头里的字段名(如Device-Type)是给下游看的,不是变量名 -
proxy_cache_key里必须用实际变量(如$device_type),不能写成$http_device_type(不存在) - 若同时 Vary 多个头(如
Origin,Device-Type),proxy_cache_key必须包含全部对应变量
移动端资源缓存需绕过 add_header 的状态码限制
add_header 默认只对 200/301/302 等成功响应生效,而移动端常返回 304(Not Modified)或 404(静态资源缺失)。这时 Vary 头不会被添加,导致缓存行为不一致。
- 加上
always参数强制输出:add_header Vary "Device-Type" always; - 注意:
always会让所有响应(包括错误页)都带该头,需确认业务可接受 - 更稳妥的做法是限定 location 范围,只对真实资源路径(如
/api/、/static/)启用
真正难的是保持映射规则长期稳定——一旦改了 $device_type 的值(比如把 "ios" 改成 "apple"),旧缓存全失效,CDN 回源洪峰可能打垮后端。上线前务必用真实 UA 字符串批量测试映射结果。


















