Nginx无法直接根据文件修改时间动态计算Expires,但可通过三种方式近似实现智能缓存:方法一用last_modified配合固定expires;方法二用map指令查预生成的过期时间表;方法三用OpenResty的Lua动态计算并缓存stat结果。

Nginx 本身不支持直接根据文件最后修改时间动态计算 Expires 头(比如 “当前时间 + 文件修改时间差” 这类逻辑),但可以通过组合 add_header、expires 指令与 Nginx 的内置变量(尤其是 $upstream_http_last_modified 和静态文件的 last_modified 行为),再配合合理的缓存策略,**近似实现“越新越久、越旧越短”的智能过期控制**。核心思路是:利用 Nginx 对静态文件自动设置 Last-Modified 的能力,再结合条件判断或外部工具辅助生成精确的 TTL。
方法一:启用 last_modified + 静态文件级固定 max-age(最简实用)
Nginx 默认对静态文件(如 .js、.css、.png)会自动添加 Last-Modified 响应头。虽然不能直接算 Expires,但可配合 expires 指令按“相对修改时间”设定一个保守的缓存窗口:
- 启用
last_modified on;(默认开启,确保未被关闭) - 对版本化资源(推荐带哈希后缀,如
app.a1b2c3.js)用长缓存:expires 1y; - 对无版本号的通用静态资源(如
logo.png),用较短缓存并依赖浏览器协商缓存:expires 1h;+add_header Cache-Control "public, must-revalidate";
这样虽非“自动计算”,但因文件更新必改名(哈希)或触发修改时间变更,浏览器在 max-age 过期后会发 If-Modified-Since 请求,Nginx 自动返回 304,效果接近智能缓存。
方法二:用 map + $date_gmt 实现“修改后 N 天过期”(需手动维护时间戳)
若你能在构建/部署时把文件修改时间写入一个映射表(如 JSON 或 Nginx map 文件),就能用 map 指令查出该文件应设的过期时间点:
- 生成一个
/etc/nginx/conf.d/file-expires.map:
default "Thu, 01 Jan 1970 00:00:00 GMT"; "/static/app.js" "Wed, 15 May 2025 10:30:00 GMT"; "/static/style.css" "Tue, 10 May 2025 09:15:00 GMT";
- 在 http 或 server 块中加载:
map $uri $expires_time {
include /etc/nginx/conf.d/file-expires.map;
}
server {
location ~ ^/static/ {
add_header Expires $expires_time;
add_header Cache-Control "public, max-age=0";
}
}
此方式需要构建脚本(如 shell/Python)读取文件 stat -c %y file.js 并生成 map,适合 CI/CD 流水线集成。
方法三:用 Lua(OpenResty)动态计算(最灵活,需额外组件)
如果你使用 OpenResty(Nginx + Lua),可在响应前读取文件元数据并动态设置 Expires:
- 安装 OpenResty,启用
lua_code_cache off;(开发期)或开(生产) - 在 location 中嵌入 Lua:
location ~ \.(js|css|png|jpg|gif)$ {
content_by_lua_block {
local file = ngx.var.document_root .. ngx.var.uri
local f = io.open(file, "r")
if f then
f:close()
local st = ngx.stat(file)
if st then
-- 计算:修改时间 + 7 天 → Expires
local expires_ts = st.mtim + 7 * 24 * 3600
local expires_gmt = ngx.http_time(expires_ts)
ngx.header.Expires = expires_gmt
ngx.header["Cache-Control"] = "public, max-age=" .. (7*24*3600)
end
end
ngx.exec("@fallback") -- 继续走默认静态处理
}
}
location @fallback {
try_files $uri =404;
}
注意:频繁 stat 文件有性能开销,建议搭配 lua_shared_dict 缓存 stat 结果(例如按文件路径缓存 5 分钟)。
关键提醒与替代建议
- 不要依赖 $time_iso8601 或 $date_gmt 直接算“修改后多久” —— 它们只提供当前时间,无法获取文件修改时间
- 优先用内容哈希(如 Webpack 的 [contenthash])+ long-term caching:这是现代前端最可靠、零服务端计算的方案
- 对非哈希资源,用 ETag + Last-Modified 协商缓存:Nginx 默认开启,无需配置,浏览器自动处理 304
- 如果必须动态 Expires,方法二(map)比方法三(Lua)更轻量、更稳定,适合大多数运维场景
不复杂但容易忽略:真正影响用户体验的是缓存命中率和更新及时性,而不是 Expires 字面是否“自动计算”。用好版本化 + 协商缓存,往往比动态算时间更健壮。










