Last-Modified — HTTP修改日期头的实质、机制与配置

作者: IT Sectr 发布日期: 2026-03-09 阅读时间: 9 分钟

Last-Modified — 是一种HTTP响应头,用于指示服务器上资源的最后一次修改日期和时间,允许客户端通过If-Modified-Since执行条件请求。如果资源自指定日期以来未发生变化,服务器将返回304 Not Modified,不传输响应主体,从而显著节省带宽。根据RFC 7232 (IETF, 2014),使用Last-Modified的条件请求可将重复访问时的页面加载时间减少30-60%。该头部被大多数HTTP服务器和代理服务器自动支持。

重点

  • Last-Modified — 用于条件请求If-Modified-Since的HTTP头,包含资源最后一次修改日期
  • 304 Not Modified — 如果资源未变化则服务器返回此响应;客户端使用其缓存副本
  • 精确度至秒级 — 头部的局限性:一秒内的变化可能被忽略
  • 与ETag协同工作 — 服务器返回两个头部,客户端发送两个条件请求
  • 自动生成 — Nginx和Apache为静态文件从文件系统设置Last-Modified

什么是Last-Modified?

Last-Modified — 属于HTTP条件请求头部组。服务器将其添加到GET或HEAD响应中,以HTTP-date格式指示所请求资源的最后一次修改日期和时间:Last-Modified: Wed, 02 Jul 2025 14:30:00 GMT。客户端(浏览器、移动应用、代理服务器)将此日期与缓存的资源一同保存。再次请求时,客户端发送带有相同日期的If-Modified-Since头部,服务器将其与资源的当前修改时间进行比较。

使用Last-Modified的条件请求协议由RFC 7232定义,得到所有现代HTTP服务器的支持。日期格式有严格规定 — 仅使用GMT(格林尼治平时间),不带时区指示。服务器必须以三种可能格式之一返回日期:RFC 1123(标准)、RFC 850(过时)或ANSI C asctime。实际上,几乎所有服务器都使用RFC 1123格式,固定长度29个字符。

Last-Modified属于验证机制缓存类别:它不告诉客户端能否缓存响应,而是提供一种检查已缓存资源是否仍有效的工具。缓存策略通过Cache-Control头部单独设置。根据Akamai (2025)的研究,正确配置Last-Modified与Cache-Control可将静态内容的源服务器负荷降低达70%。

Last-Modified的由来

Last-Modified头部早在HTTP/1.0(RFC 1945, 1996)中就已定义,成为互联网上最早的缓存管理机制之一。在HTTP/1.1中Etag出现之前,它是执行条件请求的唯一方式。尽管历史悠久,该头部因其简单性仍然自成主流 — 服务器无需计算内容的哈希值,只需从文件系统读取文件的时间戳或从数据库读取updated_at字段。

Last-Modified如何工作?

完整循环包括三个阶段。第一次请求时,服务器返回带有Last-Modified头部和HTTP状态200 OK的资源。客户端将响应连同日期一起缓存。再次请求时,客户端发送带有保存日期的If-Modified-Since头部。服务器将此日期与资源的当前修改时间进行比较。如果资源未修改 — 返回304 Not Modified,响应主体为空。如果已修改 — 返回200 OK,含有新数据和新的Last-Modified。

http
// 第一次请求 — 服务器返回带有日期的资源
HTTP/1.1 200 OK
Content-Type: application/json
Last-Modified: Wed, 02 Jul 2025 14:30:00 GMT

[{"id": 1, "name": "Alice"}]

// 再次请求 — 客户端发送保存的日期
GET /api/users HTTP/1.1
Host: example.com
If-Modified-Since: Wed, 02 Jul 2025 14:30:00 GMT

// 响应 — 数据未发生变化
HTTP/1.1 304 Not Modified

对于移动应用,Last-Modified在数据同步时尤其有用。应用程序保存最后一次成功更新的日期,并将其发送给服务器作为If-Modified-Since。如果有更多数据或数据已变化 — 服务器返回完整集合。如果未变化 — 返回304,应用程序使用本地副本。OkHttp和URLSession通过内置缓存系统自动支持该机制。

服务器如何确定日期

对于静态文件,Nginx和Apache从文件系统属性获取日期 — mtime(修改时间)。对于动态内容,服务器代码必须根据业务逻辑显式设置Last-Modified:来自数据库的updated_at字段、Git中最后一次提交的日期、构建产物的时间戳。如果没有显式设置Last-Modified,服务器可能根本不返回该头部,客户端将无法根据日期执行条件请求。

Last-Modified与ETag对比

Last-Modified和ETag执行类似的任务 — 允许客户端检查缓存的有效性 — 但具有根本性差异。Last-Modified使用时间戳,而ETag使用唯一版本标识符。每种方法在不同场景下更高效,HTTP规范建议同时使用两种头部。

维度Last-ModifiedETag
实质最后一次修改日期唯一版本标识符
精确度精确至秒精确至比特(哈希)
实现复杂度低 — 从文件系统自动获取中 — 需要计算哈希
集群服务器问题:各节点的mtime可能不同节点数据相同时稳定
范围请求支持不影响Range请求需要强ETag支持范围请求
建议适合静态文件和简单API适合需要精确验证的API

Last-Modified的主要优势是简单。服务器无需计算内容的哈希值,从而节省每次请求的CPU资源。对于高负载项目,服务静态文件或具有清晰时间戳的数据时,Last-Modified仍是最佳选择。而ETag提供了绝对的精确度 — JSON响应中一个字母的变化将改变ETag,但可能不会改变日期(如果文件被相同版本覆盖)。

共同使用

规范建议同时返回两个头部。服务器在200 OK响应中同时包含Last-Modified和ETag。客户端发送两个条件头部 — If-Modified-Since和If-None-Match。服务器先检查ETag(优先级更高),然后检查Last-Modified。如果至少有一个指示内容已变化 — 返回完整响应。这提供了最大灵活性:ETag确保精确度,Last-Modified作为不支持ETag的客户端的备用检查。

在服务器上配置Last-Modified

Last-Modified的配置取决于服务器类型。对于Nginx和Apache,静态文件的Last-Modified根据mtime自动设置。对于动态应用,需要在服务器代码中设置头部。下面看看常见平台上的配置。

javascript
// Express.js — 设置Last-Modified
app.get("/api/users", async (req, res) => {
    const updatedAt = await getLastUpdate()
    const ifModifiedSince = req.get("If-Modified-Since")

    // 检查If-Modified-Since
    if (ifModifiedSince && new Date(ifModifiedSince)
        >= updatedAt) {
        return res.status(304).end()
    }

    const users = await getUsers()
    res.set("Last-Modified", updatedAt.toUTCString())
    res.json(users)
})

在Express.js示例中,服务器从数据库获取最后一次更新日期,检查客户端的If-Modified-Since,如果缓存有效则返回304。如果数据已变化 — 设置新的Last-Modified并返回完整响应。toUTCString()将日期转换为所需的HTTP格式。在生产环境中,建议将updatedAt缓存到Redis,以免每次请求都查询数据库。

Nginx:配置Last-Modified

Nginx基于文件最后一次修改时间自动为静态文件设置Last-Modified。可以通过etag指令(关闭ETag)或通过ngx_http_headers_module模块禁用或改变这种行为。对于代理到后端的请求,Last-Modified从upstream响应中原样传递。重要:如果后端未返回Last-Modified,Nginx不会为动态响应自动添加该头部。

局限性与陷阱

Last-Modified存在几个已知局限。主要问题是精确度仅至秒级。如果资源在一秒内发生了两次变化,客户端可能会错过新版本。实际上这种情况并不常见,但对于高频更新(报价流、聊天)建议使用ETag。第二个局限是集群问题:由于复制或部署,不同服务器上的文件可能具有不同的mtime,导致Last-Modified不一致。

第三个局限是秒级精度的If-Modified-Since处理在频繁轮询服务器时可能导致多余请求。如果客户端每500ms发送一次If-Modified-Since,服务器每次都返回200 OK,因为日期未变,但资源实际上已更新。解决方案是与ETag联合使用:ETag能捕捉秒级内的变化,而Last-Modified作为备用。

第四个问题是Last-Modified无法区分相同日期的不同版本。如果文件从备份中恢复且其mtime与原始文件相同,客户端将无法察觉内容已变。ETag解决了这个问题:无论时间戳如何,内容的哈希值在数据发生任何变化时都会改变。对于关键数据,始终同时使用两种头部。

  • 秒级精确度 — 无法捕捉一秒内的变化;对于高频更新请使用ETag
  • 集群化 — mtime在不同服务器上可能不同;通过NTP同步或使用ETag
  • 竞态条件 — 如果资源在发送If-Modified-Since后但在服务器检查前发生了变化
  • 代理误解释 — 某些代理服务器可能在缓存时修改Last-Modified;HTTPS可解决此问题

常见问题

Last-Modified使用什么日期格式?

仅使用RFC 1123格式的GMT(格林尼治平时间):星期几,日期,月份,年份,时间。示例:Wed, 02 Jul 2025 14:30:00 GMT。时区始终为GMT,不允许其他格式。

Last-Modified可以设置为未来时间吗?

技术上可以,但这违反RFC 7232。如果服务器返回未来时间,客户端将不会更新资源直到该时刻。这种配置被视为错误 — 日期必须是过去或现在。

Last-Modified能用于POST请求吗?

不能,If-Modified-Since条件请求仅支持GET和HEAD。POST请求不会被缓存,也不使用日期验证。对于POST数据的有效性检查,请使用ETag或自定义机制。

Last-Modified与Cache-Control如何互动?

Cache-Control确定缓存策略(最大存储时间、谁可以缓存),而Last-Modified是验证过期缓存的机制。max-age过期后,客户端发送If-Modified-Since来检查内容是否仍有效。

如果更新数据后Last-Modified未变怎么办?

检查服务器是否从正确的源(数据库、文件系统或API)设置了头部。对于动态响应,确保在处理程序中显式调用res.setHeader(“Last-Modified”, ...)。

总结

  • Last-Modified — 用于条件请求304的HTTP头,包含资源最后一次修改日期
  • 实现简单 — 静态文件自动工作(文件mtime),API仅需极少代码
  • 精确至秒级 — 主要局限性;对于高频变化请使用ETag
  • ETag更精确,Last-Modified更简单 — 最佳组合:同时使用两种头部
  • HTTP日期格式 — 仅GMT,RFC 1123,固定957符
  • 集群化 — 需要时间同步(NTP)或以ETag为主要机制
  • 建议 — 始终为API添加Last-Modified,并通过Nginx/Apache为静态文件启用

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目

另请阅读