mercure 指令必须显式写入 caddyfile 且密钥非空,否则 hub 禁用、/.well-known/mercure 返回 404/500;需启用 mercure 块、配置 publisher_jwt 和 subscriber_jwt(可相同但不可为占位符)、添加 anonymous(开发必需),docker 下还需传入对应环境变量确保密钥生效。

mercure 指令必须显式写进 Caddyfile,且密钥配置不能为空——否则 FrankenPHP 启动后 Mercure Hub 仍处于禁用状态,/.well-known/mercure 路径会返回 404 或 500。
Caddyfile 中必须启用 mercure 块
FrankenPHP 不会自动开启 Mercure,哪怕你用了带 mercure 模块的镜像。它的启用完全依赖 Caddyfile 解析结果。最小可用配置如下:
localhost
mercure {
publisher_jwt !ChangeThisMercureHubJWTSecretKey!
subscriber_jwt !ChangeThisMercureHubJWTSecretKey!
anonymous
}
root public/
php_server
注意三点:
-
mercure块不能被注释,也不能放在import引入的子配置里(Caddy 当前版本不支持嵌套模块导入) -
publisher_jwt和subscriber_jwt必须非空;两者可相同,但不能是占位符字符串(如!ChangeThis...)——实际部署时必须替换成真实密钥 -
anonymous是开发阶段必需项;没有它,前端EventSource会因缺少Authorization头直接 401
Docker 环境下要传对环境变量
如果你用 dunglas/frankenphp 镜像跑 Docker,Caddyfile 里的密钥值会被环境变量覆盖。必须同时传入:
-e MERCURE_PUBLISHER_JWT_KEY=your_real_key-e MERCURE_SUBSCRIBER_JWT_KEY=your_real_key
否则即使 Caddyfile 写对了,运行时也会 fallback 到空密钥,导致 JWT 签发失败或验证拒绝。验证是否生效:执行 curl -v http://localhost/.well-known/mercure,成功响应应为 JSON,含 {"hub":"..."} 字段,HTTP 状态码 200。
别忽略 insecure 和 HTTPS 重定向陷阱
本地开发用 http://localhost 时,Mercure 默认拒绝 HTTP 订阅。你有两个选择:
- 在
mercure块内加insecure指令(仅限开发) - 删掉 Caddyfile 里类似
redir https://{host}{uri} permanent的全局跳转——它会让EventSource请求被 301 中断,连接立即断开
另外,Caddy 自动 HTTPS 在 localhost 下会签发自签名证书,浏览器可能拦截 SSE 连接;建议开发阶段用 insecure + HTTP,上线再切回 HTTPS + 正确域名。
发布和订阅的 JWT 密钥必须字节级一致
PHP 里调用 openssl_sign() 或 firebase/php-jwt 生成 token 时,所用密钥字符串必须和 Caddyfile 或环境变量中配置的**逐字节相同**。常见坑:
- 密钥含换行符或空格,PHP 里没
trim(),而配置里有 - 用
base64_encode()处理过密钥,但 Caddyfile 里写的是原始字符串(或反过来) - 大小写混淆,比如
MERCURE_PUBLISHER_JWT_KEY拼错成PUBLISHER(少一个 R)
只要密钥不一致,publisher_jwt 会导致 POST 发布 403,subscriber_jwt 会导致 EventSource 连接 401——这两个错误看起来像权限问题,根源其实是密钥失配。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











