应在Nginx中用map归一化User-Agent为$os_type,再通过proxy_cache_key包含该变量并添加Vary "X-OS-Type"响应头,确保CDN按操作系统类型正确分离缓存。

直接在 add_header Vary 里写 User-Agent 是错的——它会让 CDN 缓存爆炸式碎片化,且多数 CDN 对长 UA 的 Vary 支持不可靠。
用 map 把 UA 归类为稳定枚举值
原始 $http_user_agent 太杂、太长、太易变,不能直接进缓存键或响应头。必须先做归一化处理,在 http 块中定义:
map $http_user_agent $os_type {
default other;
~*[Ii]phone ios;
~*iPad.*OS ios;
~*iOS ios;
~*[Aa]ndroid android;
}
注意点:
- 正则必须加
~*表示忽略大小写匹配 -
default other不可省略,否则未匹配 UA 会导致变量为空,后续缓存键出错 - 顺序重要:更具体的规则(如
iPhone)放前面,避免被宽泛规则(如Android)误覆盖 - 不推荐用
iPhone.*OS这类跨词匹配,部分 UA 中间有空格或版本号,容易漏判
只在内容真有差异时才加 Vary: X-OS-Type
Vary 不是“设备识别开关”,而是“缓存隔离契约”:你承诺了不同 X-OS-Type 值返回的内容确实不同,否则就是误导缓存系统。
- HTML 模板渲染了 WebKit-only 样式或 iOS 17 特性字段 → 必须加
add_header Vary "X-OS-Type" - API 接口对 Android 返回额外的
android_intent字段 → 同样要加 - 静态 JS/CSS/图片未做任何 OS 分支逻辑 → 绝对不要加,否则缓存命中率断崖下跌
- 响应头名用
X-OS-Type而非User-Agent:语义清晰,且绕过某些 CDN 对原始 UA 的解析 bug
同步更新 proxy_cache_key 和响应头
只加 Vary 头但不改缓存键,等于白干——Nginx 仍会把 iOS 和 Android 请求塞进同一个缓存条目。
- 在对应
location块中显式配置:proxy_cache_key "$scheme$request_method$host$request_uri$os_type" - 同时输出标识头:
add_header X-OS-Type $os_type(便于调试和 CDN 透传) - 若还区分设备类型(mobile/tablet),可组合:
proxy_cache_key "$scheme$request_method$host$request_uri$os_type$device_type",并设Vary "X-OS-Type, X-Device-Type" - 确认 CDN 控制台已开启
Vary解析(Cloudflare 默认开;腾讯云需单独勾选“支持 Vary 头”)
验证是否真正生效
别信配置,要信 curl 输出。两个请求必须同时满足三点才算成功:
- 响应头含
Vary: X-OS-Type,且X-OS-Type值分别为ios和android - 两次请求的
Content-Length或ETag不同(说明后端真返回了不同内容) - 第二次相同 UA 请求命中缓存(
X-Cache: HIT或X-CF-Cache-Status: HIT),而跨系统请求是MISS
最容易被忽略的是第三点:很多人只验证了头是否发出,没验证缓存是否真的按 $os_type 分离——一旦漏掉 proxy_cache_key 更新,所有努力都卡在最后一公里。











