我的網站在 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 給出的錯誤答案,往往不是明顯離譜的那種,而是聽著特別合理的那種。唯一能拆穿它的,是讓它去撞真實世界的牆。跑一遍,測一遍,看看現實認不認。

回到開頭那個「又」字。

第一次宕機時我選擇了手動救火,成本是十分鐘。第二次宕機,我付出的是重做整條部署鏈路。如果我在第一次就動手,第二次根本不會發生。

每一次架構升級,都是一次故障開的帳單。區別只在於你主動付,還是被動付。

而當執行成本趨近於零,你唯一還需要付的,就是判斷。