我的网站在 48 小时里崩了两次,然后我把整条部署链路重做了

8 月 19 日上午 11 点 14 分,我在飞书里发出一句话。我们的网站,在外网又打不开了,看看是什么原因?

网络2026-08-315 分钟阅读

8 月 19 日上午 11 点 14 分,我在飞书里发出一句话。

我们的网站,在外网又打不开了,看看是什么原因?

关键在那个「又」字。

两天前它就崩过一次。当时我没多想,手动把服务拉起来,网站活了,这事就翻篇了。我当时的判断很简单,偶发,运气不好。

第二次,同样的姿势,同样的时间段,同样躺平。

同一个故障出现第二次的时候,它就不叫故障了,叫设计缺陷。

这篇文章记录了从那天上午开始的四次架构升级。8 月 19 日重做部署链路,8 月 21 日上 HTTPS,8 月 27 日接上异地备份,还有昨天深夜,我在为写这篇文章核对事实的时候,又挖出一个藏了 8 天的坑。

还有件事得先说清楚。上面这些操作,从排查到升级,几乎全是 AI 助手在服务器上执行的,我坐在飞书对话框这一头,只做了 4 次决策。这条线我会贯穿全文讲,包括它卡住的时候,和它判断错的时候。

五条命令,找到根因

报障之后我没有打开终端。我把那句话发给 AI 助手,它开始排查。

命令

结果

ps aux | grep astro

空进程

curl 127.0.0.1:3000

HTTP 000,Connection refused

sudo iptables -t nat -L PREROUTING

规则清空

curl http://<公网IP>/

HTTP 000

uptime

 + last reboot

服务器 10:32 重启过

五条命令,根因浮出来了。

当时的架构是这样的。用 pnpm dev 跑一个 Astro 开发服务器占着 3000 端口,再用 iptables 把 80 端口的流量转发过去。听着能用,实际上有两个天坑。

iptables 规则默认不持久化。服务器一重启,转发规则清零。

pnpm dev 不是 systemd 服务。服务器重启后,没有任何机制会把它拉起来。

这两条都不是 bug。它们是我在拿开发工具干生产的活。pnpm dev 这个命令从设计上就没打算活过一次重启,它是给你本地改代码时热更新用的,不是给生产环境扛流量用的。

我用它上线,等于把一辆卡丁车开上高速,然后抱怨它不安全。

三个方案,和我做的第一个决策

排查完,AI 助手给了我三个选项。

方案

做法

代价

A

systemd 单元 + iptables-persistent

保住 dev server,但还是在给错误架构打补丁

B

PM2 + pm2-startup

引入新依赖,为一个静态站杀鸡用牛刀

C

pnpm build

 + Nginx 静态托管

改动最大,但把问题连根拔了

我选了 C。

这是我在这次事故里做的第一个决策,也是最关键的一个。

理由不复杂。A 和 B 的本质都是让错的东西更稳定,只有 C 是换成对的东西。

救火的时候,人的本能会选 A。改动小、见效快、十分钟能收工。但 A 救的是这一次的火,不是下一次的。iptables-persistent 能让规则活过重启,可 dev server 依然在生产环境里跑着,没有进程守护,没有资源限制,内存泄漏了没人管,构建产物每次都要现场编译。

我的网站是个纯静态站。它根本不需要一个 Node 进程常驻。让 Nginx 直接吐静态文件,是这件事本来就该有的样子。

这种取舍 AI 给不了。它可以把三个方案的利弊列得清清楚楚,但我愿意为这次修复承担多大的改动风险,这是我的事。

重做部署链路,六步和三个坑

方案定了,接下来是执行。这部分我基本没插手,AI 助手在服务器上一路做完。

  1. 1.

    pnpm build 生成 dist/,5 个静态页面,耗时约 2 秒

  2. 2.

    apt-get install nginx,自带 systemd 服务,enabled + active

  3. 3.

    建部署目录 /var/www/xiaocao

  4. 4.

    写 Nginx 配置

  5. 5.

    清理老架构,kill dev 进程加 iptables -t nat -F PREROUTING

  6. 6.

    写一键部署脚本 deploy.sh

过程中踩了三个坑,都值得单独说。

第一个是权限坑。我的项目源码在 ~/.openclaw/workspace/ 下,而这个目录是 700 权限。Nginx 以 www-data 用户运行,压根读不到。解法是用 rsync 把构建产物同步到 /var/www/xiaocao,再 chown 给 www-data。所以部署脚本里必须有同步这一步,不能图省事直接把 Nginx 的 root 指过去。

第二个是僵尸进程坑。kill 掉 Astro dev 的父进程后,子进程 35557 还活着,端口没释放,得手动补一刀。

第三个是路由坑。Astro 构建出来的是 /path/index.html 这种结构,Nginx 默认的 try_files 处理不了,得写成这样。

⁠⁠⁠

location /{try_files$uri$uri/ $uri.html $uri/index.html =404;}

除了 try_files,Nginx 配置里还有三块值得配,_astro/ 目录挂一年长缓存并标 immutable,images/ 挂 30 天,再打开 gzip。完整配置我放在官网了,文末点阅读原文能看到可以直接抄的版本。

还有那个一键部署脚本,后来成了我更新网站的唯一入口。核心就三步。

⁠⁠⁠

pnpm build sudorsync -a --delete "$SRC/dist/""$DEST/"sudo nginx -t &&sudo systemctl reload nginx

构建,同步,重载。加上 set -e 让它一步出错就停,再配上 chown 给 www-data,就是一个能用很久的部署脚本。

从 11:19 拍板到 11:26 域名接入完成,前后 7 分钟。网站回来了,而且这次它能活过重启。

上 HTTPS,两个教程不会告诉你的坑

两天后,8 月 21 日下午。网站稳了,但还在裸奔,浏览器地址栏挂着一个刺眼的「不安全」。我准备在公众号文章末尾放官网链接,这个标记很掉分。

方案还是两个。

A 是 Let's Encrypt 加 certbot,免费,90 天自动续期,国内直连稳定。B 是 Cloudflare 前置,顺带拿到 CDN 和 Analytics,但国内节点不稳。

我选了 A,这是第二个决策。理由很简单,我的读者主要在国内,Cloudflare 的免费节点在国内的表现我不敢赌。CDN 那点好处,抵不上访问慢带来的损失。

主流程真的只有一条命令。

⁠⁠⁠

certbot --nginx -d www.example.com -d example.com \  --non-interactive --agree-tos --email your@email.com --redirect

证书装好了,Nginx 配置被自动改写,本地 curl https://127.0.0.1 返回 200。

然后卡住了。

外网访问 443 端口,超时。

这就是第一个坑,也是我认为最值得写下来的一个。腾讯云 Lighthouse 的安全组默认不开 443 端口。

certbot 没有任何问题,Nginx 也没有任何问题。问题在云厂商控制台里,那个 AI 助手进不去的地方。

几乎所有 certbot 教程都不会提这一步。因为写教程的人用的往往是自建机房,或者早就把端口开好了的机器。而你,一个刚买了云服务器准备上 HTTPS 的人,会卡在这里,然后开始怀疑是不是 certbot 装错了。

这里也是整篇文章里人机边界最清楚的一处。AI 助手能改服务器上的任何配置,但它没有我的云控制台账号。它把问题定位到了安全组,剩下的必须我去点。我登录腾讯云,放行 443,回来再测,200,103 毫秒。

第二个坑更隐蔽。certbot 自动改配置的时候,保留了我之前写的裸域跳转规则,而那条规则跳的是 http://www。结果就是,用户访问 https://裸域,先被跳到 http://www,再被跳到 https://www,白白多绕一跳。

修复只有一行。

⁠⁠⁠

sudosed -i 's|return 301 http://www.example.com|return 301 https://www.example.com|'\  /etc/nginx/sites-enabled/xiaocao

顺手把部署脚本升级了,加了四行健康检查,每次部署完自动跑。分别打本地 HTTPS、外网 HTTPS、HTTP 跳转和裸域跳转,四条全绿才算部署成功。

⁠⁠⁠

curl -s -o /dev/null -w "%{http_code} %{time_total}s\n" https://www.example.com/ curl -s -o /dev/null -w "%{http_code} -> %{redirect_url}\n" http://www.example.com/

完整的四条检查同样在官网全文里。

到这里我以为 HTTPS 这一章结束了。

八天后我才知道没有。

藏了八天的坑,nginx -t 通过不等于你改的文件在生效

昨天深夜,我为写这篇文章核对事实,让 AI 助手把四条跳转链路重新实测一遍。结果不对,http://裸域 依然要绕两跳。

AI 助手当场给了我一个解释。8 月 21 日那次 sed 只修了一半,https://裸域 修好了,http://裸域 漏了。

听着很合理。我差点就信了。

它自己也差点信了,直到它去改配置。

它按标准做法编辑 /etc/nginx/sites-available/xiaocaonginx -t 语法检查通过,systemctl reload nginx 重载成功。再测。

线上纹丝不动。

然后它 diff 了两个文件,真相才出来。

/etc/nginx/sites-enabled/xiaocao 

这个文件不是软链接,是一个独立的实体文件。

Nginx 的标准做法里,sites-enabled/ 下面放的应该是指向 sites-available/ 的软链,一份配置一个真相源。但我这台机器上,两个目录里各躺着一份内容几乎相同的文件,从 8 月 21 日起各自漂移。

而真相和那个听着很合理的解释正好相反。8 月 21 日那次 sed 修对了,它改的是 sites-enabled,恰好是线上真正生效的那份。而 sites-available 里那份从头到尾没人碰过,至今还留着 http://www 的老写法。

所以昨晚 AI 助手第一次改的,是一个死文件。改了,测了,通过了,重载了,什么都没发生。

这就是我要单独拿一节写它的原因。

nginx -t 通过,不代表你改的那个文件在生效。它只检查语法。两份配置都语法正确,它对哪一份都说 OK。它不报错,它只是不生效。

一行命令自检。

⁠⁠⁠

readlink -f /etc/nginx/sites-enabled/你的站点名

如果输出的不是 /etc/nginx/sites-available/ 下的路径,你就有两份配置在各自漂移。

我做的第三个决策是根治而不是打补丁。以线上生效的那份为准覆盖过去,删掉实体文件,建回标准软链。现在四条链路全部一跳直达,https://www 响应 97 毫秒。

最后一块拼图,源码不能只活在一台机器上

前面三次升级,解决的都是服务挂了怎么办。但有个更大的风险一直没管,源码只在这一台服务器上。

服务器挂了,网站是宕机,重启能救。服务器没了,网站是消失,从头再来。

8 月 27 日深夜补上了这一块,GitHub 私有仓库。

这次的分工感最强烈。AI 助手在服务器上装 gh CLI、配 git config、init 仓库、写 .gitignore、建远程仓、推首次提交,一条龙。我全程只输了三样东西,邮箱、仓库名、还有 OAuth 的设备码。从开工到完成 19 分钟,43 个源码文件,4314 行,dist、node_modules、.astro、.env 全被 .gitignore 拦在外面。

这里有一个坑,可能只有 OpenClaw 用户会踩到,但踩到就是大事。

我的 workspace 目录 ~/.openclaw/workspace/ 本身已经是一个 git 仓库,而网站项目是它的子目录。在子项目里跑 git status,git 会一路向上找到 workspace 那个仓,把整个工作区的文件都列出来。

看起来复用现成的仓库很省事。但那个 workspace 里有什么?有 MEMORY.md,AI 助手的长期记忆,里面是我半年来所有的项目决策、协作约定、踩坑记录。有 .agents 目录,整套配置。

一个 git add -A,全推公网。

所以必须在子项目里单独 git init。这个不能复用的判断,是我做的第四个决策,也是最不像技术决策的一个,它关乎的不是架构,是隐私边界。

那么,我到底做了什么

四次升级,完整时间线。

日期

事件

架构状态

8/17

第一次宕机

pnpm dev + iptables,手动救火

8/19

第二次宕机

改成 build + Nginx + systemd + 一键部署

8/21

主动升级

加 HTTPS + 健康检查

8/27

主动升级

加 GitHub 私仓异地备份

8/29

事实核对

修掉配置漂移,改回标准软链

命令是 AI 助手敲的,配置是它写的,坑是它踩的,也是它自己爬出来的。

我做了四次决策。

#

决策

为什么 AI 替不了

1

三方案里选 C,连根拔而非打补丁

这是风险偏好,不是技术优劣

2

选 certbot 而非 Cloudflare

需要对国内网络环境的判断

3

去腾讯云控制台放行 443

AI 没有我的控制台账号

4

源码上私仓,且不复用 workspace 仓

这是隐私边界,不是工程问题

四条里,只有第三条是能力边界,AI 进不去那个控制台。另外三条都是判断边界。它能把选项列全、把利弊说清,但选哪个,需要一个承担后果的人。

还有一件事值得记下来。昨晚它给我的第一个解释,也就是 8/21 只修了一半那个,是错的。它当时手里的证据只够支撑一个事实,裸域有两跳。但它顺手补了一整套因果链,听着专业,逻辑闭环,完全自洽。

不是我发现的,是它撞墙撞出来的。

改完发现线上没反应,才回头 diff,才挖出配置漂移。如果它当时没去动手,只是把那个合理的解释交给我,我就会带着一个错误的结论把这篇文章写完,甚至还会在文章里教别人怎么修一个根本不存在的问题。

这大概是人机协作里最需要警惕的地方。AI 给出的错误答案,往往不是明显离谱的那种,而是听着特别合理的那种。唯一能拆穿它的,是让它去撞真实世界的墙。跑一遍,测一遍,看看现实认不认。

回到开头那个「又」字。

第一次宕机时我选择了手动救火,成本是十分钟。第二次宕机,我付出的是重做整条部署链路。如果我在第一次就动手,第二次根本不会发生。

每一次架构升级,都是一次故障开的账单。区别只在于你主动付,还是被动付。

而当执行成本趋近于零,你唯一还需要付的,就是判断。