近日,多位开发者反馈在AWS云环境中遇到一个令人困惑的网络问题:当使用HTTP/2协议向部署在ECS(弹性容器服务)上、且位于AWS NLB(网络负载均衡器)后方的Spring Boot应用发起请求时,连接频频以PROTOCOL_ERROR终止,而改用HTTP/1.1协议则一切正常。这一问题在生产环境中尤为棘手,因为它直接影响使用HTTP/2特性的现代化客户端(如gRPC、支持多路复用的浏览器)的正常访问。本文将梳理问题现象、根因分析及主流解决方案,供运维和开发团队参考。

问题现象与复现环境

典型部署架构如下:客户端→AWS NLB(TLS监听器)→ECS服务(运行Spring Boot应用,基于Tomcat或Undertow)。当客户端使用HTTP/2(如curl --http2、h2load或支持HTTP/2的浏览器)发送请求时,NLB返回RST_STREAM帧或直接断开连接,应用日志中出现javax.net.ssl.SSLException: Received fatal alert: protocol_versionorg.apache.tomcat.util.net.openssl.ciphers.OpenSSLCipherConfigurationParser: java.lang.IllegalArgumentException等异常;而使用HTTP/1.1(如curl --http1.1)则响应正常,业务逻辑毫无问题。该现象在Spring Boot 2.x和3.x版本中均有报告。

根因分析:NLB的TLS终止与HTTP/2协议转换的“隐形断层”

问题核心在于AWS NLB的设计定位。NLB属于第四层负载均衡器,专注于TCP/UDP流量转发,不解析应用层协议。当配置TLS监听器时,NLB会终止TLS加密(即解密客户端请求),然后以后端协议(默认为TCP)将明文流量转发给ECS上的Spring Boot。关键在于:NLB在TLS终止后,并不会自动将HTTP/2降级为HTTP/1.1或进行任何应用层协议协商(ALPN)

正常流程中,客户端发起HTTP/2连接时,通过TLS握手中的ALPN扩展告知服务器支持h2协议。如果NLB终止了TLS,它自己会与客户端完成TLS握手,并将NLB自身能力(通常仅支持http/1.1)告知客户端。此时客户端误以为后端支持HTTP/2,却不知NLB仅会以TCP流的方式转发原始数据。当NLB将加密后的HTTP/2帧(它无法理解)直接抛给后端Spring Boot时,后端的Tomcat/Undertow作为HTTP/1.1服务器无法解析HTTP/2帧头,从而触发PROTOCOL_ERROR。反过来,若NLB不终止TLS(即透传TLS连接),则客户端与后端直接协商,问题不会出现。

另外,部分用户在使用NLB时开启了“代理协议”或“目标组设置”中的“HTTP/2健康检查”,但健康检查功能本身使用HTTP/1.1,这与实际数据路径无关,容易造成误导。

解决方案:三条主流路线

针对上述根因,业界已总结出三种有效方案,各有利弊:

方案一:替换为ALB(应用负载均衡器)。
ALB原生支持HTTP/2协议,可在TLS终止后自动将HTTP/2转换为HTTP/1.1转发给后端(或支持h2c直接转发)。这是最推荐的长期方案,但需注意ALB成本略高于NLB,且不支持某些NLB特有的静态IP特性。

方案二:NLB配置TLS透传。
将NLB的监听器设为TCP(而非TLS),不对流量进行任何加密/解密,而是将TLS连接原封不动转发给ECS。此时需要在Spring Boot端自行配置TLS证书及支持HTTP/2(例如使用Spring Boot的SSL属性和server.http2.enabled=true)。此方案保留了NLB的静态IP优势,但增加了后端SSL配置和维护成本。

方案三:强制客户端降级为HTTP/1.1。
作为临时措施,可在客户端侧禁用HTTP/2(如Nginx反向代理时设置proxy_http_version 1.1,或在Java客户端中关闭HTTP/2)。但这种方式牺牲了性能,不推荐长期使用。

社区经验与最佳实践

根据AWS官方文档及Stack Overflow上的讨论,许多团队在迁移至ALB后彻底解决了问题。一位博主描述:“我们花了两天排查,甚至怀疑是Spring Boot Tomcat的bug,直到抓包发现NLB在TLS握手时没有发送h2的ALPN。”另有用户指出,如果必须使用NLB,请务必在ECS目标组中启用“客户端IP保留”并配置后端SSL,同时确保Spring Boot的Tomcat版本支持h2c(例如Tomcat 8.5+允许server.http2.enabled=true配合非TLS连接),但此举需注意安全风险。

总结

HTTP/2请求在AWS NLB+ECS+Spring Boot架构下出现PROTOCOL_ERROR,本质是NLB的第四层特性与HTTP/2的第七层需求不匹配所致。解决方向在于让NLB不干涉应用层协议(透传TLS),或将负载均衡器升级为ALB。建议团队评估业务对NLB静态IP、极低延迟的依赖程度,做出合理选择。同时,保持Spring Boot版本更新,并开启详细日志(如-Djavax.net.debug=ssl:handshake)有助于快速定位。技术无小事,一次错误的协议栈配置可能导致整个微服务生态的连锁故障,谨慎方可致远。