Nginx代理gRPC长连接需用grpc_pass、启用HTTPS+HTTP/2、禁用缓冲、配置upstream keepalive及分层超时,禁用proxy_pass和ip_hash;否则连接退化或中断。
grpc长连接在nginx下不会自动“保持”——它靠的是http/2复用和客户端建连策略,nginx只负责不干扰、不降级、不截断。 配置错一个环节(比如用proxy_pass、漏http2、后端没开h2c),连接就会被强制断开或退化成短连接。
必须用grpc_pass,禁用proxy_pass
gRPC不是HTTP/1.x流量,Nginx必须走原生HTTP/2流式代理路径:grpc_pass会启用完整的HTTP/2帧透传(包括HEADERS、DATA、RST_STREAM),而proxy_pass默认按HTTP/1.x处理,会导致:
- TLS握手失败(客户端发ALPN h2,Nginx回HTTP/1.1)
- gRPC状态码丢失(
grpc-status头被忽略) - 流中断(DATA帧被缓冲或截断)
正确写法:
upstream grpc_backend {
server 10.0.2.10:8080;
server 10.0.2.11:8080;
keepalive 32;
}
server {
listen 443 ssl http2;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
location / {
grpc_pass grpc://grpc_backend;
grpc_buffering off;
}
}
keepalive是给Nginx→后端用的,不是给客户端用的
很多人误以为keepalive 32能让客户端长连接“保持更久”,其实它只控制Nginx worker进程与后端gRPC服务之间的空闲连接池大小。客户端是否复用连接,完全由gRPC SDK决定(如Go默认pick_first)。
- 值太小(如
keepalive 2):Nginx频繁重建到后端的连接,触发TIME_WAIT堆积 - 值太大(如
keepalive 1000):空闲连接占满后端文件描述符,拒绝新连接 - 推荐值:
keepalive 32~64,配合后端每个实例能稳定支撑的并发连接数(通常500~2000)动态调整
注意:keepalive必须写在upstream块里,不能放在location或server中。
客户端必须关掉pick_first,显式启用round_robin
Nginx的least_conn对gRPC无效,根本原因不是Nginx不支持,而是Go gRPC客户端默认只取DNS解析结果的第一个IP建连——哪怕你upstream写了5个后端,它也只连10.0.2.10:8080,然后所有stream全打过去。
- Go客户端需显式配置:
grpc.WithBalancerName("round_robin") - 若用K8s Headless Service,确保DNS返回多个A记录(不是单个VIP)
- 禁用
ip_hash或hash $remote_addr:它们会把同一客户端IP永远绑定到一个后端,加剧倾斜
验证方法:在Nginx access_log里加$upstream_addr $connection_requests,看到同一客户端IP的$connection_requests持续增长但$upstream_addr不变,就是客户端没轮询。
超时配置必须分层对齐,不能只调Nginx
gRPC长连接的稳定性取决于三层超时协同:
- 客户端
DialOption:如grpc.WithTimeout(30s)控制建连 - Nginx:
proxy_connect_timeout 5s、proxy_read_timeout 300s(必须≥后端gRPC服务的stream超时) - 后端Go服务:
http.Server.ReadTimeout和WriteTimeout要略大于Nginx的proxy_read_timeout,否则Nginx还没读完响应,后端先断了连接
特别注意:keepalive_timeout(客户端到Nginx)建议设为75s,但proxy_read_timeout(Nginx到后端)必须覆盖最长可能的gRPC stream生命周期(如实时日志推送可长达数小时),此时应设为0(禁用超时)或足够大的值,并依赖gRPC自身的deadline机制。


















