技术随笔 · 2026-10-15

网站部署排障与 HTTP/3 实践:从 Astro 到 Nginx 的一次完整复盘

记录个人技术博客从 Astro 构建到 Nginx 生产部署过程中遇到的实际问题,包括 CSP 限制、静态资源路径、Linux 文件描述符、Nginx 升级、HTTPS 恢复以及 HTTP/3 与 QUIC 的配置验证,并总结网站建设与运维中的工程经验。

13 分钟阅读AstroNginxHTTP3QUICWeb SecurityLinux网站部署
所属系列:前端 ↗
网站部署排障与 HTTP/3 实践:从 Astro 到 Nginx 的一次完整复盘

记录日期:2026 年 10 月 15 日
站点:https://boxijiekexun.com/
记录原则:区分已经实测的事实、根据现象提出的推断,以及尚未完成的工作。本文既是当天排障复盘,也是日后维护网站时可复用的操作手册。

一、背景:我的网站由哪些部分构成?

这是我第一次部署一个网站,也是第一次尝试让它支持 HTTP/3。为了让读者理解当天排障的背景,先简单介绍网站的构成。 我的前端知识比较匮乏,所以参考了不少文档。后面才弄了一个比较一般的网站。

当前网站是一套以内容阅读为主的个人技术博客,主要包含首页、文章、系列、归档、分类、标签、项目、课程实验、关于等页面,计划持续发布计算机科学学习笔记和项目复盘。

层次 当天确认的情况 作用
内容与前端 Astro 静态网站,文章使用 Markdown/内容集合 生成 HTML、CSS、JavaScript 与资源
本地开发 WSL 中的项目 ~/study/project/personal_archive/boxijiekexun-archive 编写、构建、测试
构建产物 dist/ 真正需要放到 Web 服务器上的静态文件
部署系统 deploy.sh、服务器端 byte-lab-release 打包、备份、激活版本及回滚入口
线上站点根目录 /var/www/byte-lab/current Nginx 实际读取的当前发布版本
Web 服务器 Ubuntu 24.04.5 LTS;Nginx 当日从 1.24.0 升级到 1.30.6 提供 HTTPS、静态文件与 HTTP/3
传输与安全 TLS、CSP、安全响应头、UFW、阿里云 ECS 安全组 资源访问控制与网络访问保护

可以把请求链路理解为:

用户浏览器
  ├─ HTML/CSS/JS 的加载与执行、CSP 检查、缓存
  └─ HTTP/1.1、HTTP/2 或 HTTP/3 的协议协商
           ↓
     阿里云安全组 → 服务器 UFW
           ↓
     Nginx 80/TCP、443/TCP、443/UDP
           ↓
     root /var/www/byte-lab/current
           ↓
     某个版本的 Astro dist/ 产物

重要认识:本地 npm run dev 正常,不等于线上 Nginx、CSP、网络协议、部署内容都正确。

二、当天排障过程

事件 1:明暗主题与搜索按钮在本地正常、上线后失效

现象:在 Astro 开发环境点击月亮按钮可以切换主题;线上主题、搜索、菜单等交互却无法工作。

证据与根因:线上安全响应头原来包含:

Content-Security-Policy: default-src 'none'; script-src 'none'; ...

script-src 'none' 禁止执行 JavaScript;原先的事件逻辑依赖页面内联 <script>。因此根因不是 CSS 颜色本身,而是浏览器不执行脚本,事件监听器没有被注册。

采取的修改(由本地 Codex 完成并报告经过构建/预览测试):

  • 将交互逻辑移到同源静态文件 public/scripts/site.js;构建后通过 /scripts/site.js 访问。
  • 主题、菜单、搜索使用统一事件委托;兼容 astro:page-load,避免页面切换后重复绑定或失效。
  • 使用 <html data-theme="light|dark"> 表示主题;用 localStorage 记住选择,首次访问可参考系统主题设置。
  • 将服务器 CSP 调整为 script-src 'self',允许加载本站脚本;若搜索通过 fetch() 请求本站 JSON 索引,同时允许 connect-src 'self'。

相关配置要点:

# 只展示本次涉及的 CSP 指令;不要拿此片段覆盖整份安全配置。
script-src 'self';
connect-src 'self';

这样无需设置宽松的 script-src 'unsafe-inline' 或 unsafe-eval。注意,以上是 CSP 字符串中的指令片段,不是可以直接独立写在 nginx.conf 顶层的 Nginx 指令。

教训:功能问题不一定是源代码逻辑问题;必须检查浏览器 Console,区分“事件没有注册”和“事件触发了但样式没变化”。

事件 2:site.js 在服务器存在,浏览器却收到 404

现象:

curl -I https://boxijiekexun.com/scripts/site.js
# HTTP/1.1 404 Not Found

但登录服务器查看,发现:

/var/www/boxijiekexun.com/scripts/site.js   # 文件确实存在

关键排查:

nginx -T 2>/dev/null | grep -nE 'server_name|root |location |alias |try_files'

配置明确写着:

server_name boxijiekexun.com;
root /var/www/byte-lab/current;

因此文件在磁盘上存在,不代表位于 Nginx 正在提供服务的目录中。此前检查的 /var/www/boxijiekexun.com/ 不是站点实际根目录;正式根目录为 /var/www/byte-lab/current。

进一步发现:项目已有正式的 deploy.sh:

npm run build
npm run check:site
打包 dist/ 为发布包
单独备份源码
通过 site-deploy 用户上传服务器
byte-lab-release activate <版本标识>

部署脚本将发布包上传到 /var/www/byte-lab/uploads/,源码备份到 /var/www/byte-lab/source-backups/,然后通过服务器发布工具激活版本。脚本还提供 ./deploy.sh --rollback 入口;本日没有提供一次完整回滚成功的实测记录。

正确做法:先确认本地变更,再使用项目已有的发布流程发布整个构建产物,不要长期手工将某个 JS 文件复制到错误目录,也不要绕过版本机制直接覆盖线上 current。

# 本地 WSL 项目目录
npm run build
npm run check:site
./deploy.sh

发布前必须检查是否存在未准备上线的文章、图片或页面修改;./deploy.sh 发布的是整个当前构建版本,不仅仅是一个 JS 文件。

验证线上资源:

curl -I https://boxijiekexun.com/scripts/site.js

当天后续浏览器 Network 中,site.js 已返回 200 或缓存协商的 304,说明文件已可被浏览器获取。

事件 3:服务器命令“全部没有输出”并不等于系统坏了

现象:nginx -v 有输出,而 echo、cat /etc/os-release、apt-cache policy nginx 等命令没有显示正常输出。

关键排查:

ls -l /proc/$$/fd/1 >&2
ls -l /proc/$$/fd/2 >&2

当时结果为:

fd/1 -> pipe:[1038521]
fd/2 -> /dev/pts/0

解释:文件描述符 1 是标准输出 stdout,2 是标准错误 stderr。stdout 被接到了管道而不是当前终端,很多正常信息因此不可见;Nginx 版本输出或 APT 警告经由 stderr,仍然可见。

在确认自己处于交互式 SSH 终端后,执行:

exec 1>/dev/tty

随后 stdout 恢复显示,证实不是 /etc/os-release 或 APT 损坏。管道最初为什么出现,没有在本次对话中最终查明。

教训:Linux 排障时,先分清“程序没有结果”与“结果去了哪里”;熟悉 stdin(0)、stdout(1)、stderr(2) 和重定向,能避免错误地判断软件包或系统故障。

事件 4:把 Nginx 升级到支持 HTTP/3 的版本

升级前确认:

Ubuntu 24.04.5 LTS
Nginx 1.24.0-2ubuntu7.18(Ubuntu 软件包)
nginx -V 无 --with-http_v3_module

为了让源站原生支持 HTTP/3,配置 nginx.org 官方 Stable APT 仓库。apt-cache policy nginx 显示候选版本 1.30.6-1~noble。

正式安装前,先用模拟命令查看包变化:

apt-get -s install nginx

模拟结果显示:将移除 Ubuntu 的 nginx-common,升级 nginx 到 1.30.6-1~noble,未显示一系列其他服务被移除。

实际升级完成后:

nginx -v
nginx -V 2>&1 | grep -- '--with-http_v3_module'
nginx -t

分别确认了版本 1.30.6、HTTP/3 模块已编译、当前磁盘配置语法正常。

升级中的意外:安装程序替换了部分 Nginx 配置文件;Nginx 服务一度停止。执行 systemctl start nginx 后,仅 TCP 80 在监听,TCP 443 未监听,导致 HTTPS 无法连接。

后来检查发现:

  • nginx -T 能看到 /etc/nginx/sites-enabled/boxijiekexun.com 被包含。
  • 磁盘配置中已经有 listen 443 ssl;。
  • 执行 nginx -t && systemctl reload nginx 后,TCP 443 恢复监听。
  • 本机直连 HTTPS 首页返回 200 OK,原有 CSP 也还在。

因此,“新版配置没有包含 sites-enabled”曾是一个合理的排查假设,但最终证据证明不是当时配置文件的实际状态。能确认的是运行中的 Nginx 在 reload 后才恢复 HTTPS;其更深层的启动时状态差异,本次没有取得足够证据进一步归因。

教训:

  1. nginx -t 只说明配置语法正确,不能证明进程正在运行,也不能证明 TCP 443 正在监听。
  2. nginx -T 显示读取到的配置,不能替代 ss 对真实监听端口的检查。
  3. 跨发行方的软件包迁移会引入配置、服务单元、模块、默认文件的差异;升级前要备份、模拟,升级后要检查服务和站点。

事件 5:HTTP/3 / QUIC 从“能配置”到“实际生效”

HTTP/1.1、HTTP/2 走 TCP;HTTP/3 使用 QUIC(UDP)。安装了 HTTP/3 模块并不意味着它会自动启用。

当天站点具有两个 HTTPS 虚拟主机:

  • www.boxijiekexun.com:HTTPS 访问后重定向到主域名。
  • boxijiekexun.com:提供 Astro 静态网站,根目录 /var/www/byte-lab/current。

Nginx 配置增加了 QUIC 监听,并保留已有的 TCP HTTPS 监听。配置的核心形态为:

# 示例片段:保留既有证书、重定向和 location 配置。
# 在共享同一地址端口的虚拟主机中,只对对应监听地址设置一次 reuseport。

# www HTTPS server
listen 443 ssl;
listen [::]:443 ssl;
listen 443 quic reuseport;
listen [::]:443 quic reuseport;
http2 on;

# 主域名 HTTPS server
listen 443 ssl;
listen [::]:443 ssl;
listen 443 quic;
listen [::]:443 quic;
http2 on;

还通过 Alt-Svc 告知浏览器服务器支持 HTTP/3:

add_header Alt-Svc 'h3=":443"; ma=300' always;

初期设 ma=300 秒便于试验,稳定后可视情况延长。Alt-Svc 只是协议通告,不是 QUIC 握手成功的证据。

安全响应头使用 /etc/nginx/snippets/byte-lab-security.conf 统一维护,并在有自定义 add_header 的 location 中重新 include。这是因为 Nginx 的响应头继承规则可能导致在 location 中新增缓存头时丢失上级安全头。修改这份 snippet 时必须保留 HSTS、CSP 等现有指令。

网络层同时需要放通:

入口 用途 状态
TCP 80 HTTP 与重定向 已有
TCP 443 HTTPS / HTTP/1.1 / HTTP/2 已有
UDP 443 QUIC / HTTP/3 当日新增

只在服务器运行 ufw allow 443/udp 不够;还要在阿里云 ECS 安全组入方向放行 UDP 443。反过来,只开安全组而不让 Nginx 监听 UDP 443 也不够。

按层验证:

nginx -t && systemctl reload nginx
ss -ltnp | grep ':443'  # TCP HTTPS
ss -lunp | grep ':443'  # UDP QUIC

# 绕过公网 DNS 与代理,直接访问本机对应 HTTPS 虚拟主机
curl -kI --noproxy '*' \
  --resolve boxijiekexun.com:443:127.0.0.1 \
  https://boxijiekexun.com/

# 仅检查 HTTP/3 广告头
curl -sI https://boxijiekexun.com/ | grep -i alt-svc

当日结果:ss 已显示 IPv4/IPv6 的 UDP 443 监听,本机 HTTPS 返回 200 OK 且出现 Alt-Svc: h3=":443"; ma=300。

一个重要误操作:直接在 Shell 输入 listen 443 quic reuseport;,出现 Command 'listen' not found。这是因为 listen 是 Nginx 配置指令,应写进 /etc/nginx/sites-enabled/...,而不是在 Linux 命令行执行。

最终客户端证据:

  • Chrome 早期 Network 中多数资源为 http/1.1;增加 http2 on; 后部分请求显示 h2。
  • Edge 的 Network 面板中,本站 site.js 请求的 Protocol 显示 h3,状态为 304。这证明至少存在一条实际使用 HTTP/3 的浏览器请求。
  • 304 Not Modified 是正常缓存协商结果,不是失败。
  • 当前服务器 curl 不支持 --http3-only,因此没有通过该命令完成 HTTP/3 验证;浏览器的 h3 记录是本次实际证明。

不同浏览器使用不同协议,可能与连接复用、Alt-Svc 学习过程、代理策略和网络环境有关。不能仅凭浏览器显示 http/1.1 就断定服务器 HTTP/3 配置失败。

事件 6:背景 MP4 被 CSP 拦截

现象:Chrome Console 报错,指出 /videos/hero-background.mp4 违反 CSP,且没有显式设置 media-src,因此回退到 default-src 'none'。

处理:在现有 CSP 中加入:

media-src 'self';

其含义是允许加载本站同源的音视频文件,不需要设置过宽的 default-src *。后续 Network 中视频请求出现 200 或 206 Partial Content,说明可正常请求。对于视频,206 通常表示按 Range 分段读取,不是错误。

归纳当天 CSP 的关键指令:

指令 主要作用 网站对应场景
default-src 'none' 默认拒绝未显式放行的资源 严格的安全基线
script-src 'self' 同源 JavaScript site.js、主题、搜索、菜单
connect-src 'self' 同源网络请求 搜索索引等 Fetch 请求
media-src 'self' 同源音视频 首页 MP4 背景
img-src 'self' 同源图片 封面、头像、海报
style-src 'self' 'unsafe-inline' 样式文件与特定内联样式 当前代码高亮/样式设计所需,未来可继续收紧

每新增一种资源类型,都要检查 CSP 是否允许。优先最小范围增加指定指令,避免为了图省事删除 CSP。

三、可复用的排查流程

当线上某个功能失效时,按以下顺序逐层缩小范围:

  1. 看浏览器 Console:是否有 CSP、JavaScript 异常、资源加载错误?
  2. 看 Network:请求 URL、状态码、Content-Type、Protocol、缓存命中各是什么?
  3. 看构建产物:dist/ 中有没有目标文件?HTML 中引用的路径对不对?
  4. 看实际部署版本:Nginx root 到哪里?current 指向哪个 release?
  5. 看 Nginx 生效配置:nginx -T 是否包含对应 server/location/header?
  6. 看服务与监听:systemctl status nginx、ss -ltnp、ss -lunp 是否符合预期?
  7. 看网络边界:UFW、阿里云安全组、代理、DNS 是否允许流量通行?
  8. 最后验证用户行为:Chrome/Edge 的实际页面、搜索、主题、移动端和 HTTP 协议。

常用检查命令速查

# 构建与检查(本地)
npm run build
npm run check:site
npm run preview

# 发布与回滚入口(本地;使用前确认实际变更与服务器状态)
./deploy.sh
./deploy.sh --rollback

# Nginx(服务器)
nginx -v
nginx -V 2>&1 | grep -- '--with-http_v3_module'
nginx -t
nginx -T 2>&1 | grep -nE 'server_name|root |listen |Content-Security-Policy'
systemctl status nginx --no-pager
ss -ltnp | grep -E ':(80|443)\b'
ss -lunp | grep ':443'

# 检查证书与请求实际落点(-k 仅用于本机排障,不应作为验证公网证书有效性的手段)
curl -kI --noproxy '*' \
  --resolve boxijiekexun.com:443:127.0.0.1 \
  https://boxijiekexun.com/

# 静态资源与响应头
curl -I https://boxijiekexun.com/scripts/site.js
curl -I https://boxijiekexun.com/

# 版本化目录
readlink -f /var/www/byte-lab/current

curl 输出中若出现 HTTP/1.1 200 Connection established,这可能只是 HTTP 代理成功建立了隧道;真正的网站响应要看后面的状态行。排障时要区分代理回应与源站回应。

四、当天的网站内容检查:已发现的问题与待办

当天对网站主要栏目做过一次公开页面审查。它提供了一份优化线索,其中部分抓取可能有缓存,正式修复前应重新检查最新源代码和线上内容。

观察到的问题或待办 当天报告中的具体例子 建议处理
阅读时长不一致 B+Tree 列表页约 21 分钟、详情页约 4 分钟;其他文章也出现类似差异 统一一个阅读时长计算函数和数据源
分类描述不准确 「技术随笔」分类说明疑似误用了 B+Tree 文章摘要 分类 description 单独管理,不从某篇文章借用
文章排序不一致 分类详情中文章未按日期顺序排列 所有文章聚合页面共用稳定排序函数
内容仍在建设 项目、课程实验有占位内容 逐步发布真实项目案例和实验记录,不虚构数据
搜索需求 期望输入标题/标签关键词,支持中文、英文、大小写无关与模糊匹配 使用真实文章构建搜索索引,并做生产预览与浏览器测试
前端回归测试 主题、搜索、移动端、文章目录、视频背景等 用 Playwright 等工具测试真实用户操作与 CSP 兼容性
SEO 与性能 Sitemap、robots.txt、RSS、背景 MP4 资源、移动端体验 优先实测,不因工具无法读取资源就断定其不存在

注意:本次确认成功的是 HTTP/3 的某条浏览器请求、站点 HTTPS 可用、视频及 JS 可以被浏览器请求;并未证明全部模糊搜索需求已经开发完成,也没有完成跨所有页面的端到端测试。

五、由今天的实践总结的网站建设经验

1. 把“写页面”升级为“维护一套系统”

个人网站远不止 Astro 页面和 CSS。完成一个真正可持续访问的网站,还涉及域名、证书、网络端口、Nginx、静态资源路径、安全策略、部署、缓存、监控与备份。

初学时容易把所有问题都归结为“前端代码有 Bug”。今天的主题按钮、脚本 404 和 HTTP/3 正好证明:同一个网页的故障可能来自不同层次,应该从观察到的证据逐层定位,而不是直接重写代码。

2. 明确本地、构建、线上三个环境的边界

要区分:

  • src/、public/:代码与静态素材的来源。
  • dist/:Astro 构建后的真正网站。
  • /var/www/byte-lab/current:Nginx 此刻正在提供服务的发布版本。

每次修改后至少问自己:代码改了吗?构建更新了吗?部署的是这个构建吗?浏览器请求到的是这个版本吗?

3. 将部署视为可追踪的发布,而不是复制文件

已有的 deploy.sh、发布包、源码备份和回滚入口,是值得继续保持的工程习惯。不要同时维护“手动 rsync 到旧目录”和“正式 release 系统”两套流程,否则很容易出现文件在服务器、网站却读不到的困惑。

推荐流程:本地修改 → 本地测试 → Git 提交 → 生产构建及检查 → 审核将发布的变更 → ./deploy.sh → 浏览器验收 → 必要时回滚。

4. 安全应该是最小权限,而不是粗暴关闭功能

CSP 从 script-src 'none' 演进到 script-src 'self',再按需增加 connect-src 'self'、media-src 'self',是“默认拒绝、按实际需要逐项开放”的例子。

同理,HTTP/3 只需新增 UDP 443,不能为追求兼容而开放任意端口;已有的 SSH、RDP 公网放行也应做一次单独安全审查,尤其是无必要的 RDP 3389。调整 SSH 规则之前要确认备用管理途径,避免把自己锁在服务器外。

5. 升级基础设施之前准备回退方案

Nginx 从 Ubuntu 官方包迁移到 nginx.org 包,本质上是一次生产环境变更。即使 apt-get -s 没有发现复杂依赖风险,仍发生了服务停止、HTTPS 暂时不可用的情况。

未来应固定升级流程:备份配置与软件版本 → 检查安装来源及模块 → 模拟升级 → 选维护窗口 → 执行升级 → 测试配置 → 检查进程与监听 → 本机访问 → 公网访问 → 查看错误日志。验证不能只停留在 nginx -t。

6. 以真实浏览器作为最终验收

当日 Alt-Svc 返回正确、UDP 443 也已监听,但 Chrome 起初仍显示 HTTP/1.1。直到 Edge 的实际请求显示 h3,才具备 HTTP/3 生效的直接证据。

同样,主题按钮不能只看构建是否成功;必须点击、刷新、跳页。视频不能只看文件是否存在;还要看 CSP 与播放是否正常。搜索不能只看弹窗打开;还要输入中文、英文、错误拼写并点击真实结果。

7. 先解决内容价值,再增加复杂功能

HTTP/3 对个人技术博客是一次有意义的学习与运维实践,但对访客而言,真实项目案例、易读的技术文章、准确的搜索结果、合理的移动端布局,可能比协议版本更重要。

下一阶段值得优先做的事:

  1. 修复阅读时长、文章排序、分类描述等一致性问题。
  2. 完成并测试中英文标题/标签模糊搜索。
  3. 发布真实项目与课程实验内容,完善学习笔记质量。
  4. 建立 Playwright 等自动化回归测试,覆盖搜索、主题、导航和响应式。
  5. 用 Lighthouse、浏览器 Network 与真实设备评估视频、图片和页面性能。
  6. 形成简短的运维 README:部署、回滚、证书、CSP、Nginx 检查命令。

六、发布前的个人检查清单

  • 本次改动是否都已审阅,并有可回退的 Git 提交?
  • npm run build、npm run check:site、必要的自动化测试是否通过?
  • npm run preview 中的搜索、主题、导航和文章页面是否正常?
  • 新增图片、视频、字体或脚本是否与 CSP 兼容?
  • 新文章的标题、日期、标签、系列、封面、Slug 是否正确?
  • 是否确认使用唯一的 deploy.sh 正式发布路径?
  • 部署后主要页面、CSS、JS、视频是否返回正确的状态码?
  • Console 是否没有网站自身造成的关键报错?
  • 网站原有 HTTPS、安全头和 HTTP/2 回退是否正常?
  • 遇到故障是否知道查看哪份日志、如何暂停或回滚?

七、结语

今天最大的收获,不是让网站从 HTTP/1.1 升级到 HTTP/3,而是形成了一套更可靠的排障思路:先观察,再定位;先验证,再修改;先备份,再升级;最后用真实访问证明修复生效。

个人博客本身就是一个很好的工程练习场:既能承载学习成果,也能真实经历构建、发布、网络、Linux、Web 安全与运维中的问题。只要把每一次错误记录为可复用的经验,这个网站的价值就不只是作品展示,而是一套持续成长的工程实践。