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 Large | Nginx 等代理层 | 请求头或 Cookie 超限 |
| 登录接口偶发 400/431,清理 Cookie 后恢复 | 客户端 + 中间层 | Cookie 膨胀到某个临界点 |
| “随机退出登录 / 记不住状态” | 浏览器存储层 | Cookie 数量超限导致旧值被驱逐 |
用无痕窗口/新浏览器打开同一环境,如果立刻恢复,很大概率就是“本地 Cookie(或其它站点数据)污染/膨胀”。
4. 为什么会 Cookie 过长过多?(最常见的 6 类原因)
4.1 把“大对象”塞进 Cookie
典型错误做法:
- 把用户信息、权限列表、菜单树、购物车明细等直接 JSON 串塞进 Cookie
- 把多语言/AB 实验/埋点参数都塞进 Cookie
Cookie 适合存“小而关键的标识”,不适合存“可展开的大数据”。
4.2 Token 方案导致单条 Cookie 过大(尤其是 JWT)
JWT 自带 Header + Payload + Signature,业务再塞更多 claims,就很容易膨胀到 1~3KB 甚至更大。再叠加:
access_token+refresh_token两条- 多端/多租户多条
很快就会碰到“请求头大小上限”。
“HTTP/2 有头部压缩,所以 Cookie 大一点没事”——不可靠。
很多限制检查发生在解压后(或按头部列表大小计算),而且中间层未必都支持/配置一致。
4.3 同名 Cookie 叠加(不同 Path / 不同 Domain)
你以为只设置了一条 token,实际上可能有多条:
Domain=example.com; Path=/Domain=example.com; Path=/apiDomain=www.example.com; Path=/
浏览器在请求 /api/* 时可能把多条同名 Cookie 一起带上,导致请求头暴涨,也容易引发服务端解析歧义(到底用哪一个?)。
4.4 Cookie “清不掉”:删除姿势不对
删除 Cookie 必须 Domain + Path 与当初写入时一致。否则你删的是“另一条”,旧的还在,越积越多。
另外,HttpOnly Cookie 前端 JS 读不到也删不掉,只能由服务端用 Set-Cookie 覆盖并设置过期。
4.5 依赖库的“自动分片”(Cookie splitting)
有些库为了绕过“单条 Cookie ~4KB”,会把一个大值拆成多条 Cookie(例如 sess.0、sess.1…)。这会导致:
- Cookie 数量快速增加
- 请求头更大、更容易触发中间层限制
4.6 静态资源也带 Cookie(白白浪费)
如果你的 Cookie 设置在 .example.com,那么请求 static.example.com、img.example.com 的静态资源也会带上 Cookie——这会显著增加每个页面的请求头开销。
5. 怎么排查:定位“是哪条 Cookie 在作妖”
5.1 在浏览器里看“存了多少、谁最大”
Chrome DevTools(示例路径):
Application→Storage→Cookies→ 选择对应域名- 关注: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);
5.3 服务端快速确认:是不是被 Cookie 顶爆了
在应用层打印请求头长度(示例: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. 怎么治理:优先做“设计修复”,再做“参数兜底”
6.1 只在 Cookie 里放“引用”,不要放“数据”
推荐思路:
- 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(只保留最必要字段)
一方面可能引入安全风险(压缩相关攻击、实现错误),另一方面只是把问题“推迟”,并没有从根上减少“每请求都携带”的成本。
6.5 最后的兜底:调整网关/代理的请求头上限(谨慎)
在治理完成前,有时会临时调大代理/应用的 header 限制,避免大量用户被挡在门外。但要明确:
- 这不是根治,只是“止血”
- 上限越大,越可能被滥用并放大攻击面(更大的头部意味着更大的解析成本)
7. 实战小案例
案例 1:把用户信息放 Cookie,导致接口 431
错误做法(示意):
document.cookie = `userInfo=${encodeURIComponent(JSON.stringify(bigUserInfo))}; path=/`;
修复思路:
- Cookie 只存
uid或sid - 需要用户信息时,走接口从服务端获取(或放在 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 会跟随匹配的请求一起发送,变大/变多会增加请求头大小,带来更多的网络传输与解析成本,页面请求越多影响越明显。
2)浏览器对 Cookie 的大小/数量有哪些限制?
常见经验值是“单条 ~4KB、单域名几十条”,但不同浏览器/版本有差异;更常见的硬限制来自服务端/代理的“请求头大小上限”。
3)为什么清理 Cookie 后就恢复了?
因为问题往往是“某个用户的 Cookie 累积到临界点”。清理后请求头变小,代理/服务器不再拒绝请求。
4)如何快速定位是哪条 Cookie 导致问题?
在 DevTools 的 Application → Cookies 看 size/数量,再在 Network → Request Headers 直接查看 Cookie: 头是否过长、是否存在同名多条。
5)HttpOnly Cookie 能用 JS 删除吗?
不能。前端 JS 读不到也删不掉,必须由服务端通过 Set-Cookie 覆盖并设置过期时间(同时 Path/Domain 要一致)。
6)同名 Cookie(不同 Path/Domain)会发生什么?
可能在同一个请求里同时发送多条同名 cookie,导致请求头变大,也可能造成服务端解析歧义(取到哪个值)。
7)JWT 放 Cookie 有什么“尺寸”风险?
JWT 可能很大(尤其是 claims 多、签名长),再叠加 refresh token、实验 cookie 等,很容易把请求头顶到代理/网关限制,触发 400/431。
8)“把上限调大”是不是就完事了?
不是。调大只能止血,会增加解析成本与潜在攻击面。根治应该是:减少 Cookie 的长度/数量,并避免无关请求携带。