HTTPS环境下Nginx代理的Socket.IO服务移动端连接失败求助
Socket.IO 移动端(Android/iOS)无法通过Nginx HTTPS反向代理连接问题
问题现象
- PC端Windows/MacOS/Linux浏览器均可正常建立Socket.IO连接
- Android/iOS应用无法连接,Nginx及Node.js服务无报错日志
- 通过
chrome://inspect/#devices查看移动端网络请求:xhr polling请求先处于pending状态半秒后变为canceled,请求地址为mydomain.com/socket.io/?EIO=4&transport=polling&t=Oc7fPq6 - Android应用日志显示SSL握手失败:
[ERROR:ssl_client_socket_impl.cc(978)] handshake failed; returned -1, SSL error code 1, net_error -202 - 已尝试添加
transports: ['websocket', 'xhr-polling', 'polling']参数,问题仍未解决
相关配置代码
服务端(Socket.IO与Express代码)
let SocketServer = require('./core/socket_server.js'); const bodyparser = require('body-parser'); const cors = require('cors'); const express = require('express'); const app = express(); const server = require('http').createServer(app); const io = require('socket.io')(server, { cors: { origin: "*", methods: ["GET", "POST"], transports: ['websocket', 'xhr-polling'], credentials: false }, allowEIO3: true }); let SockServer = new SocketServer(io); app.set('view engine', 'ejs'); app.use(bodyparser.json()); app.use('/call/:room_id/:user_id/:video_state', express.static('public')); app.get('/call/:room_id/:user_id/:video_state', async (request, response) => { await response.render('video_chat', { room_id: request.params.room_id, user_id: request.params.user_id, video_state: Number(request.params.video_state) }); }); server.listen(5001, "0.0.0.0"); SockServer.ServerInitialize(); app.use(cors({ origin: "*" }));
Nginx配置
events { worker_connections 1024; } http { server { listen 443 ssl; server_name mydomain.com www.mydomain.com; resolver 127.0.0.11; ssl_certificate /etc/certificates/cert.pem; ssl_certificate_key /etc/certificates/privkey.pem; location /socket.io/ { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $host; proxy_pass http://nodejs_container:5001; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; } location /videochat { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $host; proxy_pass http://nodejs_container:5001; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; } location /users { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $host; proxy_pass http://nodejs_container:5001; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; } location ~ ^/call/([^/]+)/([^/]+)/([^/]+) { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $host; proxy_pass http://nodejs_container:5001; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; } } }
Docker-compose配置
version: '3' services: nodejs_container: build: context: app dockerfile: ../docker/NodeJs/DockerFile networks: - backend environment: - NODE_ENV=production volumes: - ./app:/home/app command: sh -c "npm install && npm run start:prod" coturn_container: image: coturn/coturn ports: - "3478:3478/udp" - "5349:5349/udp" - "3478:3478/tcp" - "5349:5349/tcp" depends_on: - nodejs_container volumes: - ./coturn/turnserver/dhp.pem:/etc/turnserver/dhp.pem - ./coturn/turnserver.conf:/etc/turnserver.conf - ./coturn/certificates:/etc/coturn/certificates networks: - backend nginx_container: image: nginx ports: - 443:443 depends_on: - nodejs_container - coturn_container networks: - backend volumes: - ./coturn/certificates:/etc/coturn/certificates - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro - ./certificates:/etc/certificates command: [nginx, '-g', 'daemon off;'] networks: backend: driver: bridge
SocketServer类构造函数片段
class SocketServer{ constructor(main_socket){ this.ServerConnections = { database: undefined, socket_server: main_socket, Server_NSP: { Users: main_socket.of(ServerNamespaces.USERS), VideoChat: main_socket.of(ServerNamespaces.VIDEOCHAT) } }; } // ServerNamespaces.USERS = "/users" // ServerNamespaces.VIDEOCHAT = "/videochat"
排查解决方案
1. 优先检查SSL证书有效性
移动端对SSL证书校验比PC浏览器严格,确认以下几点:
- 证书是否为受信任的正规CA颁发(自签名证书需手动导入到移动端系统,否则默认拒绝)
- 证书是否包含完整的证书链(仅部署leaf证书会导致移动端握手失败)
- 证书的
subjectAltName字段是否包含mydomain.com和www.mydomain.com - 证书有效期正常,服务器与移动端时间同步
2. 优化Nginx SSL配置适配移动端
在Nginx的server块中添加以下配置,确保支持移动端常用的加密协议和套件:
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; ssl_session_timeout 1d; ssl_session_cache shared:SSL:10m; ssl_session_tickets off;
3. 修正Socket.IO配置
- 移除服务端
cors.transports配置,让Socket.IO自动协商传输方式 - 移动端客户端显式指定
transports: ['websocket'],优先使用WebSocket协议绕过polling的SSL问题 - 确认服务端
allowEIO3: true配置与客户端Socket.IO版本匹配(若客户端为v3版本则需保留)
4. 完善Nginx代理头信息
在/socket.io/的location块中添加以下配置:
proxy_set_header X-Forwarded-Proto $scheme; proxy_buffering off;
5. 验证证书链完整性
执行命令openssl s_client -connect mydomain.com:443,检查输出中是否有Verify return code: 0 (ok),确认证书链无问题
内容的提问来源于stack exchange,提问作者Andrei Tricolici
相关产品推荐
相关产品推荐

