用 map 指令动态控制 expires 缓存时长,核心是将按文件类型设缓存从 location 块解耦为统一键值映射,基于 $sent_http_content_type 变量正则匹配 mime 类型并赋值 $expires_time,再由 expires 指令执行;map 必须定义在 http 块顶层,支持大小写忽略、前缀匹配,推荐覆盖 text/html、image/、font/ 等常见类型,配合 add_header cache-control … always 可增强兼容性。

用 map 指令动态控制 expires 缓存时长,核心是把“按文件类型设缓存”这件事从一堆 location 块里解放出来,转为统一、可读、易维护的键值映射。它不直接发响应头,而是先算出一个时间值(比如 1y 或 -1),再交给 expires 指令去执行。
基于响应 Content-Type 动态设置最可靠
这是推荐首选方式,因为 $sent_http_content_type 是 Nginx 在后端响应已生成、Content-Type 头确定后才可用的变量,匹配结果稳定,不会受 URL 重写或路径别名干扰。
- map 必须定义在 http 块顶层,不能放在 server 或 location 内
- 正则匹配要加引号,比如 ~*^image/ 表示以 image/ 开头的 MIME 类型
- 记得覆盖常见类型:text/html(通常不缓存)、application/json(常禁用)、font/* 和 application/font-*(字体类资源建议设长缓存)
- 示例配置:
map $sent_http_content_type $expires_time {
default "-1";
~*^text/html "0";
~*^text/css "1M";
~*^application/javascript "1M";
~*^image/ "1y";
~*^font/ "1y";
~*^application/font- "1y";
}
配合 expires 指令在 location 中生效
map 算出变量后,expires 才能真正起作用。它必须出现在 server 或 location 块中,且优先级高于外层定义——也就是说,你可以在某个特定 location 里覆盖全局规则。
- 直接写 expires $expires_time; 即可,Nginx 会自动识别 "1y"、"1M"、"0"、"-1" 等格式
- 若想同时控制 Cache-Control,建议搭配 add_header Cache-Control … always,避免 expires 对非 2xx 响应不生效的问题
- 注意:如果后端没返回正确的 Content-Type(比如 PHP 脚本漏设 header),$sent_http_content_type 就为空,会落到 default 分支 —— 所以对关键类型(如 HTML)建议显式补一条空值匹配更稳妥
比硬编码 location 更适合复杂静态资源场景
当你的静态资源类型多(比如含 webp、avif、woff2、mjs、css.map)、规则细(如第三方 CDN 回源的资源要短缓存),用一堆 location ~* \.(xxx)$ 容易重复、难复用、改起来容易漏。
- map 把所有类型规则收拢到一处,增删改一目了然
- 支持正则分组、大小写忽略(~*)、前缀匹配(^),灵活性不输 location
- 变量名可自定义(如 $cache_ttl),后续还能被 add_header、proxy_cache_valid 等复用,扩展性更强
- 调试时可通过 log_format 打印 $expires_time,确认是否命中预期分支
注意几个实际踩坑点
不是所有变量都适合做 map 的输入源。比如用 $request_filename 匹配扩展名,看似直观,但若启用了 try_files 或 alias,文件路径可能被重写,导致匹配失效;而 $sent_http_content_type 是响应阶段最终确定的,更可信。
- map 不支持嵌套、不支持 if 逻辑,只做单层查表,别试图在里面写条件链
- 若需根据请求头(如 User-Agent)差异化缓存,应换用 $http_user_agent 作为源变量,但这时更适合配合 proxy_cache_valid,而非 expires
- 修改 map 后 reload nginx 即可生效,无需重启,但变量值是在请求进入时就计算好的,不存在运行时延迟










