header("cache-control: max-age=".time()+3600) 错在将 unix 时间戳(如 1747817640)误作秒数传入 max-age,实际应填相对秒偏移量如 max-age=3600;expires 才需 gmt 格式时间字符串。

PHP 中的 Cache-Control 头部不接受时间戳,只接受相对秒数(max-age)或指令组合;用 time() 计算的是过期时刻,但必须转换为距当前的秒偏移量,不能直接塞进 max-age。
为什么 header("Cache-Control: max-age=".time()+3600) 是错的
这行代码实际拼出的是类似 max-age=1747817640 的值——把 Unix 时间戳(2026年5月21日对应约 1747817640)当成了秒数。浏览器会把它理解成“缓存有效期 17.4 亿秒”,相当于 55 年,完全失去控制意义。
常见错误现象包括:资源明明改了,浏览器还死命用旧缓存;调试时反复清缓存也没用;CDN 层面缓存行为失控。
-
max-age的值必须是正整数,单位是“从响应发出那一刻起的秒数” -
time() + N是未来某个时间戳,不是“剩余秒数” - 正确写法只能是
max-age=3600、max-age=60这类固定偏移量
Cache-Control 和 Expires 的时间表达差异
Expires 头部才真正需要时间戳,但它要求是 GMT 格式的字符串,不是 Unix 时间戳。直接 date('r') 或 gmdate('D, d M Y H:i:s', time()+3600) . ' GMT' 才合法。
而 Cache-Control: max-age 和 Expires 在语义上可共存,但现代浏览器优先遵守 max-age;若两者冲突(比如 max-age=60 但 Expires 设在 1 小时后),以 max-age 为准。
- HTTP/1.1 推荐只用
Cache-Control,Expires主要为兼容 HTTP/1.0 - 设置
Expires时若用错格式(如本地时区、缺GMT后缀),整个头会被浏览器忽略 - 不要同时设
max-age=0和Expires过期时间——逻辑冗余且易引发中间代理误解
动态内容该用 no-cache 还是 max-age=0
二者表面效果相似(每次请求都校验),但底层机制不同:no-cache 强制验证(发 If-None-Match 或 If-Modified-Since),而 max-age=0 表示“缓存立即过期”,后续行为取决于是否提供验证头。
真实场景中,如果没配 ETag 或 Last-Modified,max-age=0 实际等价于不缓存;而 no-cache 至少保留了协商缓存的可能性。
- API 响应建议用
Cache-Control: no-cache,明确表达“需验证”意图 - 静态资源更新后需强制刷新,应避免
max-age=0,改用 URL 参数(cache busting)或no-store -
must-revalidate要慎用——它要求缓存失效后必须回源,对 CDN 友好但会放大源站压力
PHP 中设置缓存头的典型安全陷阱
最容易被忽略的是输出已开始后再调用 header() ——此时缓存头根本发不出去,但 PHP 不报错,导致你以为设成功了,其实浏览器完全收不到。
- 务必在任何
echo、print、HTML 输出、空格或 BOM 字符之前调用header() - 开启
output_buffering可缓解,但不解决根本问题;建议统一在脚本开头集中处理响应头 - 敏感接口(如登录态、订单页)别只依赖
Cache-Control,还要配合session_regenerate_id(true)和HttpOnlyCookie
真正难处理的从来不是怎么写那行 header(),而是搞清这个资源到底该由谁缓存、缓多久、失效后怎么验证——时间戳只是表象,背后的语义和协作链路才是关键。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











