接入CDN是提升网站访问速度的常见手段,但不少运维人员在配置完Nginx后发现,CDN命中率远低于预期,日志里出现大量回源请求。排查时经常发现一个细节:响应头里的Cache-Control要么丢失,要么出现了重复字段,CDN节点根本无法正确解析缓存时间。要彻底解决这个问题,需要同时理解Nginx的响应头处理机制和CDN的回源判断逻辑,下面结合日志分析和配置实践详细展开。

一、先从Nginx日志入手,确认回源是否真的异常
判断CDN缓存是否生效,最直接的办法是查看Nginx访问日志中的请求特征。CDN回源请求的User-Agent通常带有特定标识,例如阿里云CDN会带上
一个实用的日志配置示例如下,它把回源判断的关键字段全部输出,方便后续用awk或日志平台统计回源比例:
log_format cdn_log '$remote_addr - $upstream_cache_status [$time_local] '
'"$request" $status $body_bytes_sent '
'"cache-control:$sent_http_cache_control" '
'"x-cache:$sent_http_x_cache" '
'rt=$request_time urt=$upstream_response_time';
server {
listen 80;
server_name www.ipipp.com;
access_log /var/log/nginx/cdn_origin.log cdn_log;
location /static/ {
root /data/www;
expires 30d;
add_header Cache-Control "public, max-age=2592000";
# 注意:expires本身也会生成Cache-Control,两者叠加容易出问题
}
}
统计回源比例时,可以利用CDN回源请求与普通用户请求在请求头上的差异做过滤。例如统计携带了CDN特征头的那部分请求量占总请求的比例,如果静态资源的回源占比长期高于百分之五,基本可以判定缓存策略没有生效。同时观察日志中$sent_http_cache_control字段,如果发现它的值为空,说明Nginx根本没有下发这个响应头,CDN自然不会缓存;如果值中出现了类似max-age=2592000, max-age=3600的重复内容,说明有多处配置在同时生成该头部,CDN解析时可能取错值。
二、Cache-Control失效的三个常见原因
1. expires与add_header叠加产生重复头
Nginx的expires指令会同时生成Expires和Cache-Control两个字段,如果再手动用add_header追加一条Cache-Control,最终响应里就会出现两个同名头部。部分CDN对重复头部的处理策略是取第一个或者直接判定为无效,导致缓存时间与预期不符。正确的做法是二选一,推荐显式使用add_header,这样语义更清晰,控制粒度也更细。
2. add_header的继承规则陷阱
add_header有一个容易被忽视的特性:只有当当前层级没有定义任何add_header时,才会继承上层级的配置。如果在server块里定义了一个通用的Cache-Control,又在某个location里用add_header添加了别的内容,那么server层的那条配置会整体失效,这个location的响应里就没有Cache-Control了。排查时要逐层检查配置文件,确认没有这种覆盖情况。
3. 源站下发了互相矛盾的缓存指令
当Nginx作为反向代理时,后端应用返回的头部与Nginx配置的头部可能同时存在。例如后端返回了Cache-Control: no-cache,Nginx又追加了一条max-age=3600,CDN看到no-cache就直接回源了。这种情况可以用proxy_hide_header把后端的干扰头部隐藏掉,再统一由Nginx下发规范的缓存策略:
location /api/ {
proxy_pass http://backend;
# 隐藏后端返回的缓存相关头部,避免与本地策略冲突
proxy_hide_header Cache-Control;
proxy_hide_header Expires;
proxy_hide_header Pragma;
# 动态接口禁用CDN缓存
add_header Cache-Control "no-store, private" always;
}
location ~* \.(jpg|png|css|js|woff2)$ {
proxy_pass http://backend;
proxy_hide_header Cache-Control;
proxy_hide_header Expires;
# 静态资源缓存30天,配合文件名哈希实现长缓存
add_header Cache-Control "public, max-age=2592000, immutable" always;
}
注意always参数的含义:默认情况下add_header只在响应码为200、201等成功状态时生效,加上always后,404、302等状态码也会带上该头部。对于CDN场景,404页面的缓存策略同样重要,需要根据业务决定是短暂缓存还是完全不缓存。
三、搭建一套完整的缓存分层策略
CDN缓存优化的核心思想是分层控制:浏览器、CDN节点、源站各自持有不同时长的缓存。常用方案是利用s-maxage控制CDN侧缓存时间,用max-age控制浏览器缓存时间,两者可以不同。例如对HTML页面,可以让CDN缓存五分钟以便快速更新,同时告诉浏览器每次都协商缓存:
server {
listen 80;
server_name www.ipipp.com;
# 全局默认:CDN缓存10分钟,浏览器不缓存
add_header Cache-Control "public, s-maxage=600, max-age=0, must-revalidate" always;
# 带指纹的静态资源:浏览器与CDN都长期缓存
location /assets/ {
add_header Cache-Control "public, max-age=31536000, immutable" always;
}
# 用户个性化接口:禁止任何缓存
location /user/ {
add_header Cache-Control "private, no-store" always;
}
# 管理后台路径:直接绕过场景不涉及,保持默认
location /admin/ {
add_header Cache-Control "private, no-cache" always;
}
}
这套配置的关键点在于immutable的运用。只要静态资源的文件名中包含内容哈希,资源内容变化时文件名必然改变,浏览器就可以放心地缓存一年而无需协商,这能显著减少条件请求。而HTML等入口文件则采用短缓存加协商验证的方式,保证更新及时性。
最后回到日志监控,上线新策略后应持续观察两个指标:一是Nginx日志中CDN回源请求的占比变化,二是$sent_http_cache_control字段是否始终为预期的单一值。可以将日志接入分析平台,按路径分组统计命中率,一旦某个路径的回源比例突增,就能快速定位是哪条配置或哪个后端服务引入了问题。缓存优化不是一次性工作,配合日志持续验证,才能让CDN真正发挥降本提速的作用。
Nginx日志CDN回源Cache-Control修改时间:2026-09-02 01:02:35