这是一篇基于你提供的文档内容整理而成的技术博客文章。文章采用了"事故复盘 + 解决方案 + 实战脚本"的结构,适合发布在技术社区、团队内部知识库或个人博客上。
Nginx 换机丢了 16 个子域:掉线 16.5 小时换来的配置迁移清单
摘要 :一次看似平稳的 Nginx 服务器切换,结果丢了 16 个子域的 vhost 配置,业务掉线 16.5 小时才被发现。复盘发现两个环节同时失守:切换前没有做
server_name全量比对,切换后的探活只 ping 了 IP、只看了状态码,没有看内容。本文给出一份可直接落地的配置迁移清单和两个脚本,希望能帮你避开同样的坑。
一、事故回顾:切换当天全绿,掉线隔天才冒头
起因很普通:老服务器要下线,新服务器装好 Nginx、迁配置、测主站、切 DNS。验收时主站正常、探活正常,所有人都认为切换完成。
问题出在那些"被遗忘的角落":
老机器上除了主站,还躺着 16 个子域的 vhost 。这些是多年间陆续加上的内部系统、测试环境或旧项目入口。这批配置散落在老的 server 块里,迁移时大家只搬了「主配置文件」,没人做过一次「新旧 Nginx 一共有哪些 server_name」的比对。
更糟的是探活设计的缺陷:
监控目标仅指向主站 URL,返回 200 即判成功;ICMP ping 新机器也通。于是,掉线的 16 个子域在监控大屏上完全隐形 。直到有用户反馈「某个系统打不开了」,距离切换已经过去 16.5 小时。
二、根因分析:两个致命的隐含假设
事后复盘,这次事故由两个隐含假设叠加造成:
- 假设一:「配置 = 那几个 conf 文件」
- 真相 :Nginx 的生效配置是主配置加所有
include文件的并集,往往散落各处。只凭「文件都拷了」来判断迁移完整,等于凭记忆验收。
- 真相 :Nginx 的生效配置是主配置加所有
- 假设二:「探活 200 = 业务正常」
- 真相 :
ping通只说明机器活着,HTTP 200 只说明被监控的那个特定 URL 活着。第 17 个及以后的子域死了,主站探活一无所知。
- 真相 :
一句话总结:切换前的「完整性」靠记忆,切换后的「健康度」看单点------两个环节各漏一步,叠出了 16.5 小时的故障。
三、避坑指南:Nginx 配置迁移六步清单
为了避免此类事故再次发生,我们梳理了以下六个动作,缺一不可:
1. 导出生效配置全量
不要比对「你以为的配置文件」,要比对生效配置。
- 操作 :切换前在旧机器跑
nginx -T(输出当前生效的完整配置),存档为基线;新机器装完后同样跑一次,两份做 diff。
2. Server_name 清单比对
- 操作 :从两份基线里提取全部
server_name,做集合 diff。 - 标准:新旧清单不一致(尤其旧有新无)必须逐条归因:是「确认不迁移」还是「漏迁」?必须写进记录,严禁模糊处理。
3. 证书清点
- 操作:对照 server_name 清单,核对每条 vhost 的证书文件是否存在、路径是否正确以及有效期是否充足。
- 注意:证书路径漏拷是仅次于漏配的高频坑。
4. 探活按域名清单走,校内容不只校状态码
- 操作 :对清单里每一个域名发请求。
- 断言三点 :
- 状态码正确;
- 响应体指纹(如 md5)与切换前采集的基准一致;
- 证书校验通过。
- 警示:只 curl 主页等于没测。
5. DNS 灰度与观察期
- 操作:切 DNS 先降 TTL,分批切流量。
- 策略:切换后保留旧机器一段时间只读待命(不立刻下线),给回滚留一扇门。
6. 变更留痕
- 操作:以上每步的输出(diff 结果、清单比对表、探活报告)必须归档。
- 价值:下一次迁移的基线,就是这一次的档案。
四、实战脚本:自动化比对与探活
为了将上述流程落地,这里提供两个通用脚本示意,可根据实际环境改造。
脚本一:切换前 Server_name 全量比对 (vhost_diff.sh)
此脚本用于在新旧机器分别运行,提取域名列表并找出差异。
bash
#!/usr/bin/env bash
# vhost_diff.sh ------ 新旧机器各跑一次,diff 两份清单
# 【执行步骤】
# 1. 在旧机器执行: nginx -T2>/dev/null | grep -oP 'server_name\s+\K[^;]+' | sort -u > old.txt
# 2. 在新机器执行: nginx -T 2>/dev/null | grep -oP 'server_name\s+\K[^;]+' | sort -u > new.txt
# 3. 在任意机器运行本脚本进行比对
echo "=== 开始比对域名清单 ==="
# 找出旧机器有但新机器没有的 (MISSING)
missing=$(comm -23 old.txt new.txt)
if [ -n "$missing" ]; then
echo "❌ 存在未迁移域名,禁止切流:"
echo "$missing" | sed 's/^/MISSING_ON_NEW: /'
exit 1
fi
# 找出新机器有但旧机器没有的 (NEW_ONLY)
new_only=$(comm -13 old.txt new.txt)
if [ -n "$new_only" ]; then
echo "⚠️ 新机器多出以下域名(请确认是否为预期):"
echo "$new_only" | sed 's/^/NEW_ONLY: /'
fi
echo "✅ 域名清单比对通过,无遗漏。"
脚本二:切换后按域名清单探活 (probe_all.sh)
此脚本基于切换前采集的指纹基准,逐域验证状态码和内容一致性。
前置准备 :生成 baseline.tsv 文件(两列:域名 <TAB> 切换前 body 的 md5)。
bash
#!/usr/bin/env bash
# probe_all.sh ------ 域名清单 + 切换前指纹基准,逐域探活
BASELINE_FILE="baseline.tsv"
if [ ! -f "$BASELINE_FILE" ]; then
echo "错误:未找到基准文件 $BASELINE_FILE"
exit 1
fi
echo "=== 开始全量域名探活 ==="
while IFS=$'\t' read -r domain expect_md5; do
# 获取当前响应体的 MD5 body_md5=$(curl -sS -m 10 "https://${domain}" | md5sum | cut -d' ' -f1)
# 获取 HTTP 状态码
code=$(curl -sS -m 10 -o /dev/null -w '%{http_code}' "https://${domain}")
if [ "$code" != "200" ]; then
echo "❌ FAIL $domain status=$code"
continue
fi
if [ "$body_md5" != "$expect_md5" ]; then
echo "⚠️ DIFF $domain fingerprint_changed (期望:$expect_md5, 实际:$body_md5)"
# 注意:此处可加入白名单逻辑,忽略时间戳等动态内容的差异
else
echo "✅ PASS $domain"
fi
done < "$BASELINE_FILE"
echo "=== 探活结束 ==="
💡 实现要点提示:
- 指纹基准必须在切换前采集:事后没有「正确答案」可比。
- 处理动态内容:指纹比对允许合理差异(如时间戳、随机数)。建议先跑一遍生成白名单,剩下的差异都要严格归因。
五、结语:迁移类事故的共同形状
这次 Nginx 事故的形状,在很多场景里都在重复出现:
- 数据库换实例:只迁了业务库,漏了定时任务的辅助库;
- 域名服务商更换:只转了主域,漏了散落的解析记录;
- 容器重建:只备份了镜像,漏了挂载卷里的状态。
它们的共同点是:资产清单存在于人的记忆里,验收口径是单点指标。
解法始终一致:
- 先把「到底有多少东西」变成机器可对的清单(生效配置、解析记录、卷映射);
- 再把验收从「主入口正常」改成「逐条过清单 + 内容级校验」。
迁移的完整标准只有一个:清单上每一项,迁移前后各验一次,证据留档。
注:本文事故细节为真实故障的泛化叙述,未虚构时间线与技术栈;脚本为通用模式示意,生产环境使用前请务必测试。