HTTPS下EventSource订阅失败,需检查Caddyfile是否启用TLS透传:mercure块必须显式配置transport http { endpoint https://localhost:8080 },publish_origins字节级匹配前端域名,且subscriber_jwt须由PHP动态签发并带入Authorization头。

HTTPS 下 EventSource 订阅失败:检查 Caddyfile 的 mercure 配置是否启用 TLS 透传
FrankenPHP 内置 Mercure Hub 默认只监听 HTTP 请求,若你用 Caddy 反代或直接跑在 HTTPS 站点下,EventSource 会因协议不匹配直接报 net::ERR_INSECURE_RESPONSE 或静默失败。关键不是“能不能配 HTTPS”,而是 Mercure 的订阅端点必须走 HTTPS 协议,且 Caddy 必须把 TLS 上下文正确透传给 Hub。
确保你的 Caddyfile 包含以下配置块(不能只写 mercure,必须显式声明 transport http 并启用 TLS):
mercure {
transport http {
endpoint https://localhost:8080
}
publish_origins ["https://your-domain.com"]
anonymous true
}
注意:endpoint 值必须是 HTTPS 地址(即使本地 Hub 实际监听 HTTP,Caddy 会自动做 TLS 终止和转发),publish_origins 要严格匹配前端页面的协议+域名,字节级一致——少个 s 或端口不写都会导致 CORS 拒绝。
前端 new EventSource() 报 401 或连接立即关闭:JWT token 缺失或签名不匹配
Mercure 在 HTTPS 环境下默认强制校验 JWT,即使配置了 anonymous true,浏览器端发起的 EventSource 请求仍需携带 Authorization: Bearer <subscriber_jwt>。很多人漏掉这步,以为“开了 anonymous 就不用 token”,其实只是 Hub 允许未认证订阅,但 HTTPS 场景下浏览器会因安全策略拒绝非凭证请求。
立即学习“PHP免费学习笔记(深入)”;
-
subscriber_jwt必须由 PHP 后端生成,使用与 Hub 配置中相同的密钥(mercure.jwt_key) - token payload 至少包含
mercure字段,且subscribe数组中要明确列出你订阅的 topic URL(如["https://your-domain.com/notify"]) - 前端 JS 中不能硬编码 token,必须从后端接口动态获取,否则跨域或过期时无法刷新
示例 JS 订阅代码:
fetch('/api/mercure-token')
.then(r => r.json())
.then(data => {
const es = new EventSource(
'https://your-domain.com/.well-known/mercure?topic=https://your-domain.com/notify',
{ headers: { 'Authorization': 'Bearer ' + data.token } }
);
es.onmessage = e => console.log(e.data);
});
cURL 测试 HTTPS Mercure 订阅时卡住或超时:检查系统级 TLS 握手限制
用 curl -N https://your-domain.com/.well-known/mercure?topic=... 测试时,如果返回缓慢、卡在 Connected to 或直接 timeout,大概率不是 FrankenPHP 或 Mercure 的问题,而是系统层 TLS 握手被阻塞:
- Linux 上检查
ulimit -n,FrankenPHP + Mercure 并发连接数高时容易触达文件描述符上限 - macOS 上某些版本 OpenSSL 与自签名证书握手异常,建议用
curl --tlsv1.2显式指定协议版本 - 若用 Caddy 自签证书,
curl默认不信任,必须加-k;但生产环境必须用 Let's Encrypt 正式证书,否则浏览器拒绝建立 EventSource
验证是否为证书问题:在浏览器访问 https://your-domain.com/.well-known/mercure?topic=xxx,看能否手动触发一次 SSE 流(响应头含 content-type: text/event-stream 且连接保持打开)。
Worker 模式下 WebSocket 推送在 HTTPS 网站失效:wss:// 连接被反向代理拦截
如果你没走 Mercure,而是用 FrankenPHP Worker 自建 WebSocket 服务(如聊天室),HTTPS 网站必须用 wss:// 连接,而常见错误是 Nginx/Caddy 没正确配置 WebSocket 升级头:
- Caddy 中必须显式设置
upgrade和websocket指令,不能只靠reverse_proxy - 确保
Sec-WebSocket-Key和Sec-WebSocket-Accept头未被中间件过滤或修改 - Worker 脚本里不能用
http://localhost:8000这类地址生成推送链接,必须读取$_SERVER['HTTPS']和$_SERVER['HTTP_HOST']动态拼wss://地址
最简验证方式:在 Worker 中临时加一行日志,打印 $_SERVER['REQUEST_SCHEME'] 和 $_SERVER['HTTP_UPGRADE'],确认握手请求是否真正到达 PHP 层——如果这两个值为空,说明反向代理根本没把 Upgrade 请求透传过来。
HTTPS 下实时推送真正的难点不在 FrankenPHP 本身,而在 TLS 终止位置与协议升级头的传递链路是否完整。任何一个环节(Caddy 配置、证书信任链、JWT 签发上下文、反向代理 upgrade 规则)断开,都会表现为“连接打不开”或“连上了收不到消息”,但错误日志往往沉默。



















