跳到主要内容

Cookie 过长过多:长度/数量超限的成因、排查与治理

你可能遇到过这些现象:

  • 明明代码没改,线上突然出现 400 Request Header Or Cookie Too Large / 431 Request Header Fields Too Large
  • 用户反馈“登录不了 / 一直跳登录 / 偶尔随机退出”
  • 本地正常,换个浏览器/无痕模式又恢复

这些问题里,有一类非常常见的根因:Cookie 太长(单个过大)或太多(数量过多),导致请求头超限、浏览器丢弃 Cookie、或代理/服务器拒绝请求

本文会把“Cookie 过长过多”讲透:限制在哪里、为什么会发生、怎么定位、以及如何从设计层面彻底治理。


1. 先搞清楚:Cookie 在 HTTP 里长什么样?

Cookie 主要通过两类头在 HTTP 中传递:

  • 响应头 Set-Cookie:服务器让浏览器“存一条 Cookie”
  • 请求头 Cookie:浏览器每次请求把匹配的 Cookie 带给服务器

注意:Set-Cookie 里有属性(Path/Domain/Max-Age/HttpOnly/...),但请求头 Cookie 里只有 name=value 列表

HTTP/1.1 200 OK
Set-Cookie: sid=abc123; Path=/; HttpOnly; Secure; SameSite=Lax
Set-Cookie: theme=dark; Path=/; Max-Age=2592000

之后浏览器请求时会带上:

GET /api/user HTTP/1.1
Host: example.com
Cookie: sid=abc123; theme=dark

这也解释了一个重要结论:

关键结论

Cookie 一旦变大/变多,会“跟着每个请求一起走”。
请求越多、资源越碎(页面里请求越多),Cookie 带来的额外开销就越明显。


2. 常见限制:真正卡你的往往不止浏览器

Cookie 的“上限”不是一个固定值,它受到 浏览器限制 + 服务器/代理限制 的共同影响。

2.1 浏览器侧(经验值,不同浏览器/版本会有差异)

维度常见经验值超限后的常见表现
单条 Cookie 大小~4KB(包含 name=value浏览器可能拒绝写入/截断/丢弃
每个域名 Cookie 数量~几十条(常见 50 左右)旧 Cookie 可能被“挤掉”(驱逐),导致状态异常
为什么说“经验值”?

规范没有强制所有浏览器用同一个上限;并且同名 Cookie(不同 Path/Domain)也会影响“实际发送量”。

2.2 服务端/中间层(更常见的“硬门槛”)

即使浏览器愿意带,反向代理、网关、WAF、CDN、负载均衡、应用服务器也可能因为“请求头太大”直接拒绝请求,常见就是 400/431。


3. 典型报错与现象(你看到的是“果”)

你看到的现象/报错可能发生在哪一层常见含义
431 Request Header Fields Too Large应用/网关/代理请求头(包含 Cookie)太大
400 Request Header Or Cookie Too LargeNginx 等代理层请求头或 Cookie 超限
登录接口偶发 400/431,清理 Cookie 后恢复客户端 + 中间层Cookie 膨胀到某个临界点
“随机退出登录 / 记不住状态”浏览器存储层Cookie 数量超限导致旧值被驱逐
快速判断是不是 Cookie 相关

用无痕窗口/新浏览器打开同一环境,如果立刻恢复,很大概率就是“本地 Cookie(或其它站点数据)污染/膨胀”。


典型错误做法:

  • 把用户信息、权限列表、菜单树、购物车明细等直接 JSON 串塞进 Cookie
  • 把多语言/AB 实验/埋点参数都塞进 Cookie

Cookie 适合存“小而关键的标识”,不适合存“可展开的大数据”。

JWT 自带 Header + Payload + Signature,业务再塞更多 claims,就很容易膨胀到 1~3KB 甚至更大。再叠加:

  • access_token + refresh_token 两条
  • 多端/多租户多条

很快就会碰到“请求头大小上限”。

误区

“HTTP/2 有头部压缩,所以 Cookie 大一点没事”——不可靠。
很多限制检查发生在解压后(或按头部列表大小计算),而且中间层未必都支持/配置一致。

你以为只设置了一条 token,实际上可能有多条:

  • Domain=example.com; Path=/
  • Domain=example.com; Path=/api
  • Domain=www.example.com; Path=/

浏览器在请求 /api/* 时可能把多条同名 Cookie 一起带上,导致请求头暴涨,也容易引发服务端解析歧义(到底用哪一个?)。

删除 Cookie 必须 Domain + Path 与当初写入时一致。否则你删的是“另一条”,旧的还在,越积越多。

另外,HttpOnly Cookie 前端 JS 读不到也删不掉,只能由服务端用 Set-Cookie 覆盖并设置过期。

4.5 依赖库的“自动分片”(Cookie splitting)

有些库为了绕过“单条 Cookie ~4KB”,会把一个大值拆成多条 Cookie(例如 sess.0sess.1…)。这会导致:

  • Cookie 数量快速增加
  • 请求头更大、更容易触发中间层限制

4.6 静态资源也带 Cookie(白白浪费)

如果你的 Cookie 设置在 .example.com,那么请求 static.example.comimg.example.com 的静态资源也会带上 Cookie——这会显著增加每个页面的请求头开销。


5.1 在浏览器里看“存了多少、谁最大”

Chrome DevTools(示例路径):

  • ApplicationStorageCookies → 选择对应域名
  • 关注:Cookie 条数、单条 Size、以及 Domain/Path/Expires/HttpOnly/Secure/SameSite

5.2 在 Network 面板里看“请求到底带了多少”

打开某个失败请求:

  • Network → 点开请求 → Request Headers → 找 Cookie:
  • 复制出来看是否出现大量同名 Cookie、是否明显过长

你也可以在控制台做一个“粗略估算”(注意:document.cookie 不包含 HttpOnly):

// 粗略估算:非 HttpOnly 的 Cookie 字节数
const bytes = new TextEncoder().encode(document.cookie).length;
console.log('document.cookie bytes =', bytes);

在应用层打印请求头长度(示例:Node/Express):

app.use((req, _res, next) => {
const cookieLen = (req.headers.cookie || '').length;
const headerCount = Object.keys(req.headers).length;
console.log({cookieLen, headerCount});
next();
});

如果你看到 cookieLen 动辄几千甚至上万字符,同时客户端报 400/431,那么基本就坐实了。


6. 怎么治理:优先做“设计修复”,再做“参数兜底”

推荐思路:

  • Cookie:只放 sid / session_id / 轻量标识
  • 服务端:用 Redis/数据库存“完整会话数据”(用户信息、权限等)
类比理解

Cookie 像“取件码”,服务端存储像“快递柜”。
你应该带取件码,而不是把整个快递柜背在身上到处跑。

6.2 限定 Path/Domain,减少“无关请求”携带

常见优化:

  • 只让 API 请求携带:Path=/api
  • 不要随意设置 Domain=.example.com(会让所有子域都带上)
  • 静态资源使用“无 Cookie 域名”(如 static.example.com 且不设置共享 Domain Cookie)

6.3 控制数量:合并、去重、清理历史遗留

  • 合并同类“埋点/实验”参数:不要一个参数一条 Cookie
  • 修复删除逻辑:确保用同样的 Domain + Path 覆盖过期
  • 给非必要 Cookie 设置较短 Max-Age,避免越堆越多

6.4 Token 过大:改成“短 Token + 服务端换取”

如果你必须在客户端存认证信息,优先考虑:

  • 使用短 token(引用 token),服务端根据 token 换取用户态
  • 精简 JWT claims(只保留最必要字段)
不建议为了省空间随意“压缩/加密后塞回 Cookie”

一方面可能引入安全风险(压缩相关攻击、实现错误),另一方面只是把问题“推迟”,并没有从根上减少“每请求都携带”的成本。

6.5 最后的兜底:调整网关/代理的请求头上限(谨慎)

在治理完成前,有时会临时调大代理/应用的 header 限制,避免大量用户被挡在门外。但要明确:

  • 这不是根治,只是“止血”
  • 上限越大,越可能被滥用并放大攻击面(更大的头部意味着更大的解析成本)

7. 实战小案例

案例 1:把用户信息放 Cookie,导致接口 431

错误做法(示意):

document.cookie = `userInfo=${encodeURIComponent(JSON.stringify(bigUserInfo))}; path=/`;

修复思路:

  • Cookie 只存 uidsid
  • 需要用户信息时,走接口从服务端获取(或放在 localStorage/IndexedDB 缓存,但不要跟请求走)

案例 2:同名 Cookie(不同 Path)叠加,导致请求头暴涨

你可能同时存在:

  • token=AAA; Path=/
  • token=BBB; Path=/api

当请求 /api/user 时,Cookie 头里可能出现两段 token=。修复思路:

  • 统一写入策略:同名 cookie 只保留一种 Path/Domain
  • 删除时用一致的 Path/Domain 覆盖过期

8. 面试高频问答

1)Cookie 为什么会导致性能问题?

因为 Cookie 会跟随匹配的请求一起发送,变大/变多会增加请求头大小,带来更多的网络传输与解析成本,页面请求越多影响越明显。

常见经验值是“单条 ~4KB、单域名几十条”,但不同浏览器/版本有差异;更常见的硬限制来自服务端/代理的“请求头大小上限”。

因为问题往往是“某个用户的 Cookie 累积到临界点”。清理后请求头变小,代理/服务器不再拒绝请求。

在 DevTools 的 Application → Cookies 看 size/数量,再在 Network → Request Headers 直接查看 Cookie: 头是否过长、是否存在同名多条。

不能。前端 JS 读不到也删不掉,必须由服务端通过 Set-Cookie 覆盖并设置过期时间(同时 Path/Domain 要一致)。

6)同名 Cookie(不同 Path/Domain)会发生什么?

可能在同一个请求里同时发送多条同名 cookie,导致请求头变大,也可能造成服务端解析歧义(取到哪个值)。

JWT 可能很大(尤其是 claims 多、签名长),再叠加 refresh token、实验 cookie 等,很容易把请求头顶到代理/网关限制,触发 400/431。

8)“把上限调大”是不是就完事了?

不是。调大只能止血,会增加解析成本与潜在攻击面。根治应该是:减少 Cookie 的长度/数量,并避免无关请求携带。