WSS服务端必须用http.Server配合tls.Config,不可直接使用ListenAndServeTLS;需通过tls.Listen创建TLS监听器并显式处理Upgrade请求,否则握手失败。
WSS 服务端必须用 http.Server + tls.Config,不能只靠 ListenAndServeTLS
go 的 http.listenandservetls 只支持 http/https,不处理 websocket 升级后的 tls 层复用。wss(websocket secure)本质是客户端先发 upgrade: websocket 请求,服务端在已建立的 tls 连接上完成协议切换——这要求你手动接管 tls 连接生命周期,而非让 listenandservetls 一揽子包办。
常见错误是直接把 WSS 路由注册到 http.HandleFunc 后调 ListenAndServeTLS,结果浏览器能连上 HTTPS 页面但 WebSocket 报 Error during WebSocket handshake: net::ERR_SSL_PROTOCOL_ERROR。根本原因是 Upgrade 请求被标准 HTTP server 拦截并返回 400 或 426,没走到你的 handler。
正确做法是:用 http.Server 显式配置 TLSConfig,再通过 server.Serve(tlsListener) 启动:
- 先用
tls.Listen("tcp", ":443", tlsConfig)创建带 TLS 的 listener - 再传给
http.Server.Serve(),而非ListenAndServeTLS() - 确保路由 handler 中对
Upgrade请求做显式处理(比如用gorilla/websocket的Upgrader.Upgrade())
证书加载必须用 tls.LoadX509KeyPair,不能合并成单文件传入
WSS 场景下你几乎一定会用自定义 tls.Config(比如要禁用 TLS 1.0、指定曲线、或热更新证书),这时 http.ListenAndServeTLS 的“两参数文件路径”模式就失效了。必须改用 tls.LoadX509KeyPair(certFile, keyFile) 加载证书对,并赋值给 TLSConfig.Certificates。
容易踩的坑:
-
certFile必须是 PEM 格式且含完整链(服务器证书 + 中间 CA),顺序不能反;keyFile必须是未加密私钥(无DEK-Info头),否则LoadX509KeyPair返回空 slice + error - Windows 路径要用正斜杠或原始字符串:
"C:/tls/cert.pem"或`C:\tls\cert.pem`,混用反斜杠会静默失败 - 权限问题仍存在:Linux/macOS 下
keyFile权限 >0600时LoadX509KeyPair不报错但后续tls.Listen会 panic,提示accept tcp: accepting: accept tcp [::]:443: accept: operation not permitted
WSS 客户端校验失败?重点检查 ServerName 和 SNI
Go 客户端(比如用 gorilla/websocket 连 wss://localhost:8443)默认启用 SNI,并严格校验证书中的 DNSNames 或 IPAddresses。如果证书只写了 CN = example.com,访问 wss://localhost 就会报 x509: certificate is valid for example.com, not localhost。
本地调试务必确认证书 SAN 包含:
-
DNSNames: []string{"localhost"}(浏览器和多数客户端必需) -
IPAddresses: []net.IP{net.ParseIP("127.0.0.1"), net.ParseIP("::1")}(若需支持 IP 访问)
生成命令示例(OpenSSL ≥1.1.1):
openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365 \ -nodes -subj "/CN=localhost" \ -addext "subjectAltName = DNS:localhost,IP:127.0.0.1,IP:::1"
注意:Go http.Client 默认走系统代理,若本地开了 mitmproxy 或 Charles,可能拦截 WSS 流量导致握手失败,可临时设 HTTP_PROXY="" 排查。
为什么 WSS 在某些客户端连不上,但 HTTPS 正常?
HTTPS 和 WSS 共享同一套 TLS 配置,但 WSS 对 ALPN 和协议协商更敏感。典型现象是 curl/wget 能访问 https://,但浏览器控制台报 Failed to construct 'WebSocket': An insecure WebSocket connection may not be initiated from a page loaded over HTTPS 或直接 timeout。
关键排查点:
- 服务端
TLSConfig.NextProtos是否包含"h2"和"http/1.1"?WSS 依赖 ALPN 协商,缺失"http/1.1"会导致旧客户端(如某些 Android WebView)无法降级,握手卡住 - 证书是否含 ECDSA 密钥?部分老旧客户端(如 iOS 12 以下)不支持 ECDSA 签名算法,优先用 RSA 生成测试证书
- 防火墙/NAT 是否拦截了 TCP 连接后未关闭的长连接?WSS 是长连接,比 HTTPS 更易暴露中间设备的保活策略缺陷
最常被忽略的是:WSS 升级请求必须走同一个 TLS 连接上下文,不能在 HTTP/2 stream 上复用。这意味着反向代理(如 Nginx)若未显式配置 proxy_http_version 1.1 和 proxy_set_header Upgrade $http_upgrade,WSS 就会静默失败。











