如何在香港服务器(Ubuntu)启用 QUIC / HTTP/3,把跨境电商站的响应“拉回正轨”

这是我在屯门机房一个加班夜的完整记录:风墙呼呼,过道 28℃ 的热岛效应在机柜门口打着旋。前端同事在 IM 里敲来:“广东移动 4G 上结账页转圈 3~5 秒,偶发直接超时。”
我看了一眼流量曲线——晚高峰从华南、东南亚涌过来,RTT 摇摆、重传攀升。那一刻我基本确定:该把 QUIC / HTTP/3 真正顶到线上了。
我们的现场与目标
业务目标: 降低跨境(内地 ↔ 香港、东南亚 ↔ 香港)用户的首包时间(TTFB)与页面交互抖动,提升移动弱网(丢包 1%~3%)下的下单成功率。
硬件&系统:
| 项 | 配置 |
|---|---|
| 机房 | 香港,双上联(NTT + PCCW),单机房演进为主 |
| 服务器 | 1U,AMD EPYC 7443P(24C)、128GB RAM |
| 存储 | 2× 1.92TB NVMe(Samsung PM9A3,RAID1) |
| 网卡 | Intel X710 10GbE(支持 RSS、RPS、GRO/GSO) |
| OS | Ubuntu 24.04 LTS(内核 6.8 HWE) |
| 反向代理 | Nginx 1.27.x(编译启用 HTTP/3/QUIC) |
| 应用 | Node.js / PHP 混合,走 upstream |
重要认知先说在前面:Linux 主线并没有“QUIC 协议内核模块”。
QUIC 是用户态协议(跑在 UDP 之上),真正要“启用”的是:
1)用户态的 QUIC/HTTP3 服务器(Nginx/Caddy/HAProxy/Envoy 等),
2)内核的 UDP 加速能力(UDP GRO/GSO、队列调度、缓冲参数、conntrack 等)。
我下面的做法就是围绕这两点,确保端口开、栈通畅、实现靠谱、参数合适。
路线图(5 步到位)
- 确认 NIC/内核支持 UDP GRO/GSO,打开必要 offload。
- 系统网络栈调优(sysctl/ethtool),给 QUIC 通道“腾路”。
- 编译并部署带 QUIC 的 Nginx(链接 quictls OpenSSL QUIC 分支)。
- 开放 UDP 443(别被防火墙/云厂商安全组卡脖子)。
- 验证、观测与回滚阈值(curl/h2load、日志标记、灰度与熔断)。
1)NIC 与内核能力自检
先看内核版本与网卡 offload 能力:
uname -a
# 期待:Linux ... 6.8.x ...
ethtool -k eth0 | egrep 'gro|gso|rx-udp|tx-udp'
# 关注:generic-receive-offload (GRO) / generic-segmentation-offload (GSO)
# rx-udp-gro-forwarding / tx-udp-segmentation 是否 available/on
如果看到 rx-udp-gro-forwarding: off [fixed] 也别慌,很多时候 GRO/GSO 的大头是 generic(不依赖硬件),只要 generic-receive-offload: on、generic-segmentation-offload: on 就能让 QUIC 受益。
谨慎地开启你需要的开关(线上要先在影子/灰度机器做):
sudo ethtool -K eth0 gro on gso on
# 某些驱动还支持:
sudo ethtool -K eth0 rx-udp-gro-forwarding on 2>/dev/null || true
sudo ethtool -K eth0 tx-udp-segmentation on 2>/dev/null || true
2)系统网络栈调优(针对 QUIC/UDP)
sysctl:(在 /etc/sysctl.d/99-quic.conf,然后 sudo sysctl --system)
# 队列与缓冲
net.core.rmem_max = 134217728
net.core.wmem_max = 134217728
net.core.rmem_default = 8388608
net.core.wmem_default = 8388608
net.core.netdev_max_backlog = 250000
# UDP 专项
net.ipv4.udp_mem = 262144 524288 1048576
net.ipv4.udp_rmem_min = 4096
net.ipv4.udp_wmem_min = 4096
# 避免过度分片 & 允许路径 MTU 探测
net.ipv4.ip_no_pmtu_disc = 0
# 调度器与拥塞控制
net.core.default_qdisc = fq
net.ipv4.tcp_congestion_control = bbr # 对 TCP 回退通道仍有益(HTTP/2/1.1)
解释:QUIC 的拥塞控制与速率控制主要在用户态实现,但fq 调度器的 pacing 能改善整体队列行为;
UDP 缓冲放大可缓解高并发/突发时的丢包与应用阻塞。
RSS/RPS:(多队列/多核并行能抵“单核打满”)
# 查看队列数
ethtool -l eth0
# 打开 RPS/RFS(示例;按 CPU 数调大)
echo ffffffff | sudo tee /proc/sys/net/core/rps_sock_flow_entries
for f in /sys/class/net/eth0/queues/rx-*/rps_cpus; do echo ffffffff | sudo tee $f; done
3)编译 Nginx(HTTP/3/QUIC)
也可以用发行版仓库里自带的 http3 包,但我线上偏好显式链接 quictls,版本可控、回滚方便。
3.1 准备 quictls(OpenSSL with QUIC)
sudo apt update
sudo apt install -y build-essential ca-certificates git zlib1g-dev libpcre3-dev
cd /usr/local/src
sudo git clone --depth=1 -b openssl-3.1.7+quic https://github.com/quictls/openssl.git quictls
cd quictls
sudo ./Configure enable-ktls linux-x86_64
sudo make -j"$(nproc)"
sudo make install_sw
sudo ldconfig
openssl version
# 期待:OpenSSL 3.1.7+quic ...
选 3.1.x 是因为与当前主流 Nginx http_v3 模块兼容性更稳。若你已验证 3.2/3.3 分支,可替换相应 tag。
3.2 编译 Nginx 主线带 HTTP/3
cd /usr/local/src
sudo curl -O https://nginx.org/download/nginx-1.27.2.tar.gz
sudo tar xf nginx-1.27.2.tar.gz
cd nginx-1.27.2
# 指向我们刚装的 quictls
sudo ./configure \
--prefix=/etc/nginx \
--sbin-path=/usr/sbin/nginx \
--conf-path=/etc/nginx/nginx.conf \
--with-http_ssl_module \
--with-http_v3_module \
--with-pcre-jit \
--with-threads \
--with-file-aio \
--with-cc-opt='-O2 -fstack-protector-strong' \
--with-ld-opt='-Wl,-rpath,/usr/local/lib'
sudo make -j"$(nproc)"
sudo make install
nginx -V 2>&1 | egrep 'http_v3|OpenSSL'
# 期待输出里有 --with-http_v3_module 且 OpenSSL 为 3.1.x+quic
如果你更习惯 Caddy/HAProxy/Envoy:
- Caddy 原生带 HTTP/3(基于 quic-go),部署更快;
- HAProxy 2.9+ 支持 H3(需启 QUIC),配置心智稍高;
- Envoy 也支持 QUIC(基于 quiche)。
本文用 Nginx 方便与既有 upstream / 规则复用。
4)Nginx 配置(TLS1.3 + QUIC/H3)
证书放好(例如 /etc/nginx/ssl/fullchain.pem 和 privkey.pem),创建一个最小可用的 server:
# /etc/nginx/nginx.conf
worker_processes auto;
events {
worker_connections 65535;
use epoll;
}
http {
# 日志格式里把协议写出来,方便区分 H3 / H2
log_format main '$remote_addr "$request" $status $body_bytes_sent '
'$request_time $upstream_response_time "$server_protocol" "$http_user_agent"';
access_log /var/log/nginx/access.log main;
sendfile on;
keepalive_timeout 65;
server {
# 1) TCP/TLS(HTTP/2)作为回退通道
listen 443 ssl http2 reuseport;
# 2) UDP/QUIC(HTTP/3)
listen 443 quic reuseport;
server_name shop.example.com;
# TLS 1.3 必选,H3 只用 TLS1.3
ssl_certificate /etc/nginx/ssl/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/privkey.pem;
ssl_protocols TLSv1.3;
ssl_ciphers TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256;
# 广而告之支持 H3(Alt-Svc)
add_header Alt-Svc 'h3=":443"; ma=86400' always;
# 反代到应用(示例)
location / {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
server {
listen 80;
server_name shop.example.com;
return 301 https://$host$request_uri;
}
}
启动&开机自启(示例用 systemd wrapper):
# 简单的 systemd unit(/etc/systemd/system/nginx.service)
# 若你已有包管理装的 nginx,请按原有 unit 管理
sudo systemctl daemon-reload
sudo systemctl enable --now nginx
sudo systemctl status nginx
5)打开 UDP 443(别忽略“外部”安全组)
本机防火墙:
sudo ufw allow 443/tcp
sudo ufw allow 443/udp
sudo ufw reload
sudo ss -lnup | grep :443 # 应能看到 nginx 占用 UDP :443
云/运营商安全组(常见坑):
- 很多云默认不放行 UDP 443;
- 上了第三方高防/WAF/负载均衡,它们自己也要支持 H3/UDP 透传,不然你本机开了也白搭。
- 任何中间层 4/7 设备如果做了 NAT 超时太短,会让长连接 H3 断流 —— 记得调大或旁路。
6)验证:我在线上怎么“量”
6.1 协议可用性
# 在你本地或一台境外/内地探针上
curl -I --http3 https://shop.example.com
# 期待:HTTP/3 200/301;响应头里能看到你的 Server/Alt-Svc
6.2 压测(更贴近移动弱网)
# h2load 支持 H3(nghttp3/QUIC 版本)
h2load -n 2000 -c 100 -m 10 --h3 https://shop.example.com/static/app.js
# 关注:ttfb(start-to-first-byte),error rate,p95/p99
我还做了一组真实链路(深圳移动 4G、广州联通 5G、胡志明电信 4G)的 AB 对比,核心指标如下(电商首页 + 详情页):
| 场景 | 启用前(H2/TCP)p95 TTFB | 启用后(H3/QUIC)p95 TTFB | 变化 |
|---|---|---|---|
| 深圳移动 4G(丢包≈1.2%) | 920 ms | 610 ms | -33.7% |
| 广州联通 5G(丢包≈0.6%) | 680 ms | 520 ms | -23.5% |
| 胡志明 4G(丢包≈1.8%) | 1,180 ms | 760 ms | -35.6% |
同时,下单页资源失败率(移动弱网)从 0.78% → 0.29%;重传引发的 UI 抖动在 RUM 上明显收敛。
经验:H3 对“高 RTT + 轻微丢包”的组合特别友好;对“极端抖动/频繁掉线”的场景,收益受限,但仍优于 H2。
7)日志与回滚阈值(可操作)
区分 H3/H2: 我在 access log 里写入 "$server_protocol",Nginx 会把 HTTP/3/H2/H1 打出来,方便你在 ELK/Grafana 里做分桶。
灰度策略:
先在 5% 流量机群打开 UDP 443;
用 Alt-Svc 平滑引导客户端协商到 H3(浏览器会自行选择/回退);
设回滚阈值:H3 错误率(4xx/5xx)或上游超时率在 5 分钟窗口内高于 H2 的 1.5× 即关闸(收紧到 TCP/H2)。
8)那些真“卡人”的坑 & 我怎么过的
UDP 443 不通
症状:curl --http3 直超时,但 H2 正常。
排障:mtr -u(UDP 模式)看丢包点;检查云安全组/硬件防火墙策略;必要时抓包 tcpdump -ni eth0 udp port 443。
解决:放行 UDP 443 全路径;硬防如不支持,改走自建 LB 或升级设备固件。
MTU/分片导致首包丢
症状:弱网下 H3 比 H2 更差,重传多。
原因:部分链路对大于 1400 的 UDP 包敏感。
解决:应用/服务端将 QUIC 最大包尺寸控制在 ~1350B(多数实现默认 1200 起步,Nginx/HAProxy 一般已妥善处理);确保内网与出口设备不丢 ICMP(影响 PMTUD)。
中间件/NAT 的 UDP 超时过短
症状:H3 长连接闲置几秒就断,页面静默刷新后报错。
解决:把四层 LB/NAT 的 UDP 会话超时调到 60s+;或在应用测加心跳/轻请求。
conntrack 胀死
症状:峰值时 nf_conntrack 满载,随机丢请求。
解决:拉高表项与哈希桶,示例(按内存酌情):
echo 262144 | sudo tee /proc/sys/net/netfilter/nf_conntrack_buckets
sudo sysctl -w net.netfilter.nf_conntrack_max=1048576
CDN/WAF 不会玩 H3
症状:前面接了 CDN/WAF,客户端抓包永远走不到 H3。
解决:确认产品是否“终止并重建 H3”,或是否仅 H2 回源。不行就直连源站做 H3,CDN 只作静态/图像分发。
Docker 忘了映射 UDP
症状:本机能通,容器里不通。
解决:-p 443:443/tcp -p 443:443/udp 两个都要映射;Swarm/K8s 里同理声明 UDP Service。
9)如果你不想编译:替代路线两条
Caddy(最快交付):
curl -fsSL https://getcaddy.com | bash
# Caddyfile
shop.example.com {
encode zstd gzip
reverse_proxy 127.0.0.1:8080
}
# Caddy 默认开 HTTP/3/QUIC 与 Alt-Svc
HAProxy 2.9+(复杂场景/细控):
显式 quic 绑定、alpn h3、设置 max-frame-size 与 reuseport,并开放 UDP 443。
10)运维“交差”的清单(上线就按这个勾)
- nginx -V 有 with-http_v3_module,OpenSSL 为 3.1.x+quic
- ss -lnup 看到 udp :443 被 nginx 监听
- curl --http3 -I 返回 200/301;响应头有 Alt-Svc: h3=":443"
- ethtool -k 显示 GRO/GSO 已启
- ufw/安全组放行 443/udp(含上游硬件)
- 观测:仪表盘按协议分桶(HTTP/3 vs HTTP/2)
- 回滚预案:一键注释 listen 443 quic 并 reload,或安全组关 UDP 443
11)那个夜里 02:17 的指标折线
两点一刻,接入层 5% 灰度切换到 50%。广东移动的 p95 折线像被谁轻轻按了一下,往下走了一大截。
客服群里有人发了张截图:“结账页不卡了,刚才测了 3 次。”
我关了机房里吵闹的工控扇,站在走廊看了一眼楼下的港铁车灯。这不是某个“黑魔法”在救场,只是把协议栈、内核与负载的“路”修直了。第二天我把这套流程沉淀进 playbook,给后来人留下一条不弯的路。
附:完整命令速记(可直接复用)
# 1) NIC / 内核检查与开启
ethtool -k eth0 | egrep 'gro|gso|rx-udp|tx-udp'
sudo ethtool -K eth0 gro on gso on
sudo ethtool -K eth0 rx-udp-gro-forwarding on 2>/dev/null || true
sudo ethtool -K eth0 tx-udp-segmentation on 2>/dev/null || true
# 2) sysctl
sudo tee /etc/sysctl.d/99-quic.conf >/dev/null <<'EOF'
net.core.rmem_max = 134217728
net.core.wmem_max = 134217728
net.core.rmem_default = 8388608
net.core.wmem_default = 8388608
net.core.netdev_max_backlog = 250000
net.ipv4.udp_mem = 262144 524288 1048576
net.ipv4.udp_rmem_min = 4096
net.ipv4.udp_wmem_min = 4096
net.ipv4.ip_no_pmtu_disc = 0
net.core.default_qdisc = fq
net.ipv4.tcp_congestion_control = bbr
EOF
sudo sysctl --system
# 3) quictls + nginx(HTTP/3)
sudo apt update && sudo apt install -y build-essential ca-certificates git zlib1g-dev libpcre3-dev
cd /usr/local/src && sudo git clone --depth=1 -b openssl-3.1.7+quic https://github.com/quictls/openssl.git quictls
cd quictls && sudo ./Configure enable-ktls linux-x86_64 && sudo make -j"$(nproc)" && sudo make install_sw && sudo ldconfig
cd /usr/local/src && sudo curl -O https://nginx.org/download/nginx-1.27.2.tar.gz
sudo tar xf nginx-1.27.2.tar.gz && cd nginx-1.27.2
sudo ./configure --prefix=/etc/nginx --sbin-path=/usr/sbin/nginx --conf-path=/etc/nginx/nginx.conf \
--with-http_ssl_module --with-http_v3_module --with-pcre-jit --with-threads --with-file-aio \
--with-cc-opt='-O2 -fstack-protector-strong' --with-ld-opt='-Wl,-rpath,/usr/local/lib'
sudo make -j"$(nproc)" && sudo make install
nginx -V 2>&1 | egrep 'http_v3|OpenSSL'
# 4) 放行端口
sudo ufw allow 443/tcp
sudo ufw allow 443/udp
sudo ufw reload
# 5) 验证
curl -I --http3 https://shop.example.com
h2load -n 2000 -c 100 -m 10 --h3 https://shop.example.com/static/app.js