核心是分层隔离与精准降级:为旧安卓(4.4–6.x)、ios 9–10等设备配置独立子域名(如legacy.example.com),禁用http/2、启用tlsv1.1/tlsv1.2及aes-sha兼容套件,并同步调整后端proxy_ssl协议与响应头控制字符。

Nginx 启用 HTTP/2 后,部分旧版安卓(如 Android 4.4–6.x)、iOS 9–10 设备或系统 WebView 会出现连接失败、空白页、ERR_CONNECTION_CLOSED 等现象,并非单纯“不支持 HTTP/2”,而是 TLS 协商、ALPN 行为、帧处理能力或响应头格式与这些客户端存在隐性不兼容。解决核心是分层隔离 + 精准降级,而非全局禁用或强行统一协议。
按设备能力分流,不混用同一 HTTPS 端口
旧设备(尤其是 Android 5–6 的 WebView、iOS 9.0–9.2)常因 ALPN 不识别 h2、TLS 握手超时、或 HPACK 解压异常直接断连。把它们和现代浏览器塞进同一个 listen 443 ssl http2 块,等于让兼容性最弱的客户端拖垮整体体验。
- 为老旧移动端单独配置子域名,例如
legacy.example.com或m-api.example.com - 在该 server 块中:
- 使用
listen 443 ssl;(不加http2) -
ssl_protocols TLSv1.1 TLSv1.2;(iOS 9 最低要求 TLSv1.2,但早期版本补丁不全,保留 TLSv1.1 更稳妥) -
ssl_ciphers 'ECDHE-RSA-AES128-SHA:AES128-SHA:ECDHE-ECDSA-AES128-SHA';(含 CBC 套件,避免仅 GCM 导致握手失败) - 显式关闭
http2相关指令(如http2_max_requests等无需配置)
- 使用
确保后端服务也同步降级 TLS 版本
Nginx 能连上旧设备,不代表能连通后端。若 upstream 是 Java 微服务(如 Spring Boot 1.5 + Tomcat 8.0),它可能只支持 TLSv1.1;而 Nginx 默认以 TLSv1.2 向后端发起连接,结果首页能打开(静态资源走 Nginx),API 却全报 502。
- 在对应
location或upstream中设置:-
proxy_ssl_protocols TLSv1.1; -
proxy_ssl_ciphers "ECDHE-RSA-AES128-SHA:AES128-SHA"; -
proxy_ssl_server_name on;(多域名证书场景必须开启,否则返回默认证书)
-
- 实测验证:
openssl s_client -connect backend:8443 -tls1_1 2>/dev/null | grep "Verify return code"
清理响应头中的非法控制字符
HTTP/2 使用 HPACK 压缩头部,任何 ASCII 控制符(如 \x00–\x1f、\x7f)都会导致解压失败,触发静默断连——浏览器可能显示为空白或 ERR_HTTP2_PROTOCOL_ERROR,但服务端日志无报错。
- 用
curl -v https://legacy.example.com/api/test抓取原始响应头 - 检查
Set-Cookie、X-Trace-ID、X-Debug等自定义字段是否含不可见字符(如^?、^@) - 在后端输出 header 前统一过滤:移除所有
ord(c) 的字符 - Nginx 层可临时加
more_set_headers "X-Checked: ok";辅助定位,但根治必须在应用层
避免中间链路干扰,尤其 CDN 和代理
很多问题实际发生在 CDN(如 Cloudflare)或企业网关之后:
- CDN 若未开启 ALPN 或强制 TLSv1.3,会拦截并重写 ALPN 列表,导致客户端收不到
h2标识,却仍走 HTTP/2 流程,最终帧错乱 - 用
openssl s_client -alpn h2 -connect legacy.example.com:443 2>&1 | grep ALPN验证服务端是否真实通告ALPN protocol: h2(对 legacy 域名应为空;对主站应有输出) - 若使用 CDN,确保其“HTTP/2”开关仅对主域名开启,且 legacy 子域名被排除在优化之外(如设置 Page Rule:
legacy.example.com/* → Disable HTTP/2)
不复杂但容易忽略











