
nginx 默认 `proxy_read_timeout` 为 60 秒,导致后端 websocket 连接在空闲约 1 分钟后被强制关闭;需在 nginx 配置中显式延长该超时值,并配合 gorilla 的心跳机制确保长连接稳定。
WebSocket 在生产环境中常因反向代理(如 Nginx)的默认超时策略而意外中断。你遇到的 websocket: close 1006 unexpected EOF 错误,并非客户端主动关闭或 Go 程序异常,而是 Nginx 在 60 秒无数据读取 后主动终止了与后端 Go 服务的连接——此时 Gorilla WebSocket 尝试从已关闭的底层 TCP 连接读取数据,便触发 unexpected EOF。
✅ 正确的 Nginx 配置(关键修复)
你需要在 location /ws/(或对应 WebSocket 路径)块中显式设置更长的 proxy_read_timeout,同时保留 WebSocket 必需的升级头配置:
server {
listen 80;
server_name example.com;
location /ws/ {
proxy_pass http://127.0.0.1:1234;
# 必须:启用 WebSocket 协议升级
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# 必须:透传真实客户端信息
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# ? 关键:延长读超时(建议至少 3600s,即 1 小时)
proxy_read_timeout 3600s;
# 可选但推荐:写超时与读超时保持一致
proxy_send_timeout 3600s;
}
# 其他 location(如 /)可保留原有静态或 HTTP 服务配置
}⚠️ 注意:proxy_read_timeout 必须放在 location 块内(而非 server 或 http),且作用于 proxy_pass 的后端连接,直接影响 WebSocket 长连接的保活能力。
✅ Go 服务端增强:添加 Ping/Pong 心跳
仅调大 Nginx 超时还不够——若客户端长时间不发消息,Nginx 仍可能因无流量而断连。Gorilla WebSocket 提供内置心跳支持,推荐在服务端主动发送 Ping,并自动响应 Pong:
var upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
return true // 生产环境请按需校验 Origin
},
// 可选:设置读写超时(单位:秒),避免 goroutine 泄漏
ReadBufferSize: 1024,
WriteBufferSize: 1024,
}
func handleWS(w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Printf("Upgrade error: %v", err)
return
}
defer conn.Close()
// ? 启用自动 Pong 响应(收到 Ping 自动回 Pong)
conn.SetPongHandler(func(string) error {
conn.SetReadDeadline(time.Now().Add(30 * time.Second))
return nil
})
// ? 设置初始读取截止时间(防阻塞)
conn.SetReadDeadline(time.Now().Add(30 * time.Second))
for {
_, message, err := conn.ReadMessage()
if err != nil {
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
log.Printf("WebSocket error: %v", err)
}
break
}
// 示例:回显消息
if err := conn.WriteMessage(websocket.TextMessage, message); err != nil {
log.Printf("Write error: %v", err)
break
}
}
}此外,建议客户端(JavaScript)也定期发送 Ping(如每 25 秒),以维持双向活跃状态:
const ws = new WebSocket("ws://example.com/ws/");
// 每 25 秒发送一次 ping(服务端会自动 pong 响应)
let pingInterval;
ws.addEventListener("open", () => {
pingInterval = setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ type: "ping" }));
}
}, 25000);
});
ws.addEventListener("close", () => {
clearInterval(pingInterval);
});✅ 总结:三步确保 WebSocket 稳定长连接
- Nginx 层:在 WebSocket 对应 location 中设置 proxy_read_timeout(≥3600s)和 proxy_send_timeout;
- Go 层:启用 SetPongHandler 并合理设置 SetReadDeadline,避免空闲超时;
- 客户端层:主动发送 Ping 心跳,配合服务端 Pong 响应,形成闭环保活机制。
完成上述配置后,WebSocket 连接将稳定维持数小时甚至更久,彻底解决“一分钟自动断开”问题。务必重启 Nginx(nginx -s reload)并重新部署 Go 服务以使配置生效。










