You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Laravel-WebSockets+Pusher+React SSL连接故障排查求助

Laravel-WebSockets + Pusher SSL握手错误排查方案

问题背景

使用Pusher、Laravel-Echo和laravel-websockets搭建实时服务,HTTP直连服务器IP(端口6001)完全正常,但通过Nginx反向代理的SSL连接(端口6002)出现握手错误,Nginx日志报错:SSL_do_handshake() failed (SSL: error:0A00006C:SSL routines::bad key share)。

技术栈

后端依赖

{
    "php": "^8.1",
    "beyondcode/laravel-websockets": "^1.14",
    "predis/predis": "^2.2",
    "pusher/pusher-php-server": "^7.2"
}

前端依赖

{
    "laravel-echo": "^1.15.3",
    "pusher-js": "^4.4.0",
    "react": "^18.2.0"
}

部署架构

Laravel 10 API后端(内置WebSocket服务)+ React前端,部署在OVH Ubuntu 23.04 VPS,使用Nginx反向代理和Docker容器化部署。

核心现象

  • SSL连接触发握手失败,无法建立WebSocket连接
  • 服务器IP+6001端口的HTTP连接工作正常
  • 服务器端口6001、6002已开放

关键配置详情

后端配置

broadcasting.php

'pusher' => [
    'driver' => 'pusher',
    'key' => env('PUSHER_APP_KEY'),
    'secret' => env('PUSHER_APP_SECRET'),
    'app_id' => env('PUSHER_APP_ID'),
    'options' => [
        'host' => env('PUSHER_HOST'),
        'port' => env('PUSHER_PORT'),
        'cluster' => env('PUSHER_APP_CLUSTER'),
        'scheme' => env('PUSHER_SCHEME', 'http'),
        'encrypted' => true,
        'useTLS' => env('PUSHER_SCHEME') === 'https',
    ],
],

websockets.php

'apps' => [
    [
        'id' => env('PUSHER_APP_ID'),
        'name' => env('APP_NAME'),
        'key' => env('PUSHER_APP_KEY'),
        'secret' => env('PUSHER_APP_SECRET'),
        'host' => env('PUSHER_APP_HOST'),
        'port' => env('PUSHER_APP_PORT'),
        'capacity' => null,
        'enable_client_messages' => false,
        'enable_statistics' => false,
        'enable_logging' => true,
    ],
],

.env

BROADCAST_DRIVER=pusher
PUSHER_APP_ID=XXX
PUSHER_APP_KEY=XXX
PUSHER_APP_SECRET=XXX
PUSHER_APP_CLUSTER=mt1
PUSHER_HOST=127.0.0.1
PUSHER_PORT=6001
PUSHER_SCHEME=http

Nginx WebSocket反向代理配置

server {
    listen 6002 ssl http2;
    listen [::]:6002 ssl http2;
    server_name yourdomain.com;
    server_tokens off;

    ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;

    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers TLS13-AES-256-GCM-SHA384:TLS13-CHACHA20-POLY1305-SHA256:TLS_AES_256_GCM_SHA384:TLS-AES-256-GCM-SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS-CHACHA20-POLY1305-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA;
    ssl_prefer_server_ciphers on;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

    add_header X-Frame-Options "SAMEORIGIN";
    add_header X-XSS-Protection "1; mode=block";
    add_header X-Content-Type-Options "nosniff";

    location / {
        proxy_pass             http://127.0.0.1:6001;
        proxy_read_timeout     60;
        proxy_connect_timeout  7d;
        proxy_redirect         off;

        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
    }

    access_log /var/log/nginx/access_ws.log;
    error_log /var/log/nginx/error_ws.log;
}

前端Echo初始化代码

const privateChannel = `private-channel.${userID}`;
const echo = new Echo({
  broadcaster: 'pusher',
  key: Config.PUSHER_KEY,
  cluster: Config.PUSHER_CLUSTER,
  wsHost: Config.PUSHER_HOST,
  wsPort: Config.PUSHER_PORT,
  wssPort: Config.PUSHER_PORT,
  forceTLS: false,
  encrypted: true,
  disableStats: true,
  enabledTransports: ['ws', 'wss'],
  authEndpoint: Config.PUSHER_AUTH_ENDPOINT,
  auth: {
    headers: {
      Authorization: `Bearer ${localStorage.getItem('authToken')}`,
    },
  },
});

前端配置参数

{
  PUSHER_KEY: 'XXX',
  PUSHER_CLUSTER: 'mt1',
  PUSHER_HOST: 'yourdomain.com',
  PUSHER_PORT: 6002, 
  PUSHER_PORT_SSL: 6002,
  PUSHER_FORCE_TLS: false, 
  PUSHER_AUTH_ENDPOINT: 'https://yourdomain.com/api/broadcasting/auth', 
}

已尝试无效方案

  • 切换Apache反向代理,问题复现
  • 将证书添加至Chrome信任列表
  • 调整数十种前后端配置组合,无改善

针对性排查与解决步骤

1. 修复Nginx TLS密钥协商配置

bad key share错误通常源于TLS密钥交换算法不兼容,当前Nginx的ssl_ciphers配置过于严格,且TLS 1.3与ssl_prefer_server_ciphers on存在冲突。修改Nginx SSL配置:

ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off; # TLS 1.3无需此配置,避免冲突

修改后重启Nginx:sudo systemctl restart nginx

2. 修正前端Echo配置矛盾

当前前端forceTLS: false与encrypted: true配置冲突,针对SSL连接调整如下:

const echo = new Echo({
  broadcaster: 'pusher',
  key: Config.PUSHER_KEY,
  cluster: Config.PUSHER_CLUSTER,
  wsHost: Config.PUSHER_HOST,
  wssPort: 6002, // 明确指定SSL端口
  forceTLS: true, // 强制使用TLS
  encrypted: true,
  disableStats: true,
  enabledTransports: ['wss'], // 仅启用SSL传输
  authEndpoint: Config.PUSHER_AUTH_ENDPOINT,
  auth: {
    headers: {
      Authorization: `Bearer ${localStorage.getItem('authToken')}`,
    },
  },
});

3. 验证Docker网络与端口绑定

确保Laravel-WebSockets服务在容器内绑定到所有网卡,修改websockets.php:

'apps' => [
    [
        // ...其他配置
        'host' => '0.0.0.0', // 替换为0.0.0.0,允许容器外部访问
        'port' => 6001,
        // ...其他配置
    ],
],

重启Docker容器:docker-compose restart webservice

4. 检查证书有效性

使用openssl验证证书链与握手:

openssl s_client -connect yourdomain.com:6002

确认输出中无证书错误、域名匹配,且握手流程正常。

5. 跳过Nginx代理直接测试SSL(可选)

若上述步骤无效,可让Laravel-WebSockets直接处理SSL:

  • 将Let's Encrypt证书挂载到Docker容器中
  • 修改broadcasting.php和websockets.php配置:
// broadcasting.php
'options' => [
    'scheme' => 'https',
    'certificate' => '/var/www/webservice/certs/fullchain.pem',
    'certificate_key' => '/var/www/webservice/certs/privkey.pem',
    // ...其他配置
],

// websockets.php
'ssl' => [
    'local_cert' => '/var/www/webservice/certs/fullchain.pem',
    'local_pk' => '/var/www/webservice/certs/privkey.pem',
    'verify_peer' => false,
],

前端直接连接wss://yourdomain.com:6001测试。


内容的提问来源于stack exchange,提问作者Michał Gajak

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 06:20:54