搭建HTTP3网站的最终尝试以及成果分享

前言

对于HTTP/3一直有个执念:前段时间(9月份)了解HTTP/3后,在本地虚拟机测试,始终达不到要求;但又由于没有单独的多余服务器测试(主要是这台服务器陪伴已久,里面的数据以及环境都比较复杂,测试难度比较高),导致一直搁置着。

最近新申请了个域名,搭配服务器也比较优惠,就搞了台国内基础配置的云服务器。这样万事俱备,直接开干!

成果

网站预览:HTTP/3 Adventure

首页

导航栏

HTTP/3检测

悬浮球查看当前网络协议

卡片特效

网站设计

这个网站90%基于Google Gemini设计。

当时只是想简单设计个页面,需求也就寥寥几个字:

我准备做一个HTTP/3的网站,帮我写一个首页的html文件,大致介绍网站的介绍,功能,以及其他一些http/3方面的支持,页面做美观大气

分别发给了Google Gemini以及ChatGPT

  • Gemini设计的页面基本和现在看到的区别不大;

  • ChatGPT设计的页面非常简单,如果不对比Gemini给的页面,那么还能接受,毕竟只是想简单测试一下,对比之后就显得粗糙异常,封建时代城里人与农村人的感觉,落后好几个时代

没有对比,就没有伤害。

当然,也侧面验证了一个事实,现在的Gemini真的在AI领域No.1。(⌚️11/30/2025)

心路历程:

最开始,只想拿个检测页面看看HTTP/3的支持程度;

看了Gemini的界面,感觉好好设计一下可以当个网站玩玩;

逐步完善各个页面;

增加网站的HTTP/3检测功能(本来是纯前端页面,现在又要加后端),而且后端需要基于支持HTTP/3的cURL;

最终,就是现在的样子。

PS:为什么必须通过后端检测网站是否支持HTTP/3?

前端页面的检测只能获取到网站的头部信息,即: Alt-Svc 'h3=":443"; ma=86400' ,但是有些网站即使有这个头部信息,也并非真正意义上的支持HTTP/3,因此想要完全确保HTTP/3的检测真实有效,就只能通过支持HTTP/3的cURL来获取网站的信息。

基础环境

服务器: Ubuntu Server (2核 CPU / 2GB RAM,国内机房)

Web服务器: 基于目前最新的Nginx 1.29.3版本

TLS库: BoringSSL(用于编译Nginx)

语言: 前端 HTML + 后端 PHP

cURL: 基于OpenSSL、nghttp3、ngtcp2编译以支持HTTP/3

网站源码

网站源码已经分享出来,想尝试部署的同学可以跟着以下搭建步骤进行部署。

网站源码地址:GitHub - luuaiyan/HTTP-3-Website: HTTP/3个人网站的代码,带有HTTP/3网站测试功能

全程无坑,如果不改编译路径(/root/nginx-build)以及网站路径(/var/www/http)的话,全程复制命令就行。

搭建步骤

为了方便操作,相关程序均下载/克隆至 /root/nginx-build 目录。

一、环境部署

安装编译工具及相关依赖:

sudo apt install -y git gcc make libpcre3 libpcre3-dev zlib1g-dev libssl-dev libbrotli-dev libzstd-dev cmake g++ build-essential autoconf libtool pkg-config libpsl-dev

二、基于BoringSSL编译Nginx

首先,编译BoringSSL:

mkdir /root/nginx-build
cd /root/nginx-build
git clone https://gitee.com/mirrors/boringssl.git  #  BoringSSL
# git clone https://boringssl.googlesource.com/boringssl  #  也可以克隆官方链接的仓库

cd boringssl
mkdir build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Release
make -j$(nproc)

然后,编译Nginx:

cd /root/nginx-build
wget https://nginx.org/download/nginx-1.29.3.tar.gz  #  自行下载最新版的Nginx
tar -zxvf nginx-1.29.3.tar.gz  #  解压

编译配置:

cd nginx-1.29.3
./configure \
    --prefix=/etc/nginx \
    --sbin-path=/usr/sbin/nginx \
    --conf-path=/etc/nginx/nginx.conf \
    --http-client-body-temp-path=/var/cache/nginx/client_body_temp \
    --http-proxy-temp-path=/var/cache/nginx/proxy_temp \
    --http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp \
    --http-scgi-temp-path=/var/cache/nginx/scgi_temp \
    --http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp \
    --error-log-path=/var/log/nginx/error.log \
    --http-log-path=/var/log/nginx/access.log \
    --pid-path=/run/nginx.pid \
    --lock-path=/run/nginx.lock \
    --modules-path=/usr/lib/nginx/modules \
    --with-http_realip_module \
    --with-http_addition_module \
    --with-http_sub_module \
    --with-http_dav_module \
    --with-http_flv_module \
    --with-http_mp4_module \
    --with-pcre \
    --with-pcre-jit \
    --with-http_gunzip_module \
    --with-http_gzip_static_module \
    --with-http_stub_status_module \
    --with-http_random_index_module \
    --with-http_secure_link_module \
    --with-http_degradation_module \
    --with-http_slice_module \
    --with-http_auth_request_module \
    --with-mail \
    --with-mail_ssl_module \
    --with-stream \
    --with-stream_realip_module \
    --with-stream_ssl_module \
    --with-stream_ssl_preread_module \
    --with-http_ssl_module \
    --with-http_v2_module \
    --with-http_v3_module \
    --with-cc-opt="-I/root/nginx-build/boringssl/include" \
    --with-ld-opt="-L/root/nginx-build/boringssl/build -L/root/nginx-build/boringssl/build/crypto -lssl -lcrypto -lstdc++ -lpthread -lm" \
    --user=www-data \
    --group=www-data \
    --with-threads \
    --with-file-aio \
    --with-debug

完成编译:

make -j$(nproc)
make install

通过 nginx -V查看输出:

配置Nginx:

mkdir -p /var/cache/nginx/client_body_temp  # 缺少这个临时目录

创建Nginx系统服务:

vim /etc/systemd/system/nginx.service

内容为:

[Unit]
Description=A high performance web server and a reverse proxy server
Documentation=man:nginx(8)
After=network-online.target remote-fs.target nss-lookup.target
Wants=network-online.target

[Service]
Type=forking
PIDFile=/run/nginx.pid
ExecStartPre=/usr/sbin/nginx -t -q -g 'daemon on; master_process on;'
ExecStart=/usr/sbin/nginx -g 'daemon on; master_process on;'
ExecStartPost=/bin/sleep 0.1
ExecReload=/usr/sbin/nginx -g 'daemon on; master_process on;' -s reload
ExecStop=-/sbin/start-stop-daemon --quiet --stop --retry QUIT/5 --pidfile /run/nginx.pid
TimeoutStopSec=5
KillMode=mixed

[Install]
WantedBy=multi-user.target

加载并启动Nginx:

systemctl enable nginx  # 开机启动
systemctl start nginx  # 启动
systemctl status nginx  # 查看状态

三、安装PHP-FPM

add-apt-repository ppa:ondrej/php
apt install php8.5-fpm

四、配置和编译支持HTTP/3的cURL

定义以下路径变量:

变量名 路径 作用
OPENSSL_PREFIX /root/nginx-build/openssl/build OpenSSL 3.6.0 的安装目录
NGHTTP3_PREFIX /root/nginx-build/nghttp3/build nghttp3 的安装目录
NGTCP2_PREFIX /root/nginx-build/ngtcp2/build ngtcp2 的安装目录
CURL_PREFIX /root/nginx-build/curl/install cURL 的最终安装目录

具体的编译步骤:

第 1 步:配置和编译 OpenSSL

# 1. 切换到主构建目录
cd /root/nginx-build
# 2. 克隆 OpenSSL
git clone --depth 1 -b openssl-3.6.0 https://github.com/openssl/openssl openssl

# 3. 编译 OpenSSL
cd openssl
./config --prefix=/root/nginx-build/openssl/build --libdir=lib
make -j$(nproc)
make install

第 2 步:编译 nghttp3

# 1. 切换到主构建目录
cd /root/nginx-build
# 2. 克隆 nghttp3
git clone https://github.com/ngtcp2/nghttp3
cd nghttp3

# 3. 准备配置
git submodule update --init
autoreconf -fi

# 4. 配置 nghttp3
./configure --prefix=/root/nginx-build/nghttp3/build --enable-lib-only
make -j$(nproc)
make install

第 3 步:编译 ngtcp2

# 1. 切换到主构建目录
cd /root/nginx-build
# 2. 克隆 ngtcp2
git clone https://github.com/ngtcp2/ngtcp2
cd ngtcp2

# 3. 准备配置
autoreconf -fi

# 4. 配置 ngtcp2 (关键步骤:设置 PKG_CONFIG_PATH 和 LDFLAGS)
OPENSSL_PREFIX=/root/nginx-build/openssl/build
NGHTTP3_PREFIX=/root/nginx-build/nghttp3/build

./configure \
  PKG_CONFIG_PATH="$OPENSSL_PREFIX/lib/pkgconfig:$NGHTTP3_PREFIX/lib/pkgconfig" \
  LDFLAGS="-Wl,-rpath,$OPENSSL_PREFIX/lib" \
  --prefix=/root/nginx-build/ngtcp2/build \
  --enable-lib-only \
  --with-openssl

make -j$(nproc)
make install

第 4 步:编译支持 HTTP/3 的 cURL

# 1. 切换到主构建目录
cd /root/nginx-build
# 2. 克隆 cURL
git clone https://github.com/curl/curl
cd curl

# 3. 准备配置
autoreconf -fi

# 4. 配置 cURL
OPENSSL_PREFIX=/root/nginx-build/openssl/build
NGTCP2_PREFIX=/root/nginx-build/ngtcp2/build

LDFLAGS="-Wl,-rpath,$OPENSSL_PREFIX/lib" \
PKG_CONFIG_PATH="$NGTCP2_PREFIX/lib/pkgconfig" \
./configure \
  --prefix=/root/nginx-build/curl/install \
  --with-openssl=$OPENSSL_PREFIX \
  --with-nghttp3=$NGHTTP3_PREFIX \
  --with-ngtcp2 

make -j$(nproc)
make install

验证

运行以下命令来确认 cURL 是否支持 HTTP/3:

/root/nginx-build/curl/install/bin/curl -V

在输出中,应该看到 Features: ... HTTP3 ...,说明编译成功了!

参考资料:HTTP/3 with curl官方文件

五、修改 PATH 环境变量 (推荐)

可以将上述自定义安装目录添加到 PATH 环境变量的最前面。这样,直接输入 curl 时,系统会优先找到该版本,但不会影响其他系统工具(因为它们通常使用绝对路径)。

# 临时生效(只对当前终端会话有效)
export PATH="/root/nginx-build/curl/install/bin:$PATH"

# 永久生效(编辑 ~/.bashrc 或 ~/.zshrc 文件)
echo 'export PATH="/root/nginx-build/curl/install/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc

# 验证:现在直接输入 curl 应该显示您的新版本
curl -V

系统层使用,如果平时在系统中不需要http3的curl功能,可以不用添加。

六、部署 cURL 到 Nginx/PHP 环境

一般默认 PHP-FPM 进程是以 www-data 用户运行的,但是系统层的权限为 root ,无法直接调用。

因此,计划将 cURL 部署到 /var/www/http3-curl 目录。

第一步:文件部署和权限设置

1.1 移动文件和创建目录

将 cURL 可执行文件 (curl) 及其所有依赖的动态链接库(libcurl.so, OpenSSL, QUIC 库)复制到目标目录。

# 1. 定义源路径 (您的编译安装目录)
CURL_INSTALL_DIR="/root/nginx-build/curl/install"
OPENSSL_LIB_SOURCE="/root/nginx-build/openssl/build/lib"
NGTCP2_LIB_SOURCE="/root/nginx-build/ngtcp2/build/lib"
NGHTTP3_LIB_SOURCE="/root/nginx-build/nghttp3/build/lib"

# 2. 定义目标部署路径
TARGET_DIR="/var/www/http3-curl"
PHP_USER="www-data"

# 3. 创建目录结构
sudo mkdir -p $TARGET_DIR/bin
sudo mkdir -p $TARGET_DIR/lib

# 4. 复制 cURL 可执行文件
# 这是 PHP 脚本中调用的程序路径
sudo cp $CURL_INSTALL_DIR/bin/curl $TARGET_DIR/bin/

# 5. 复制 cURL 动态链接库 (libcurl.so)
# 这一步解决了 'undefined symbol' 错误
sudo cp $CURL_INSTALL_DIR/lib/libcurl.so* $TARGET_DIR/lib/

# 6. 复制所有依赖的动态链接库 (OpenSSL, ngtcp2, nghttp3)
sudo cp $OPENSSL_LIB_SOURCE/*.so* $TARGET_DIR/lib/
sudo cp $NGTCP2_LIB_SOURCE/*.so* $TARGET_DIR/lib/
sudo cp $NGHTTP3_LIB_SOURCE/*.so* $TARGET_DIR/lib/

1.2 设置文件所有权和权限

确保 PHP-FPM 运行用户 (www-data) 对这些文件拥有读和执行权限。

# 1. 设置所有者为 PHP 运行用户
sudo chown -R $PHP_USER:$PHP_USER $TARGET_DIR

# 2. 确保目录和文件有执行权限
sudo chmod -R 755 $TARGET_DIR

第二步:PHP 后端调用配置 (check.php)

check.php 脚本中,不仅需要调用正确的 cURL 路径,更关键的是要设置 LD_LIBRARY_PATH 环境变量,告诉系统去哪里加载那些非标准的动态链接库。

2.1 完整的 check.php 脚本

确保 /var/www/http3/check.php 文件内容如下所示:

<?php
/**
 * HTTP/3 真实连接验证后端 API
 * 文件路径: /var/www/http3/check.php
 */

header('Content-Type: application/json');

// ----------------------------------------------------
// 路径定义:使用第一步部署的新路径
// ----------------------------------------------------
$curl_path = '/var/www/http3-curl/bin/curl'; 
$lib_path = '/var/www/http3-curl/lib'; 

$url = isset($_GET['url']) ? trim($_GET['url']) : '';

if (empty($url) || !preg_match('/^https:\/\//i', $url)) {
    http_response_code(400);
    echo json_encode(['status' => 'error', 'message' => 'URL无效,必须以 https:// 开头。']);
    exit;
}

// 权限和可执行性检查
if (!is_executable($curl_path)) {
    http_response_code(500);
    echo json_encode(['status' => 'error', 'message' => '服务器错误:cURL 工具无法执行。请检查文件权限和用户所有权。']);
    exit;
}

// ----------------------------------------------------
// 核心逻辑:设置环境变量并执行 cURL 命令
// ----------------------------------------------------

// 关键:通过 LD_LIBRARY_PATH 告诉系统在哪里找到非标准路径下的 OpenSSL/QUIC 库
$cmd = "LD_LIBRARY_PATH=" . escapeshellarg($lib_path) . " " . 
       escapeshellarg($curl_path) . " -I --http3 --silent " . escapeshellarg($url) . " 2>&1";

$output = shell_exec($cmd);

// ----------------------------------------------------
// 解析 cURL 输出 (与之前提供的逻辑相同)
// ----------------------------------------------------
$is_http3_success = false;
$response_status_message = '未知';
$raw_headers = '';

if (strpos($output, 'HTTP/3') !== false && (strpos($output, '200') !== false || strpos($output, '302') !== false)) {
    $is_http3_success = true;
    $response_status_message = 'HTTP/3 (QUIC) 连接成功';

} elseif (strpos($output, 'HTTP/2') !== false || strpos($output, 'HTTP/1.1') !== false) {
    $is_http3_success = false;
    $response_status_message = '连接成功,但回退到 TCP 协议 (H3 尝试失败)';

} elseif (preg_match('/HTTP\/\d.\d\s+(\d+)/', $output, $matches)) {
    $is_http3_success = false;
    $response_status_message = "连接成功,状态码: " . $matches[1] . " (非 H3)";

} else {
    $is_http3_success = false;
    $response_status_message = '连接失败或 QUIC 握手错误 (请检查 UDP 443)';
}

$header_start = strpos($output, 'HTTP/');
if ($header_start !== false) {
    $raw_headers = trim(substr($output, $header_start));
} else {
    $raw_headers = trim($output);
}

$response_data = [
    'status' => $is_http3_success ? 'success' : 'failure',
    'protocol' => $is_http3_success ? 'HTTP/3' : 'Failed',
    'message' => $response_status_message,
    'details' => $raw_headers,
];

echo json_encode($response_data);

?>

第三步:Nginx 配置和测试

Nginx 只需要确保它能将对 check.php 的请求正确转发给 PHP-FPM 处理即可。

3.1 Nginx 配置确认

确保 Nginx 网站配置(例如 /etc/nginx/sites-enabled/http3.conf)中包含正确的 PHP 处理器块:

server {
    # ... 其他配置 ...

    location ~ \.php$ {
        # 确保 fastcgi_pass 指向正确的 PHP-FPM socket 或地址
        # 如果是 Ubuntu/Debian:
        fastcgi_pass unix:/var/run/php/php8.5-fpm.sock; 
        # 如果是 CentOS/RHEL:
        # fastcgi_pass 127.0.0.1:9000;

        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }

    location = /check-http3-api {
        rewrite ^ /check.php last;
    }

    # ... 其他配置 ...
}

3.2 重启服务

重启 Nginx 和 PHP-FPM 服务:

sudo systemctl reload nginx
sudo systemctl restart php8.5-fpm

七、Nginx配置模版

基本的配置模版为:

server {
    listen 80;
    listen [::]:80;
    server_name http3.xxx.cn;
    index index.php index.html index.htm;
    return 301 https://http3.xxx.cn$request_uri;
}

server {
    listen 443 ssl;
    listen [::]:443 ssl;
    listen 443 quic reuseport;
    listen [::]:443 quic reuseport;

    root /var/www/http3;
    server_name http3.xxx.cn;

    http2 on;

    index index.htm index.html;

    #ssl_protocols TLSv1.3;
    ssl_certificate     certs/http3.xxx.cn/http3.xxx.cn_bundle.pem;
    ssl_certificate_key certs/http3.xxx.cn/http3.xxx.cn.key;

    location / {
        try_files $uri $uri/ $uri.html =404;
        add_header Alt-Svc 'h3=":443"; ma=86400';  # 必须的头部信息
    }

    # PHP-FPM
    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        include fastcgi_params;
        fastcgi_pass unix:/run/php/php8.5-fpm.sock;
        fastcgi_param PATH_INFO $fastcgi_path_info;
    }

    # 必须的后端API转发
    location = /check-http3-api {
        rewrite ^ /check.php last;
    }

}

八、测试

访问网站吧~

提醒

由于HTTP/3是基于QUIC的UDP协议实现的,也就是服务器的UDP端口必须打开。

还有就是第一次握手大概率是基于TCP的HTTP/2访问,识别到头部信息支持QUIC后,会在下次优先使用HTTP/3进行访问,因此,如果一次访问不成功,可以关闭浏览器或者清空缓存重新访问。

总结

前前后后用了大概一天多的时间,大致可以分为两部分:

第一部分: 前端页面制作

第二部分: 后端cURL with HTTP/3 的搭建

如果是纯前端页面的话太简单了,难点在编译支持HTTP/3的cURL上面,在这方面卡了我很长时间,最终自己查阅官方文档才搞定。

HTTP/3的普及其实非常一般,很多知名公司的网站仍然采用HTTP/2的标准,毕竟基于TCP协议的传输非常可靠,不会造成丢包,甚至比如Nginx的官网这种纯前端架构,仍然使用HTTP/1.1。

因此,HTTP/3只是尝鲜体验,对于内容简单的网站提升有限,但是当网站有一定规模之后,为了保持网站的稳定可靠运行以及访问者的体验,更倾向于基于TCP协议的HTTP/2。

如何取舍,未来会给我们答案。

0 0 投票数
文章评分
订阅评论
提醒
guest
0 评论
最旧
最新 最多投票
内联反馈
查看所有评论
滚动至顶部
0
希望看到您的想法,请您发表评论x