Firefox无法建立WSS连接至本地WebSocket服务器的问题排查
WebSocket(WSS)连接失败问题排查与解决
问题描述
尝试基于HTTPS环境,用客户端HTML文件和Python websockets库开发WebSocket演示程序,但Firefox加载socket_client.html时提示:Firefox无法建立到wss://localhost:8000/的连接。
客户端代码(socket_client.html)
<html> <head> <title>WebSocket demo</title> <style type="text/css"> body { font-family: "Courier New", sans-serif; text-align: center; } .buttons { font-size: 4em; display: flex; justify-content: center; } .button, .value { line-height: 1; padding: 2rem; margin: 2rem; border: medium solid; min-height: 1em; min-width: 1em; } .button { cursor: pointer; user-select: none; } .minus { color: red; } .plus { color: green; } .value { min-width: 2em; } .state { font-size: 2em; } </style> </head> <body> <div class="buttons"> <div class="minus button">-</div> <div class="value">?</div> <div class="plus button">+</div> </div> <div class="state"> <span class="users">?</span> online </div> <script> var minus = document.querySelector('.minus'), plus = document.querySelector('.plus'), value = document.querySelector('.value'), users = document.querySelector('.users'), websocket = new WebSocket("wss://localhost:8000/"); minus.onclick = function (event) { websocket.send(JSON.stringify({action: 'minus'})); } plus.onclick = function (event) { websocket.send(JSON.stringify({action: 'plus'})); } websocket.onmessage = function (event) { data = JSON.parse(event.data); switch (data.type) { case 'state': value.textContent = data.value; break; case 'users': users.textContent = ( data.count.toString() + " user" + (data.count == 1 ? "" : "s")); break; default: console.error( "unsupported event", data); } }; </script> </body> </html>
服务端代码(server.py)
# WS server example that synchronizes state across clients import asyncio import json import logging import websockets import ssl logging.basicConfig() ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) # Generate with Lets Encrypt, chown to current user and 400 permissions ssl_cert = "cert.pem" ssl_key = "key.pem" ssl_context.load_cert_chain(ssl_cert, keyfile=ssl_key) STATE = {"value": 0} USERS = set() def state_event(): return json.dumps({"type": "state", **STATE}) def users_event(): return json.dumps({"type": "users", "count": len(USERS)}) async def notify_state(): if USERS: # asyncio.wait doesn't accept an empty list message = state_event() await asyncio.wait([user.send(message) for user in USERS]) async def notify_users(): if USERS: # asyncio.wait doesn't accept an empty list message = users_event() await asyncio.wait([user.send(message) for user in USERS]) async def register(websocket): USERS.add(websocket) await notify_users() async def unregister(websocket): USERS.remove(websocket) await notify_users() async def counter(websocket, path): # register(websocket) sends user_event() to websocket await register(websocket) try: await websocket.send(state_event()) async for message in websocket: data = json.loads(message) if data["action"] == "minus": STATE["value"] -= 1 await notify_state() elif data["action"] == "plus": STATE["value"] += 1 await notify_state() else: logging.error("unsupported event: {}", data) finally: await unregister(websocket) start_server = websockets.serve(counter, "0.0.0.0", 8000, ssl=ssl_context) asyncio.get_event_loop().run_until_complete(start_server) asyncio.get_event_loop().run_forever()
环境说明
- 服务端使用Let's Encrypt生成的证书(
cert.pem、key.pem)配置SSL,权限已设为400,且确认服务端正常监听0.0.0.0:8000 - 错误提示:Firefox无法建立到wss://localhost:8000/的连接
错误成因
- 证书域名不匹配:Let's Encrypt证书为真实域名签发,而客户端连接的是
localhost,证书的Subject Alternative Name(SAN)字段未包含localhost,导致Firefox证书验证失败。 - 证书信任问题:Firefox拥有独立的证书存储,即使是有效证书,若未导入到Firefox信任列表中,会被判定为不可信,阻断WSS连接。
- SSL配置兼容性问题:服务端SSL上下文可能使用了Firefox不兼容的加密套件,或证书链不完整。
解决步骤
1. 生成适配localhost的自签名证书
本地开发环境建议生成包含localhost的自签名证书,替代真实域名证书:
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes -subj "/CN=localhost" -addext "subjectAltName=DNS:localhost"
该命令生成有效期1年、包含localhost域名的证书,同时设置SAN字段(Firefox强制要求,仅CN字段不被认可)。
2. 优化服务端SSL配置
确保SSL上下文兼容现代加密套件:
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) # 设置Firefox兼容的加密套件 ssl_context.set_ciphers('ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384') ssl_context.load_cert_chain(ssl_cert, keyfile=ssl_key)
3. 在Firefox中信任本地证书
Firefox需手动导入并信任证书:
- 打开Firefox,输入
about:preferences#privacy进入隐私设置 - 找到「证书」→「查看证书」→「证书机构」标签
- 点击「导入」,选择生成的
cert.pem,勾选「信任由此证书机构标识的网站」并保存。
4. 验证基础HTTPS连接
先访问https://localhost:8000,若Firefox能正常加载(或信任后加载),说明证书问题已解决,再测试WebSocket连接。若HTTPS无法访问,需检查证书路径、权限是否正确。
Firefox针对自签名证书的特殊配置
Firefox默认严格验证证书,开发环境可临时调整配置:
- 输入
about:config进入配置页面 - 搜索
security.enterprise_roots.enabled,设为true(让Firefox使用系统信任的证书,需先将证书导入系统存储) - 或搜索
security.ssl.enable_ocsp_stapling,设为false(关闭在线证书验证,避免本地证书无法通过OCSP检查) - 临时用
wss://127.0.0.1:8000替代wss://localhost:8000,部分场景下证书验证逻辑更宽松,但不推荐长期使用。
内容的提问来源于stack exchange,提问作者Adwaith Rajeev
相关产品推荐
相关产品推荐

