PHP 8.6 收紧了三个 session INI 默认值:代码没动,登录流程先坏了
PHP 8.6 计划于 2026-11-19 发布。这次改动不新增函数,它改的是 INI 默认值:session.use_strict_mode、session.cookie_httponly、session.cookie_samesite 三项的默认值都朝更安全的方向移动。方向没有问题,但会打断一部分现有部署,尤其跨站 POST 流程,以及任何用 JavaScript 读会话 cookie 的代码。
迁移说明见 PHP 8.6 migration86,改动摘要见 stitcher.io / new-in-php-86。
三个默认值分别变了什么
| INI 项 | 8.6 之前 | 8.6 默认 |
|---|---|---|
session.use_strict_mode | 0 | 1 |
session.cookie_httponly | 0 | 1 |
session.cookie_samesite | 不设置 | Lax |
session.use_strict_mode:0 → 1
严格模式下,PHP 不再接受「未初始化过的 session id」。客户端送来一个 PHP 从未生成过的 id,会被直接拒绝,PHP 重新生成一个新 id 下发。
会出问题的写法:
- 自建 SSO / 单点登录,认证中心签发 id 后由各子系统直接沿用;
- 多系统共用同一个
PHPSESSID,靠上游把 id 透传下来; - 上游网关或反向代理往请求里注入 session id。
这些场景在 8.6 下的表现是:每个请求都拿到一个新会话,登录态写进去,下一个请求就找不到了——看起来像「登录成功但立刻掉线」。是否触发取决于 id 是否由 PHP 自身生成过,需自行验证自己的链路。
session.cookie_httponly:0 → 1
cookie 带上 HttpOnly 后,前端 JavaScript 读 document.cookie 拿不到 session 值。任何依赖 document.cookie 解析 PHPSESSID、把它塞进自定义请求头或 WebSocket 握手的代码,会读到 undefined。
这类逻辑本身就不该存在(HttpOnly 存在的意义就是挡住它),但升级当晚它会变成线上故障。如果确实需要在前端拿到会话标识,正确做法是单独下发一个非 HttpOnly 的 token,不要关掉这一项。
session.cookie_samesite:未设置 → Lax
Lax 意味着跨站发起的 POST 不再携带 session cookie。典型受影响的流程:
- 第三方站点用 `` 提交回本站(支付回跳、OAuth 回调、合作方跳转);
- 跨域 iframe 里的表单提交;
- 任何跨站的自动 POST。
浏览器此时不带 cookie,服务端看到的是一个未登录的新会话。
排查路径
按顺序走,先确认默认值,再看实际下发的属性,最后看 id 有没有被反复重置。
1. 对比新旧环境的默认值
php -i | grep -i '^session\.'
重点看三行:session.use_strict_mode、session.cookie_httponly、session.cookie_samesite。旧机器上它们大概率分别是 0、0、空值,新机器上是 1、1、Lax。如果 php.ini 或 FPM pool 配置里已经显式写过,这里显示的就是显式值,不会被默认值变更影响。
2. 用响应头确认实际下发的 cookie 属性
curl -s -D - -o /dev/null https://example.com/login.php | grep -i '^set-cookie:'
正常应看到类似:
Set-Cookie: PHPSESSID=4f9c...; path=/; HttpOnly; SameSite=Lax
少了 HttpOnly 或 SameSite,说明有代码在调用 session_set_cookie_params() 覆盖,或者配置没生效——逐层往上查 FPM pool 的 php_admin_value。
3. 看会话是否每次请求都换 id
连续发两次请求,带上第一次的 cookie:
curl -s -c /tmp/cj.txt -o /dev/null https://example.com/
curl -s -b /tmp/cj.txt -c /tmp/cj.txt -D - -o /dev/null https://example.com/ | grep -i '^set-cookie:'
如果第二次响应里又出现一条新的 PHPSESSID,并且值跟 /tmp/cj.txt 里的不同,基本可以判定是严格模式拒绝了旧 id。把 session.use_strict_mode 临时设回 0 复测,能快速确认因果关系。
修复:先把三项写死,再有意识地放开
不要在升级后靠默认值碰运气。在 php.ini 里给三项显式赋值,让升级过程无法在背后改动行为;之后需要哪一项放宽,单独改、单独验证。
; 先固定为 8.6 之前的行为,保证升级不改变线上表现
session.use_strict_mode = 0
session.cookie_httponly = 0
session.cookie_samesite =
; 逐项迁移完成后,再按需改回
; session.use_strict_mode = 1
; session.cookie_httponly = 1
; session.cookie_samesite = Lax
samesite 留空表示不输出该属性;如果某个 SAPI 下留空写法不被接受,删掉整行并确认默认值是否已经变成 Lax(需自行验证)。
确实需要跨站 POST 才能走通的流程,才把这一项设成 None,并且必须同时要求 Secure,否则浏览器会直接丢弃这条 cookie:
session.cookie_samesite = None
session.cookie_secure = 1
这要求站点全程 HTTPS。如果只有个别回调接口需要跨站,优先考虑把该流程改成同站跳转或后端对后端调用,而不是全局放宽 SameSite。
顺带:session_set_save_handler() 的弃用
PHP 8.6 同时弃用了不完整的 session_set_save_handler() 实现。由于 session.use_strict_mode 自 8.6 起默认开启,传给 session_set_save_handler() 的对象必须同时实现:
SessionIdInterface::create_sid()SessionUpdateTimestampHandlerInterface::validateId()
缺任何一个,严格模式下的会话处理会走不通。到 PHP 9.0,这两个方法会移入 SessionHandlerInterface 成为必需项。自研 session handler 的项目现在就该补齐这两个方法,而不是等到 9.0 再改。
版本信息
以上均为 PHP 8.6 的默认值变化,不涉及新函数或新 API。8.6 计划发布日期为 2026-11-19。具体到自己的运行环境(PHP 版本、SAPI、框架是否再次覆盖 cookie 参数),建议先用上面的三步排查实测一遍再动手改配置。