记录日期: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;其更深层的启动时状态差异,本次没有取得足够证据进一步归因。
教训:
nginx -t只说明配置语法正确,不能证明进程正在运行,也不能证明 TCP 443 正在监听。nginx -T显示读取到的配置,不能替代ss对真实监听端口的检查。- 跨发行方的软件包迁移会引入配置、服务单元、模块、默认文件的差异;升级前要备份、模拟,升级后要检查服务和站点。
事件 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。
三、可复用的排查流程
当线上某个功能失效时,按以下顺序逐层缩小范围:
- 看浏览器 Console:是否有 CSP、JavaScript 异常、资源加载错误?
- 看 Network:请求 URL、状态码、Content-Type、Protocol、缓存命中各是什么?
- 看构建产物:
dist/中有没有目标文件?HTML 中引用的路径对不对? - 看实际部署版本:Nginx
root到哪里?current指向哪个 release? - 看 Nginx 生效配置:
nginx -T是否包含对应 server/location/header? - 看服务与监听:
systemctl status nginx、ss -ltnp、ss -lunp是否符合预期? - 看网络边界:UFW、阿里云安全组、代理、DNS 是否允许流量通行?
- 最后验证用户行为: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 对个人技术博客是一次有意义的学习与运维实践,但对访客而言,真实项目案例、易读的技术文章、准确的搜索结果、合理的移动端布局,可能比协议版本更重要。
下一阶段值得优先做的事:
- 修复阅读时长、文章排序、分类描述等一致性问题。
- 完成并测试中英文标题/标签模糊搜索。
- 发布真实项目与课程实验内容,完善学习笔记质量。
- 建立 Playwright 等自动化回归测试,覆盖搜索、主题、导航和响应式。
- 用 Lighthouse、浏览器 Network 与真实设备评估视频、图片和页面性能。
- 形成简短的运维 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 安全与运维中的问题。只要把每一次错误记录为可复用的经验,这个网站的价值就不只是作品展示,而是一套持续成长的工程实践。
