Cookie有效期谁说了算?569条到期日实测
本文目录
- 打开一个首页,服务器塞给你几个到期日?
- 这七个到期日里,有几个是你自己定的?
- 一条cookie的到期日,靠什么活到那天?
- 声明两年,浏览器只认400天,这条线卡住了谁?
- Expires和Max-Age同时写,两个数打架了吗?
- 到期日写成过去时,是干什么用的?
- 响应头里那个1994年12月1日,是从哪来的?
- 有没有哪个到期日,必须人手动去续?
- 这些日期是怎么写下来的,格式统一吗?
- 这一轮实测,我的尺子错了三次
- 自己站上怎么盘这些到期日?
- 常见问题解答
- cookie的有效期到底该设多久?
- Expires和Max-Age都写,哪个说了算?
- 响应头里的Expires写1994年,是不是配错了?
- security.txt里的Expires过期了会怎样?
- Safari把cookie砍到7天,那400天上限还有意义吗?
- 怎么知道哪些cookie不是自己写的?
- 权威参考资料
摘要:打开131个海外品牌站的首页,102个站在第一次响应里就写下了未来的到期时刻,去重后共569条Set-Cookie,中位数是一个站7条。这些日期各有各的续期方式:cookie靠每次响应重发,你哪天不发它就按最后一次说的过期;security.txt里那个到期字段必须人手动改,11个站里已经有1个过期、2个干脆填到2035年;而5个站的响应头至今还在发1994年12月1日——一个从写下那天起就永远是过去时的日期,抄了三十年没人动过。
网页上写着未来时间的地方比想象中多。你在HTML里几乎看不到它们,它们藏在响应头、藏在Set-Cookie的属性里、藏在一个大多数人没打开过的文本文件里。每一个都是一句承诺:这东西在某年某月某日之前有效。
承诺一旦写下就不会自己变。会变的是它描述的那个东西——业务改了、法务改了、人换了。于是就有了这轮实测想问的那个问题:这些日期到期那天,谁负责去续?
打开一个首页,服务器塞给你几个到期日?
只请求首页,不点任何按钮,不接受任何弹窗。131个站里,102个站在这一次响应里就写下了至少一条带到期时刻的记录。按名称、域和路径去重之后一共569条,中位数是7条一个站,最多的fahertybrand一口气写了15条。
写法上分两派:317条用Expires写一个绝对时刻,227条用Max-Age写一个相对秒数,其中57条两个都写;另有82条(14.4%)两样都不写,关掉浏览器就没了。
七条。这个数字本身不算什么,真正有意思的是它们的长短分布。
| 有效期区间 | 条数 | 大致是什么 |
|---|---|---|
| 已经是过去时 | 23 | 删除指令,或者写完就作废 |
| 不超过一小时 | 77 | 反爬与会话保护 |
| 不超过一天 | 9 | 弹窗、横幅的冷却 |
| 不超过30天 | 70 | 语言、货币、购物车 |
| 30天到400天 | 304 | 访客标识与归因 |
| 400天到3年 | 2 | 越线,会被截短 |
| 3年以上 | 2 | 越线,会被截短 |
一头一尾都很有意思:23条的到期时刻已经过去了,2条排到了三年以后。中间那一大坨落在400天以内——这个数字不是巧合,后面会说到它是从哪来的。
再往下看一层:这七条记录并不归同一个人管。有的写在服务端代码里,有的由建站平台在响应链路上追加,有的干脆是CDN在边缘节点补的。它们最后都落到同一个浏览器里,看上去像是一个站统一发出来的声明,实际上出自三拨互不通气的人。
这七个到期日里,有几个是你自己定的?
先看名字。把569条按cookie名统计跨站出现次数,结果一目了然。
| cookie名 | 出现在多少个站 |
|---|---|
| _shopify_essential | 56 |
| localization | 55 |
| cart_currency | 51 |
| _shopify_y/_shopify_s | 各50 |
| _shopify_analytics/_shopify_marketing | 各46 |
| __cf_bm | 12 |
| __cq_dnt/dw_dnt | 各9 |
一个中位数为7条的站,其中五到六条来自建站平台或者CDN。你能自己决定有效期的,通常只剩一到两条。剩下那些的到期日由平台统一定,改版之后跟着一起变,你既不知道它什么时候变,也没有地方能看到变更记录。
这一点在归因上会咬人。做投放的人算的是“多少天内回来的算这次投放带来的”,这个窗口的物理上限就是那条追踪cookie能活多久。你在广告后台设的是30天归因窗口,实际那条cookie活多久,得看平台。关于归因窗口本身怎么选,站内那篇多触点归因模型怎么选才不被最后一次点击骗走预算拆得更细;零点击时代的归因怎么补那篇讲的是窗口之外还漏了什么。
还有一处更硬的错位。多数司法辖区要求你在隐私或者Cookie说明里告诉用户,这些数据会被存多久。本轮把政策页正文也扫了一遍,只有7个站在文字里写出了具体的留存期——hellotushy的Cookie说明列了4天、27天、1个月、5个月、11个月、1年这一串,nativecos列到了5年。剩下的绝大多数只写一句“在必要期限内保留”。
于是同一件事在一个站上有两个版本:响应头里那个精确到秒的数字,和政策页里那句没有数字的话。前者每天在发,后者从上线起没动过。这两份声明分别由谁维护、又各自停在哪一年,站内那篇政策页最后更新日期的实测量的就是后者。
一条cookie的到期日,靠什么活到那天?
这是整件事里最容易被误解的一点。
你写下Max-Age等于一年,浏览器就把这条记录存一年。但这个“一年”不是从此不管了——每一次响应里重新发一遍同名cookie,那一年就重新从当下开始算。用户每周来一次,那条cookie就永远差一年到期;用户三个月不来,它到期的日子就是最后一次访问加一年。
所以cookie的到期日属于“自动续期”那一类:只要用户还在来,只要你的服务端还在发那一行,它就一直往后滚。你什么都不用做。
代价是另一头:你想改它也得靠这条通道。把有效期从一年改成三个月,只对改完之后来过的用户生效;那些三个月没来的人,浏览器里还揣着老那一份,按老规矩到期。你在服务端改一行配置,用户那边的实际生效是一条拖着长尾的曲线,而不是一条竖线。
顺带说一句,这条通道还很容易被别的东西剪断。同域名下一张图片的响应头带上Clear-Site-Data,就能把整站的cookie一次清光——站内一张自家子域上的图片把登录态删干净那篇写的就是这个事故;跨子域能不能共享,则要看浏览器认不认它们是同一个站。
声明两年,浏览器只认400天,这条线卡住了谁?
Chrome从2022年起把cookie有效期的上限压到400天,任何超过这个值的声明都会被截短。这不是某个厂商的独门规矩,它写在HTTP状态管理规范的修订草案里,其他主流浏览器也陆续跟进了。
569条里,声明值超过400天的只有4条:
- swarovski的clientInfo,Max-Age等于360000000秒,约11.4年,对应的Expires写到2038年1月29日;
- on.com的on_uuid,整整10年;
- bugaboo的bgCID和harrys的FLAGSHIP_VISITOR_ID,各2年。
只有0.8%。说明这条线基本上已经被吃透了——那304条落在30到400天之间的记录,绝大多数写的是31536000秒,也就是一年整,稳稳压在线内。
但这个“遵守”多半不是每个站自己想明白的,而是平台在某次升级里统一改掉的。真正值得留意的是被截短之后会发生什么:声明值和实际值不一致,而你的日志里只有声明值。你在服务端记着“这条追踪ID能活两年”,浏览器只给400天,第401天用户回来会被当成新访客。差的那部分不会报错,只会变成一条对不上的曲线。
这件事怎么落到报表上,可以算一笔。假设你按“两年内回访算同一个人”做去重,实际上浏览器在第400天就把标识丢了,那么第400天之后回来的老客会重新拿到一个新标识,在你的口径里变成新访客。结果是新客占比虚高、复购周期虚短,而这两个指标恰恰是很多投放决策的输入。你在数据库里查不出任何异常,因为写进去的每一行都是对的。
swarovski那条更有意思。它的到期时刻定在2038年1月29日,比32位有符号时间戳能表示的最后一刻——2038年1月19日03:14:07——还晚了10天。在任何一个仍然用32位存时间的环节里,这个值都是溢出的。当然它先会被400天上限截掉,所以实际上不会出事,但这行字本身说明写它的人没打算让任何人再看第二眼。
Expires和Max-Age同时写,两个数打架了吗?
同一件事写在两个字段里,按经验应该会有一批对不上。规范里说得很清楚:两个都给的时候以Max-Age为准。那就来数一数有多少站踩在这条规则上。
57条同时写了两个字段。按各自换算成绝对时刻之后,有17条对不上——但把这17条打开一看,全部是同一种写法:Max-Age等于0,Expires写1970年1月1日(或者montbell那两条写2021年)。那不是打架,那是删除。把到期时刻设成过去,是从上个世纪沿用至今的删cookie惯用法,两个字段说的是同一件事:现在就扔掉它。
剔掉这一类之后,真正的口径冲突是0条。这个结果一开始让我以为是尺子坏了,所以专门倒回去查了一遍——确实是0。
为什么偏偏是这个字段没有出现不一致?因为这两个值不是人分别填的,是同一个框架用同一个变量算出来的两种表达。站内那篇HTTP响应头自相矛盾实测里,142个站有108个在犯自相矛盾的毛病,而那些矛盾恰恰都出在“两个不同的人/两层配置各写各的”的字段上。同一个变量算出来的两种表达不会打架,两个人各写一遍才会。
到期日写成过去时,是干什么用的?
23条记录的到期时刻已经过去了。它们分三类。
第一类是删除,前面说过,charleskeith、cybex-online、joolz、suitsupply、theordinary都在用1970年1月1日这个值。这是正常操作,只是从字面看颇有喜感:服务器郑重其事地写下一个到期时刻,而那个时刻在Unix纪元刚开始的第十秒。
第二类是过期的清理动作还留在代码里。montbell有两条cookie写着2021年9月3日到期——五年前的日期。这多半是某次改版留下的清理逻辑,本来只需要跑一次,结果一直挂在响应里,每个访客都要收一遍这条对谁都没用的指令。
第三类最隐蔽:到期时刻正好等于本次响应的时刻。quince有六条以q_utm开头的cookie就是这样——它们记的是广告来源参数,写下去的同时就作废。看结果和不写是一样的,但它多占了响应头的字节,也多占了每一次请求回传的字节。请求头里的cookie到底吃掉多少带宽,站内每个请求都把同一份cookie重发一遍那篇量过;真把请求头撑爆之后会发生什么,则在老用户打不开而SEO工具全返回200那篇里。
响应头里那个1994年12月1日,是从哪来的?
把视线从Set-Cookie挪到Expires这个响应头,会看到一批更古老的东西。
131个站里,28个站的首页响应带Expires头。其中8个写的是未来时刻,7个写0或者-1,剩下13个写的是一个已经过去的具体日期。这13个里:
| 写的日期 | 站点 |
|---|---|
| 1994年12月1日16:00:00 GMT | bugaboo、canyon、charleskeith、cybex-online、joolz |
| 1984年1月11日05:00:00 GMT | casetify |
| 1970年1月1日00:00:01 GMT | fjallraven |
五个互不相干的品牌,用同一秒。这不是巧合——1994年12月1日16时整(GMT)是当年浏览器文档里给出的“让页面永不缓存”的示例值,后来被写进各种框架的默认配置、贴进无数篇教程,一路抄到今天。casetify那个1984年1月11日是另一支同样古老的血脉。
这些日期的妙处在于:它们不需要任何人去续。一个从写下那天起就是过去时的日期,永远有效,因为它永远无效。它是这轮实测里唯一一类不存在维护责任的到期声明——代价是它同时也不再表达任何意思,纯粹是一句口令。
类似的化石在响应头里还有不少。站内那篇HTTP响应头有248个死字段量过整体规模;PHP在响应头里替你写的那行1981年的日期讲的是同一个现象在PHP默认配置里的版本。今天要表达同样的意思,写Cache-Control就够了,具体各字段怎么配,浏览器缓存头怎么配那篇有完整对照,边缘层的部分在CDN缓存策略那篇。
有没有哪个到期日,必须人手动去续?
有,而且只有一个。
security.txt是网站放在.well-known目录下的一个纯文本文件,告诉安全研究者发现漏洞之后该联系谁。它的规范里有一条相当罕见的强制要求:必须写Expires字段,过了那个时刻,这份文件的内容就不应再被信任。没有任何机制会自动续它——服务器不会,CDN不会,建站平台不会。
131个站里有11个站放了这个文件。逐个看:
| 站点 | Expires写的是 | 状态 |
|---|---|---|
| ikea | 2026年9月20日 | 快到了 |
| arcteryx/jysk | 2026年12月31日 | 有效 |
| swarovski | 2027年1月31日 | 有效 |
| vuoriclothing/notino/eufy | 2027年3月至7月 | 有效 |
| braun/nativecos | 2035年1月1日 | 形同废除 |
| lookfantastic | 2025年5月15日 | 已过期 |
| shopify | 没写这个字段 | 不合规 |
11个站,1个已经过期一年多,1个连必填字段都没写——而没写的那个是shopify,本轮样本里超过一半的站点跑在它的平台上。braun和nativecos那两个把到期日填到2035年,等于用规范允许的写法把规范的意图取消掉:规范建议这个值不要超过一年,正是为了逼你每年回来看一眼这个联系方式还对不对。
这11份文件里,还有一件事:没有一份带PGP签名。规范里签名是可选项,但它是唯一能证明“这份文件确实是这个组织放的”的手段。到期日和签名一起缺席,这份声明就退化成了一行谁都能改的联系方式。
把它和cookie放在一起对比,两种续期方式的差别就很清楚了。
| 声明 | 谁在续 | 不续的后果 | 本轮的失效率 |
|---|---|---|---|
| cookie的到期日 | 每次响应自动重发 | 用户不来自然过期 | 几乎为零 |
| security.txt的Expires | 只能人手动改 | 整份文件按规范失效 | 11个里坏了2个 |
| Expires头写1994年 | 不需要续 | 没有后果,也没有意义 | 不适用 |
结论很朴素:凡是需要人回头去续的声明,失效率就高;凡是机器顺手就续了的,几乎不出问题。这条规律在证书上也成立——证书有效期正在被压到几十天,倒逼所有人把续期自动化,站内那篇证书有效期砍向47天,一年一换的流程撑不到2027年讲的就是这条路怎么走。
这些日期是怎么写下来的,格式统一吗?
317条Expires里,311条用的是标准的RFC 1123格式,比如Thu, 03 Sep 2026 00:00:00 GMT。剩下6条来自5个站——bolia、madewell、nespresso、shein、weber——用的是更老的写法,日期部分用连字符连起来:Thu, 07-Oct-2027 23:39:54 GMT。
这个格式来自Netscape最早那份cookie草案,比RFC 1123还早。浏览器为了兼容一直认它,所以它至今能用。它出现在这里的意思和1994年那个日期一样:这段代码的血统比写它的人的司龄长。
顺带把属性也扫了一遍,因为有效期在实际生效时会被这几个属性一起影响。
| 属性 | 覆盖情况 |
|---|---|
| Secure | 326条,57.3% |
| HttpOnly | 207条,36.4% |
| SameSite | Lax 330条、None 140条、没写98条 |
| Partitioned | 13条,2.3% |
| Domain | 写了159条,27.9% |
有一条写着SameSite=true。这不是合法取值,浏览器会当成没写来处理——也就是说,写它的人以为自己开了一道锁,实际什么都没开。SameSite写错的代价在结账环节最贵,站内付款回来购物车就空了那篇量过浏览器给的那两分钟宽限到底发给谁。
还有一件值得单独记的事:声明的有效期是一回事,浏览器认不认是另一回事。Safari的跟踪防护会把脚本写下的cookie砍到7天,某些场景下只给24小时;Chrome的400天上限则对两种写法一视同仁。你在响应头里写下的那个数字,是三方博弈的起点,不是终点。
这一轮实测,我的尺子错了三次
三次里有两次差点把结论写反,而且两次都是同一个毛病:拿一把没校准的尺子去量差异。
第一次:算cookie还能活多久时,用错了起点。把Expires换算成“还能活多少天”,我拿了一个跟这批数据无关的固定时刻当零点,而不是每一次响应自己的Date头。两者差了十天,于是每一条cookie的寿命都被凭空拉长十天,结果是55条被判成“Expires和Max-Age对不上”。改成用响应自己的时钟之后,掉到17条,再剔掉删除写法之后是0条。任何“A和B不一样”的结论,先得确认A和A自己用的是同一把尺子。
第二次:cookie自己的Domain属性被站点域名覆盖了。脚本里把每条记录标上所属站点时,字段名和cookie的Domain属性重名,结果整列被冲掉。第一版跑出来的结论是“100%的cookie都写了Domain属性”——一个漂亮得不像真的数字。真实值是27.9%。
第三次:并发太高把自己判成了没有数据。第一轮跑完,131个站里52个返回429。如果就这么算下去,“多少个站会发cookie”这个分母直接少掉四成。降并发、加间隔重跑之后,102个站的数据才回来。限流不是站点的属性,是你自己的属性。
自己站上怎么盘这些到期日?
一条命令就能开始:curl -sI https://你的域名/ | grep -i -E 'set-cookie|expires|cache-control'。注意别只用这一条就下结论,很多站对HEAD和GET给的头不一样,具体差在哪站内curl -I和GET的对照那篇写得很细。
拿到清单之后,按五步过一遍。
- 分清哪些是你的。把名字对着建站平台和CDN的文档扫一遍,标出哪些不是你写的。不是你写的那些,有效期由平台定,你能做的是记下当前值,作为归因窗口的上限依据。
- 把超过400天的改掉。不是因为会报错,是因为声明值和实际值不一致会让你的日志说谎。写一年整最省心。
- 删掉那些没用的删除指令。翻一遍写着1970年或者Max-Age等于0的记录,确认它对应的清理动作是不是早就该退休了。每个访客都在为这几行没用的字节买单。
- 把有效期抄进政策页。响应头里那个数字和政策页里那句“在必要期限内保留”应该是同一件事的两种说法。本轮只有7个站真的写了具体天数。做法很简单:把第一步整理出来的那张表,挑出会存到用户设备上的那几条,把名称、用途、天数写进Cookie说明。
- 给security.txt排一个提醒。如果你放了这个文件,把Expires设成一年以内,并且在日历上排一个到期前一个月的提醒。它是这套东西里唯一没有自动续期的一个。顺手补上PGP签名,成本只有一次。
最后一句留给那个1994年的日期。它能活三十年,不是因为它有多正确,而是因为没有人有理由去动它——它不报错,不影响任何指标,改了也没有人会注意到。网站上真正长寿的东西,往往不是最有用的那些,而是最没人负责的那些。这轮实测里的每一类到期日,说到底都在回答同一个问题:写下它的那个人走了以后,谁接着管。同样的问题在政策页上有另一个答案,站内那篇隐私政策的最后更新日期实测量的就是没人接手之后那行日期停在了哪一年。
常见问题解答
cookie的有效期到底该设多久?
先分清用途。会话态和购物车这类,设成会话级或者几小时就够,用户关掉浏览器就该失效;语言、货币、地区这类偏好,30天到一年都合理;用于归因的访客标识,上限就是浏览器给的400天,写一年整最省心。别写超过400天的值——不会报错,但浏览器会截短,从此你的日志里记的和用户浏览器里存的就是两个数。
Expires和Max-Age都写,哪个说了算?
Max-Age说了算,这一条写在HTTP状态管理规范里。同时写两个的好处是兼容一些极老的客户端。本轮实测里57条同时写了两个字段,换算之后真正对不上的是0条——因为这两个值通常是同一个框架用同一个变量算出来的,不是两个人分别填的。如果你的站上出现了对不上的情况,多半说明有两层配置在各写各的。
响应头里的Expires写1994年,是不是配错了?
不是配错,是一句沿用了三十年的口令,意思是“别缓存这个响应”。1994年12月1日16:00:00 GMT这个具体值来自当年浏览器文档里的示例,本轮实测里有5个互不相干的品牌在用同一秒。它能正常工作,只是今天没必要了——Cache-Control里写no-store或者no-cache表达同样的意思,更清楚也更容易被各层缓存正确处理。
security.txt里的Expires过期了会怎样?
按规范,过期之后这份文件的内容就不应再被信任,也就是说安全研究者不该再照着上面的联系方式报漏洞。它不会有任何报错,页面照常返回200,文件照常能下载。规范建议这个值不要超过一年,正是为了强制你每年回来确认一次联系方式还有效。本轮11个站里有1个已经过期一年多,2个把它填到了2035年——后者等于把这条自检机制关掉了。
Safari把cookie砍到7天,那400天上限还有意义吗?
有,两者管的不是同一批。Safari的跟踪防护主要限制脚本通过document.cookie写下的记录,服务端在响应头里写的第一方cookie通常不受那条7天规则约束。400天上限管的是所有服务端写的cookie。实际结果是同一条业务需求在不同浏览器上寿命不同,做归因分析的时候要按浏览器分组看,不能用一个统一的窗口去算。
怎么知道哪些cookie不是自己写的?
最快的办法是看名字有没有跨站出现。本轮569条里,_shopify_essential出现在56个站,localization出现在55个,cart_currency出现在51个——凡是这种在大量不相干品牌上同名同姓的,都是平台或者第三方脚本写的。这类记录的有效期不由你定,平台改版时会跟着变,而且不会通知你。把它们单独列一张表,作为归因窗口和合规清单的输入。
权威参考资料
本文标题:《Cookie有效期谁说了算?569条到期日实测》
本文链接:https://zhangwenbao.com/cookie-expiry-declaration-renewal-audit.html
版权声明:本文原创,转载与引用请注明作者与原文链接。许可协议: CC BY 4.0