Gin 必须显式构造 http.Server 并配置 net.ListenConfig 启用 TCP KeepAlive,否则默认 http.ListenAndServe 不启用 SO_KEEPALIVE,易被 NAT 或 SLB 静默断连;IdleTimeout 需小于反向代理超时(如设 55s),TCP_KEEPIDLE 等参数需调低以适配云环境。

gin.Engine.Run() 不能直接设 KeepAlive,得换 http.Server 启动方式
你调 router.Run(":8080") 的那一刻,Gin 就偷偷用了 http.ListenAndServe,这个函数底层会创建默认的 http.Server,而它 IdleTimeout 是 0(即无限),KeepAlive 也压根没开——TCP 层连 SO_KEEPALIVE 都没启用。NAT 设备、云负载均衡器(比如阿里云 SLB 默认 5 分钟清理空闲连接)会在你毫无察觉时把连接静默断掉。
必须显式构造 http.Server,并用 net.ListenConfig 控制 socket 创建时的行为:
lc := net.ListenConfig{
Control: func(fd uintptr) {
syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_KEEPALIVE, 1)
syscall.SetsockoptInt(fd, syscall.IPPROTO_TCP, syscall.TCP_KEEPIDLE, 30)
syscall.SetsockoptInt(fd, syscall.IPPROTO_TCP, syscall.TCP_KEEPINTVL, 10)
syscall.SetsockoptInt(fd, syscall.IPPROTO_TCP, syscall.TCP_KEEPCNT, 3)
},
}
ln, _ := lc.Listen(context.Background(), "tcp", ":8080")
srv := &http.Server{
Handler: router,
Addr: ":8080",
IdleTimeout: 55 * time.Second, // 必须小于 Nginx/SLB 的超时
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
}
srv.Serve(ln)
-
IdleTimeout是关键:它控制 HTTP 层空闲多久关连接,不设或设太大,中间设备先动手;建议比反向代理超时小 5–10 秒 -
TCP_KEEPIDLE=30是 Linux 上第一个 keepalive 探测包发出前的空闲秒数,不是 2 小时默认值;云环境必须调低 - Read/WriteTimeout 不是保活用的,但设成 0 会导致请求卡死不释放,设太大又掩盖后端异常
Nginx 反向代理侧 keepalive_timeout 必须和 Gin 后端对齐
如果你的 Gin 服务前面还有一层 Nginx(几乎必然),那光调 Gin 没用——Nginx 会先断。常见错误是 Gin 设了 60 秒,Nginx 却用默认 keepalive_timeout 65s,结果 Gin 连接还活着,Nginx 已经把客户端连给关了,客户端收不到响应,重试后变成双倍压力。
典型协同配置:
- Gin
IdleTimeout = 55 * time.Second - Nginx
keepalive_timeout 50s 45s;(第一个值是 server 等待时间,第二个是写入响应头的提示值) - Nginx upstream 块里必须有
keepalive 32;+proxy_http_version 1.1;+proxy_set_header Connection ""; - 后端 Gin 若走 HTTPS,还得确认 TLS 会话复用开启(如
srv.TLSConfig = &tls.Config{SessionTicketsDisabled: false})
WebSocket 或 SSE 场景下,应用层心跳不能省
TCP keepalive 探测只保证链路层“通”,不保证业务层“活”。比如 WebSocket 连接上没有数据来往,但 TCP 探测包能过 NAT,Nginx 却可能因 proxy_read_timeout(默认 60 秒)主动断开。SSE 更明显:浏览器发完 GET 请求后就等着,Nginx 若等不到数据,timeout 一到直接 reset。
- WebSocket:在 Gin handler 里启动 goroutine,每 25 秒发一次
Ping帧(用c.Ping()),同时设c.SetPongHandler处理客户端 Ping - SSE:响应头加
Cache-Control: no-cache和Connection: keep-alive,服务端每 30 秒写一次data: \n\n心跳事件 - 别依赖
WriteTimeout拦截断连:它只管写操作耗时,不拦住空闲等待;必须靠主动心跳+合理 timeout 组合
容易被忽略的系统级限制:文件描述符与 TIME_WAIT
调大 IdleTimeout 和 keepalive 探测频率,会让连接在 ESTABLISHED 或 CLOSE_WAIT 状态停留更久。如果并发连接数高,ulimit -n 不够,你会看到 accept: too many open files;如果短连接突增,大量连接进入 TIME_WAIT,又没配 net.ipv4.tcp_tw_reuse = 1,就会出现 Cannot assign requested address。
- 上线前务必检查:
ulimit -n至少设为 65535,Gin 进程启动脚本里加ulimit -n 65535 - Linux 内核参数建议同步调整:
net.ipv4.ip_local_port_range = 1024 65535,net.ipv4.tcp_fin_timeout = 30,net.ipv4.tcp_tw_reuse = 1 - 用
ss -s观察tw(TIME_WAIT)数量,若持续 > 10k,说明连接回收策略和客户端行为不匹配,得回头调keepalive_timeout或查客户端是否异常断连



















