HTTP/2 代理与反向代理指南:Nginx、Go 与连接复用
HTTP/2 代理与反向代理指南:Nginx、Go 与连接复用,提供最小配置、验证命令和连接失败排查顺序。
HTTP/2 连接失败时,先确认普通 HTTP 请求经过代理 IP 能返回,再分别检查 TLS、HTTP/2 协商、Nginx 转发和连接复用。代理 IP 只负责改变对外的出口 IP,不会自动修复反向代理配置。
接入全球代理服务前先确认当前环境属于支持的海外网络环境;不在支持范围内时,应先确认服务适用条件。
HTTP/2、TLS、代理 IP 与反向代理分别负责什么
可以先把链路写成:Go 客户端 → 代理 IP → Nginx → 上游服务。代理 IP 决定出口 IP,TLS 与 ALPN 决定客户端和服务端协商什么协议,Nginx 负责接收与转发,上游服务决定最终响应。某一跳使用 HTTP/2,不代表后续每一跳都必须使用 HTTP/2。
客户端入口、代理 IP、Nginx 和上游服务可能使用不同协议。先画清每一跳,再判断是代理 IP 这一跳、TLS/ALPN、Nginx 转发还是上游服务的问题。
Nginx 与 Go 的最小配置
Nginx 的 listen 443 ssl 不能单独运行,必须同时配置 ssl_certificate 和 ssl_certificate_key,或明确说明证书由上层网关继承;替换域名后先执行 nginx -t。当上游按域名区分服务时,还要同步检查 SNI 与 Host。
Nginx 反向代理
下面配置只演示链路边界,真实域名、证书和上游地址应替换为自己的服务:
```nginx
server {
listen 443 ssl;
http2 on;
server_name example.com;
location / {
proxy_pass https://upstream.example.com;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header Connection "";
proxy_read_timeout 60s;
超时应结合上游响应时间调整
}
}
```
上面采用 Nginx 1.25.1+ 推荐的独立 http2 on; 写法;旧版本不支持该指令时,应按对应版本文档使用 listen ... http2,不要混用两套语法。
先用 curl -I https://example.com 建立直连基线,再用 curl --proxy "$PROXY_URL" --http2 -v https://example.com 验证代理路径;curl 构建必须支持 HTTP/2。两条命令分别记录 ALPN、响应头、连接保持时间和关闭原因。
进一步验证时可查看 curl -v 输出中的 TLS 协商、ALPN 和响应协议。入口协商成功但 Nginx 返回 502/504,通常应继续检查上游连接和超时,而不是直接更换代理 IP。
Go 客户端
Go 程序可以通过 http.Transport 的代理函数接入 HTTP/HTTPS 代理 IP。先验证普通请求,再进入长连接或高并发测试:
```go
package main
import (
"io"
"log"
"net/http"
"net/http/httptrace"
"net/http/httputil"
"net/url"
"os"
)
func main() {
proxyURL, err := url.Parse(os.Getenv("PROXY_URL"))
if err != nil { log.Fatal(err) }
transport := &http.Transport{
Proxy: http.ProxyURL(proxyURL),
ForceAttemptHTTP2: true,
MaxIdleConnsPerHost: 10,
}
client := &http.Client{Transport: transport}
for i := 0; i < 2; i++ {
req, err := http.NewRequest("GET", "https://example.com", nil)
if err != nil { log.Fatal(err) }
reused := false
req = req.WithContext(httptrace.WithClientTrace(req.Context(), &httptrace.ClientTrace{
GotConn: func(info httptrace.GotConnInfo) { reused = info.Reused },
}))
resp, err := client.Do(req)
if err != nil { log.Fatal(err) }
body, err := io.ReadAll(resp.Body)
resp.Body.Close()
if err != nil { log.Fatal(err) }
dump, _ := httputil.DumpResponse(resp, false)
log.Printf("request=%d reused=%t status=%s bytes=%d headers=%s", i+1, reused, resp.Status, len(body), dump)
}
}
```
两次请求是否复用连接,应通过 httptrace.GotConnInfo.Reused 或等价连接日志确认,不能只凭两次都返回 200 推断。
代理协议、主机、端口和认证要以服务商提供的信息为准,不要把代理协议和 HTTP/2 协议混为一层。
如何验证 TLS、ALPN 和连接复用
1. 普通 HTTP 请求是否成功。
2. TLS 证书、SNI 和 ALPN 是否协商成功。
3. Nginx 是否收到请求,上游是否返回响应。
4. 连接复用、读取超时和上游主动关闭是否符合预期。
5. 记录客户端版本、响应头、状态码、连接时长和每一跳日志。
502、504、降级和长连接失败怎么排查
如果普通请求无法建立连接,先查代理协议、主机、端口和认证;如果普通请求成功但 HTTP/2、TLS 或长连接失败,应转查 Nginx、ALPN、上游响应和超时。
官方文档
示例需要结合当前 Nginx、Go 和 TLS 版本复核:
- Nginx HTTP/2 module:确认 HTTP/2 入口语法和版本差异。
- Nginx proxy module:核对上游协议、请求头和超时。
- Go net/http:核对 Transport、代理函数和连接池参数。
- HTTP/2 RFC 9113:查看 HTTP/2 协议定义。
FAQ
HTTP/2 需要什么代理 IP?
先看客户端和目标服务支持的协议,再按地区、并发和会话需求选择动态住宅代理 IP、静态住宅代理 IP 或机房代理 IP。代理 IP 类型不能替代 HTTP/2 兼容性测试。
如何确认请求实际协商到了 HTTP/2?
查看客户端输出中的 ALPN 和响应协议,同时检查 Nginx access/error log。客户端入口使用 HTTP/2,并不代表 Nginx 到上游也使用 HTTP/2。
排查时按客户端 → TLS/ALPN → Nginx → 上游服务逐跳确认。如果基础代理参数尚未确认,可先查看代理设置帮助。
