凌晨三點,監控告警把值班手機震醒:/ 分割區使用率 98%,MySQL 寫不進 binlog,CI 部署在 docker pull 階段直接報 no space left on device。我們連上機器跑 df -h,根分割區只剩 412MB——不是突然被駭客塞了木馬,而是三個月沒人管的日誌、Docker 層、apt 快取和 journal慢慢把 40GB 小碟吃光了。
這類故障在中小 VPS 上極其常見:套餐磁碟不大,業務卻 7×24 跑容器、拉映像檔、打日誌。手動 rm -rf 既慢又容易誤刪;更靠譜的做法是先定位大戶,再用腳本批次清理「可再生」目錄。本文把我們生產環境裡反覆用過的一鍵清理腳本整理成可複製版本,並標明哪些操作安全、哪些需要二次確認。
前提:你已有 SSH 登入權限和 sudo。若此刻已經連不上(例如 SSH 金鑰寫不進去),請先參考 Linux 伺服器 SSH 連線被拒排查指南,確認是磁碟滿導致還是網路/防火牆問題。磁碟救回來之前,別急著改 UFW 規則——先把空間騰出來,服務才能正常寫日誌。
磁碟滿了,你會先看到什麼?
Linux 在根分割區寫滿時,表現往往很「玄學」:SSH 還能登入,但 vim 儲存不了、apt install 失敗、Docker 容器起不來、Nginx 回傳 500 且 error.log 不再增長。MySQL / PostgreSQL 可能直接唯讀或崩潰;systemd 服務反覆 restart,因為無法建立 pid 檔案或寫 socket。
第一步永遠是看掛載點,而不是猜哪個目錄「看起來大」:
# 各分割區剩餘空間 df -hT # 根目錄下一層誰最大(可能較慢) sudo du -xh --max-depth=1 / 2>/dev/null | sort -hr | head -20 # inode 用盡也會「假滿」 df -ih
du 在檔案特別多時會掃很久;若機器已經卡死,可只對可疑路徑點查,例如 sudo du -sh /var/lib/docker /var/log /var/cache。官方 du(1) 手冊 對 -x(不跨檔案系統)和 --apparent-size 有詳細說明——跨碟掛載時別被 du 總數誤導。
常見「吃碟」目錄對照表
不同業務組合不同,但下面幾類在 Ubuntu / Debian VPS 上出現頻率最高。表裡標注了是否適合腳本一鍵清——帶「謹慎」的請先確認沒有業務依賴再動。
| 路徑 / 來源 | 典型體積 | 可否自動清 | 說明 |
|---|---|---|---|
/var/lib/docker |
數 GB~數十 GB | 部分可 | 懸空映像檔、停止的容器、未用 volume;執行中容器的資料卷勿刪 |
/var/log/journal |
1~10+ GB | 可 | systemd 日誌預設無上限時膨脹極快 |
/var/log/*.log |
數百 MB~GB | 謹慎 | 可 truncate 或 logrotate;直接 rm 可能導致行程仍占 inode |
/var/cache/apt |
數百 MB~數 GB | 可 | apt-get clean 安全,只刪已下載 deb 包 |
/tmp、/var/tmp |
不定 | 部分可 | 刪 7 天未存取檔案;注意是否有程式把暫存上傳放這裡 |
Nginx proxy_cache |
數 GB | 可 | 快取可重建;若你按我們 Nginx 反向代理調優 設了 cache,清完會短暫 MISS |
舊核心 /boot |
數百 MB | 可 | apt autoremove --purge 刪舊 kernel,保留目前與上一個 |
| core dump、崩潰轉儲 | 不定 | 可 | /var/crash、應用目錄下的 core.* |
/home、/var/lib/mysql、物件儲存掛載點或備份目錄最大,腳本清快取幫不上忙——需要歸檔、擴容或遷走冷資料。清理只能回收「可再生」空間,不能代替容量規劃。
一鍵清理腳本(可複製)
下面腳本預設乾跑(dry-run):只列印將釋放的空間與將執行的命令,加 --apply 才真正刪除。建議先 dry-run,確認輸出合理後再 apply。腳本按模組拆分,你可以註解掉不需要的部分(例如機器沒裝 Docker 就跳過 Docker 段)。
#!/usr/bin/env bash # 伺服器磁碟一鍵清理 — 僅處理可再生快取 # 用法: sudo ./disk-cleanup.sh # 預覽 # sudo ./disk-cleanup.sh --apply # 執行 set -euo pipefail APPLY=false [[ "${1:-}" == "--apply" ]] && APPLY=true log() { echo "[$(date '+%F %T')] $*"; } run() { if $APPLY; then log "EXEC: $*" eval "$@" else log "DRY-RUN: $*" fi } before=$(df -h / | awk 'NR==2 {print $3 " used, avail " $4}') log "Before: $before" # 1. apt 快取 if command -v apt-get >/dev/null; then run "apt-get clean -y" run "apt-get autoremove -y --purge" fi # 2. systemd journal — 保留最近 7 天或 500MB if command -v journalctl >/dev/null; then run "journalctl --vacuum-time=7d" run "journalctl --vacuum-size=500M" fi # 3. Docker 未使用資源(不含命名 volume 內業務資料) if command -v docker >/dev/null && docker info >/dev/null 2>&1; then run "docker system prune -af --filter 'until=168h'" run "docker builder prune -af --filter 'until=168h'" fi # 4. 暫存目錄 — 7 天未存取 run "find /tmp /var/tmp -type f -atime +7 -print 2>/dev/null | head -20" if $APPLY; then find /tmp /var/tmp -type f -atime +7 -delete 2>/dev/null || true fi # 5. Nginx proxy_cache(依你的 cache_path 修改) NGINX_CACHE="/var/cache/nginx" if [[ -d "$NGINX_CACHE" ]]; then run "du -sh '$NGINX_CACHE'" run "find '$NGINX_CACHE' -type f -delete 2>/dev/null || true" fi # 6. 崩潰轉儲 [[ -d /var/crash ]] && run "rm -rf /var/crash/*" # 7. pip / npm 快取(若存在) [[ -d /root/.cache/pip ]] && run "rm -rf /root/.cache/pip/*" for u in /home/*; do [[ -d "$u/.npm" ]] && run "npm cache clean --force --cache '$u/.npm' 2>/dev/null || rm -rf '$u/.npm/_cacache'" done after=$(df -h / | awk 'NR==2 {print $3 " used, avail " $4}') log "After: $after" $APPLY || log "預覽模式結束。確認無誤後: sudo $0 --apply"
賦予執行權限:sudo chmod 750 /usr/local/sbin/disk-cleanup.sh。第一次務必不加 --apply,把 DRY-RUN 輸出存一份;若 Docker 段顯示將刪除大量映像檔,確認沒有「只剩本地、倉庫已刪」的唯一副本。
docker system prune -a 會刪掉所有未使用的映像檔,下次部署要重新 pull,在頻寬緊張的 VPS 上可能反而拖慢恢復。更穩妥的是加 --filter until=168h(一週未用才刪),或定期清理 dangling 映像檔:docker image prune -f。詳見 Docker 官方 pruning 文件。
分模組說明:清什麼、不清什麼
journalctl:--vacuum-time 與 --vacuum-size 可組合使用,systemd 會按 journalctl 文件 規則裁剪二進位日誌。若你希望永久限制體積,在 /etc/systemd/journald.conf 裡設 SystemMaxUse=500M 後 systemctl restart systemd-journald。
日誌檔案:對仍在被行程開啟的大日誌,用 truncate -s 0 /var/log/nginx/access.log 比 rm 安全——行程仍持有 fd,rm 只刪目錄項,空間不會立刻釋放。長期應配 logrotate,讓 Nginx / 應用日誌按天切割並保留 N 份。
apt:apt-get clean 只清 /var/cache/apt/archives;autoremove --purge 會刪舊核心和孤兒依賴。升級核心後建議保留「目前 + 上一版」兩個 kernel,防止新核心啟動失敗時無法回退。
業務資料別碰:/var/lib/mysql、/var/lib/postgresql、Redis RDB、使用者上傳目錄、備份掛載點——這些不在腳本預設範圍內。若 du 顯示它們最大,需要的是歸檔、擴容或遷冷儲存,而不是清快取。
清理之後:別讓磁碟再次悄悄滿
救急腳本跑完,根分割區從 98% 降到 60% 只是開始。我們給中小 VPS 的最低配預防是三件事:
- 監控閾值:雲端廠商或自建 Prometheus 在 80% 告警,85% 發簡訊——別等 95% 才有人看。
- 每週 cron:
0 4 * * 0 root /usr/local/sbin/disk-cleanup.sh --apply >> /var/log/disk-cleanup.log 2>&1 - journald + logrotate 上限:把「無限增長」改成「有頂的天花板」。
若跑 Docker Compose 堆疊,給 json-file 日誌加 max-size 和 max-file,否則單個容器的 stdout 就能把碟寫滿——這和清映像檔一樣重要,卻常被忽略。
上線前檢查清單
把下面幾條抄進你的維運筆記,下次告警來時按順序打勾:
df -hT與df -ih— 確認是空間滿還是 inode 滿du -sh點查 docker、journal、log、cache 目錄- 腳本 dry-run → 人工掃一眼 →
--apply - 清理後
df -h驗證,並抽查核心服務(DB、Nginx、Docker)是否正常 - 補 journald 限制、logrotate、cron 與 80% 告警
磁碟滿從來不是「刪幾個檔案」就結束的維運事件,而是一次容量與日誌策略的體檢。腳本負責把你在凌晨三點的手速從「慌亂 rm」拉回到「可重複的標準流程」——剩下的,是把預防補上,讓告警少響幾次。
開發建置占滿磁碟?考慮分工部署
若你的 VPS 既要跑生產服務,又要本地 xcodebuild、Docker 多層建置或 Ollama 模型快取,小碟很容易在編譯週被 DerivedData 和映像檔層擠爆。更穩妥的做法是生產 VPS 保持精簡,把重建置遷到專用環境——例如雲端 Mac 跑 iOS 出包、獨立 Runner 跑 CI,生產機只拉最終產物。
Mac mini M4 憑藉統一記憶體與低功耗,適合作為「只幹建置、不扛 7×24 業務流量」的節點;與 Linux VPS 分工後,生產碟只留執行時資料,編譯快取不再和生產日誌搶同一塊 SSD。
如果你正在規劃「生產機瘦身 + 建置環境分離」,可以先把重活遷到雲端 Mac,讓 VPS 回歸輕量維運——了解 VPSSPark 套餐,少在凌晨三點和 df -h 搏鬥。