摘要

折腾家庭数据中心,坑是躲不掉的,能做的是"坑只踩一次"。这篇是踩坑合集:caddy 按 Host 头路由、反代透传公网域名回 200 空包;DSM 的 auth.cgi 登录保护,短时间多调被限流;pct push/pull 失败退出码还是 0;nps 后端端口体检误判。每个坑写清症状、根因、解法,最后讲"坑的分类学"——踩坑记录怎么写,下次才查得到。

目录

一、坑的分类学:先分类,再记录

踩坑记录最怕写成流水账,半年后自己都查不到。分类是第一步:

类别 特征 例子
协议/行为坑 软件按文档之外的行为工作 caddy Host 头路由、DSM 登录保护
工具陷阱 工具输出误导人 pct 退出码为 0、curl 只看状态码
流程坑 顺序错了就坏 先签证书后配 nginx、OPcache 没清
误判坑 结论下早了 nps 端口"暴露"误判

分类的意义:同类坑的排查思路相通。协议坑去看文档和抓包,工具陷阱去验"真实效果",流程坑去对 checklist,误判坑去二次验证。分类是为了下次更快定位。

二、caddy 按 Host 头路由:200 空包

症状:nginx 反代到后端的 caddy,curl 返回 200,但 content-length: 0,body 是空的。状态码正常,内容没有。

根因:caddy 按 Host 头路由站点。nginx 反代时如果透传公网域名作 Host 头,caddy 不认识这个域名,返回 200 空包,且没有任何报错。200 + 空 body,极难察觉——监控看状态码是绿的,实际服务是坏的。

解法:反代 caddy 时,把 Host 头改成 caddy 认识的内网名:

location / {
    proxy_pass http://127.0.0.1:2019;  # caddy 管理/业务端口
    # 关键:Host 改成 caddy 认识的内网名,别透传公网域名
    proxy_set_header Host 192.168.0.120;
}

教训:验收反代不能只看状态码,必须看 body 大小和内容。curl -s -o /dev/null -w "%{http_code} %{size_download}\n",状态码和下载字节一起看。这个习惯后来成了全家验收的标准动作。

三、DSM auth.cgi 登录保护:别反复调

症状:调 DSM 的 auth.cgi 登录,短时间多调几次后,开始超时或返回空响应。但 DSM 页面本身正常,能打开。

根因:DSM 有登录保护,短时间内反复调用 auth.cgi 会被限流。不是 DSM 挂了,是你的调用方式触发了保护。

解法:登录一次拿到 sid,复用到底:

# 登录一次,拿 sid
SID=$(curl -s "https://192.168.0.106:5001/webapi/auth.cgi?api=SYNO.API.Auth&version=6&method=login&account=xxx&passwd=xxx&session=FileStation" | jq -r .data.sid)
# 后续请求都带这个 sid,别再调 login
curl -s "https://192.168.0.106:5001/webapi/entry.cgi?api=SYNO.FileStation.List&version=2&method=list&_sid=$SID"

触发限流后,冷却约 15 分钟。写自动化脚本调 DSM API 时,这个限制要记在心里。教训:"页面正常但 API 超时",先怀疑调用频率,别怀疑服务挂了。

四、pct push/pull:退出码为 0 不代表成功

症状:pct push 传文件进 LXC 容器,命令退出码 0,但文件没进去,或者权限不对。

根因:pct push/pull 失败时,退出码仍然为 0。0 不代表成功,只代表"命令跑完了"。

解法:事后验货。

pct push 702 /local/xray /usr/local/bin/xray
# 别信退出码,进容器验
pct exec 702 -- ls -lh /usr/local/bin/xray
# 权限不对就补
pct exec 702 -- chmod +x /usr/local/bin/xray

教训:这是全家通用的验收哲学——退出码为 0 不代表成功,看真实效果。pct 的这个问题,后来在 AGENTS.md 里记了一笔:所有经 pct 的操作,事后验货。

五、体检误判:nps 后端端口"暴露"了?

症状:一次安全体检,结论说 nps 后端端口(28392/28443/28100)公网暴露了。

根因:体检方法有问题,复查发现是误判。端口实际未公网暴露。

解法:二次验证。用公网 IP 直接连这几个端口,超时才算"未暴露"。结论:28392/28443/28100 确认未公网暴露。

教训:负面结论("暴露了"、"坏了"、"丢了")发布前必须二次验证。误报消耗信任,比漏报更伤。体检报告里,负面结论单独标出来,逐个复核。

六、踩坑记录怎么写

踩坑记录的目标:下次遇到,能搜到、能看懂、能直接用。格式:

## 坑:<一句话>
**症状**:看到了什么
**根因**:为什么
**解法**:怎么做(含命令)
**教训**:提炼成一句话的规则

存放:NAS「私密配置」下的 踩坑记录.md 持续追加,博客发一篇合集。私密那份是原文(含敏感细节),博客这份是脱敏版。两份都写,私密的详细,公开的有用。

好记录的标准:半年后的自己,能靠这篇记录独立解决问题,不用重新排查。如果写完自己都看不懂,等于没写。

更多坑:OPcache、备份撞车、端口冲突

坑 5:PHP 改完没效果,先查 OPcache

PHP 应用(EasyImage、WordPress)改完配置或代码,页面还是旧的,第一反应别是"改错了",先 systemctl reload php-fpm(或重启容器)清 OPcache。OPcache 把 PHP 文件缓存在内存里,文件变了它不知道。这个坑在图床、博客两篇都出现过,通用解法:改 PHP 必清 OPcache,把它当固定流程。

坑 6:备份时间撞车,IO 打架

家里定时备份多了,时间没规划好,凌晨 2 点三个备份一起跑,NAS 的 IO 被打满,备份变慢还互相影响。解法就是备份时间表(见 PVE 备份那篇):大 IO 任务错开半小时以上。新加定时任务先查表,别硬挤。

坑 7:端口规划先查后用

CT702 上应用越加越多,8090、8080、3001、3002、8093……新应用随手配个端口,容易和已有的撞。mknpc.sh 的端口池是"先规划再分配",应用端口也要一样:ss -tlnp 看已监听的,规划表里记一笔,再配。端口冲突的表象是"服务起不来",日志里一句"address already in use",5 秒能定位,但提前规划能省这 5 秒。

踩坑的元经验:为什么总在同一个地方跌倒

写完这些坑,回头看,有个规律:坑不是随机分布的,它们集中在几个"地形"里。认出地形,下次没走到坑边就绕开了。

地形一:看不见的中间层。 caddy 的 Host 路由、OPcache、pct 的退出码,这三个坑的共同点是"中间有一层你看不见的东西"。caddy 按 Host 路由,但你以为反代只看 IP 和端口;OPcache 缓存了 PHP 文件,但你以为改了文件就生效;pct 返回 0,但你以为 0 就是成功。每次都是"我以为"和"实际"之间差了一层。对策:凡是经过"代理、缓存、封装"的链路,验收时多看一眼中间层。curl 加 -v 看请求头,进容器看实际文件,日志看真实返回。中间层不可见,就用"两端对照"逼它现形。

地形二:频率触发的保护。 DSM 的 auth.cgi 限流、fail2ban 的封禁、nginx 的限流,都是"单位时间超过阈值就动手"。这类坑的特征是"第一次好好的,多试几次就坏",特别容易误判为"服务不稳定"。对策:凡是"多试几次才坏"的现象,先查频率限制。看文档里的 rate limit,数自己发了多少次请求。自动化脚本尤其要小心:人手点几次没事,脚本一秒十次直接触发保护。写脚本调 API,第一件事就是看它的频率限制。

地形三:结论下得太早。 nps 端口"暴露"误判,是这类坑的典型。体检扫了一遍,看到端口开着,就下了"暴露"的结论,没做二次验证。技术判断里,"负面结论"(坏了、暴露了、丢了)要比"正面结论"更谨慎,因为负面结论会触发行动(半夜爬起来修、通知用户、回滚),行动的成本很高。对策:负面结论发布前,换一种方法再验一遍。nmap 说暴露了,就用公网 curl 连一下;监控说服务挂了,就手动点一下。多花 5 分钟,省一次误报消耗的信任。

地形四:默认值不等于正确值。 端口随手配、备份时间随手定、filter 正则直接抄模板,都是"用默认值"的坑。默认值是"能跑",不是"适合你"。对策:凡是"第一次配"的东西,问自己三个问题——这个值在我这是什么意思?不改会有什么后果?改了要通知谁?三个问题答不上来,就先别上线。

四个地形,八个坑,其实讲的是一件事:慢即是快。多看一眼 body,多测一次 filter,多验一遍结论,多问三个问题。每次多花 5 分钟,一年省出几十个小时的排障时间。踩坑记录的终极目标,不是"记住每个坑",而是认出"坑的地形",没走到坑边就绕开。这篇合集,就是一张地形图。

坑的复利:记录的价值随时间增长

踩坑记录有个反直觉的特性:它的价值随时间增长,不贬值。刚写完时,价值是"下次不踩";半年后,价值是"新人不用踩";三年后,价值是"看当年的自己多傻"。记录是时间的朋友。

复利一:排查速度。 同样的坑,第一次两小时,第二次五分钟。差的不是技术,是"有没有记录"。记录把"重新排查"变成"查表"。 homelab 的坑,80% 是重复坑(OPcache、端口冲突、权限),查表解决,不用动脑子。脑子留给没见过的新坑。

复利二:决策质量。 "nps 管理面板保留公网"这个决定,当时是逐项定夺的,有记录。半年后有人问"为什么 nps 面板还在公网",翻记录,理由、权衡、当时的选项,全在。不用重新开会,不用重新吵。记录让决策"可回溯",可回溯的决策才敢做。

复利三:知识传承。 这 20 篇文章,本身就是踩坑记录的"公开版"。私密的详细版在 NAS,公开的脱敏版在博客。写博客的过程,是把"经验"提炼成"知识"的过程:经验是"我踩过",知识是"你不用踩"。提炼的标准:半年后的陌生人,照着做能成。达不到这个标准,就是没提炼完。

记录的成本是"写下来的 10 分钟",收益是"未来无数个 10 分钟"。这笔账,时间越长越划算。所以这套家庭数据中心的规矩:凡是排障超过半小时的,必写记录;凡是生产变更,必记日志。规矩是反人性的(谁愿意写文档),但复利是实在的。写吧,未来的你会感谢现在的你。

上线前 checklist:过一遍再发布

坑踩多了,checklist 是最便宜的防御。新服务上线前,过一遍这个单子:

  • [ ] 端口:查过占用,规划表里记了
  • [ ] 配置:改完清了缓存(OPcache、页面缓存)
  • [ ] 验收:看的 body,不只状态码
  • [ ] 备份:数据卷独立,备份策略定了
  • [ ] 安全:默认口令改了,不必要的端口没开
  • [ ] 文档:Dashy 录了,运维日志记了
  • [ ] 监控:Uptime Kuma 加了(至少 P2)

7 项,5 分钟。每个坑背后,都有一项没打勾。checklist 不是形式主义,是"把教训变成流程"。打印出来贴墙上,比什么都管用。 坑踩完了,记录写好了,地形图画好了。下一个坑见——带着地图去。地图在 NAS 私密配置/踩坑记录.md,持续更新,永不过时。这篇就是地图的公开版。

七、总结

四个坑,四类教训:caddy(验收看 body,不只看状态码)、DSM(登录一次复用 sid)、pct(退出码 0 也要验货)、体检误判(负面结论二次验证)。共同点是"别信表面信号":状态码、退出码、体检结论,都要看真实效果。踩坑记录按"症状-根因-解法-教训"写,存两份(私密详细版 + 博客脱敏版)。坑只踩一次,靠的不是记性,是记录。

相关阅读:《家庭数据中心架构总览:VPS、PVE、NAS 三方分工》、《qemu-guest-agent 卡死排障记》。