
本文详解 symfony 应用通过 hubinterface 向本地 caddy mercure hub 推送更新时出现 “failed to send an update” 错误的根本原因(ssl 证书验证失败),并提供安全、可落地的配置修复方案,涵盖开发环境适配与生产注意事项。
在本地开发环境中使用 Symfony 集成 Mercure(尤其是搭配 Caddy 作为 Hub)时,常见报错如下:
Failed to send an update SSL certificate problem: unable to get local issuer certificate for "https://localhost/.well-known/mercure"
或 PHP 原生 file_get_contents() 调用失败:
Warning: file_get_contents(https://localhost/.well-known/mercure): Failed to open stream: operation failed
该问题并非 Mercure 配置错误,而是 PHP HTTP 客户端(如 Symfony HttpClient 或 cURL)在发起 HTTPS 请求时,严格校验 TLS 证书链,而本地 Caddy 开发模式默认使用自签名证书(或未正确配置信任根证书),导致 SSL 握手失败。值得注意的是,终端 curl 命令能成功,是因为系统 curl 默认不启用严格的证书验证(或已配置 --insecure / curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false)),而 PHP 的 ext-curl 或 stream 上下文默认开启验证。
✅ 推荐解决方案(开发环境)
在 config/packages/framework.yaml 中为 Symfony HttpClient 显式禁用证书验证(仅限开发):
# config/packages/framework.yaml
framework:
http_client:
default_options:
verify_peer: false
# 可选:同时禁用主机名验证(若证书 CN 不匹配 localhost)
verify_host: false⚠️ 注意:verify_peer: false 仅应在本地开发环境启用。切勿在生产环境使用,否则将导致中间人攻击(MITM)风险。
? 其他补充排查与优化建议
确认 Caddy 是否真正监听 HTTPS:检查 Caddyfile.dev 是否包含 https://localhost 或 :443,并确保 Mercure Hub 启动日志显示 HTTPS server started on https://localhost:443。若仅监听 HTTP(如 http://localhost:3000),应统一使用 http:// 协议访问 .well-known/mercure,并在 Symfony 配置中设置 MERCURE_PUBLISH_URL=http://localhost/.well-known/mercure(需同时禁用 https 强制重定向)。
PHP cURL 扩展需启用:Symfony HttpClient 默认优先使用 cURL。运行 php -m | grep curl 确认已加载;若未启用,需在 php.ini 中取消 ;extension=curl 注释。
-
纯 PHP 示例修复:若仍需使用 file_get_contents(),需显式配置上下文:
$context = stream_context_create([ 'http' => [ 'method' => 'POST', 'header' => "Authorization: Bearer {$jwt}\r\nContent-Type: application/x-www-form-urlencoded\r\n", 'content' => http_build_query(['topic' => $topic, 'data' => $data]), 'ignore_errors' => true, ], 'ssl' => [ 'verify_peer' => false, 'verify_peer_name' => false, ], ]); $result = file_get_contents('https://localhost/.well-known/mercure', false, $context); -
终极调试技巧:启用 HttpClient 日志,定位真实请求细节:
bin/console debug:container --parameter=framework.http_client.default_options
并在代码中捕获异常详情:
try { $hub->publish(new Update(['https://example.com/books/1'], '{"foo":"bar"}')); } catch (TransportException $e) { dump($e->getPrevious()?->getMessage()); // 查看底层 cURL 错误 }
? 总结
“Failed to send an update” 的核心症结是 PHP 客户端对本地 Mercure Hub 自签名 HTTPS 证书的严格校验。通过在 framework.yaml 中配置 verify_peer: false 可快速解决开发问题。但请始终牢记:此配置是开发便利性让步,上线前必须切换为受信证书(如 Let’s Encrypt)并恢复证书验证,保障通信安全。










