🔑 關鍵洞察
✦ AI·GEN這篇把 UPS 加 NUT 的完整設定寫成一份可照做的教學:UPS 選型的三個條件(PFC 純正弦波、AVR、NUT 支援)、NUT 的 driver / upsd / upsmon 三層架構與五個設定檔各管什麼、upsmon.conf 逐行說明、POWERDOWNFLAG 為什麼不能漏,以及 BIOS 那個選錯就前功盡棄的選項——Restore after AC Power Loss 必須是 On 而不是 Last State,因為 NUT 乾淨關機後機器的狀態就是關機。另外收錄把 UPS 狀態接到 cron log、Discord 通知與自架面板的做法。而這一切的起點是搬家後伺服器每天半夜硬斷電:線鬆、共用迴路、過熱、PSU 全試過,但那些都只是猜測。作者買 UPS 一半是為了保護、一半是把它當量測儀器用,最後靠它的 log 證明問題在 UPS 下游,兇手是一條被貓咬過的電源線。
搬完家的第一週,伺服器開始每天半夜自己關機。
不是當機、不是重開,是斷電式的死亡:回來開機一定跑 ext4 recovery,uptime 歸零,前一秒還在跑的東西沒留下任何遺言。而且它挑時間,幾乎都在清晨。
我第一時間想的是「線鬆了」,去壓緊,隔天照死。想到搬家後冷氣和熱水器可能跟它共用迴路,關掉試,照死。量溫度,50~60°C,正常。最後懷疑是搬家把 PSU 震壞了——那是一顆買不到一年的振華白金牌,我不太想相信。
問題是這些都是猜測,而每次驗證要等一整天。查了幾天之後我意識到真正缺的不是假設,是證據:我根本不知道死掉的那一瞬間,送進機器的電是什麼樣子。
所以我去買了一台 UPS,一半是為了保護,一半是把它當量測儀器用。
這篇是後來的完整過程:UPS 怎麼選、NUT 從零設定到自動安全關機、BIOS 那個一定要設對的選項,以及最後靠 UPS 的 log 破了案——兇手是一條被貓咬過的電源線。
為什麼一台 UPS 能定案#
先講清楚它為什麼是儀器,因為這決定了整篇的邏輯。
UPS 量測的是它自己輸出端的狀態,而它會用幾個代號回報自己現在在幹嘛。整篇會一直看到,先講清楚:
| 狀態 | 意思 |
|---|---|
OL | On Line,市電正常,UPS 只是在旁邊待命 |
OB | On Battery,市電沒了,正在放電池 |
LB | Low Battery,電池快沒了,該關機了 |
有了這三個代號,只要 UPS 持續記錄,下次再死就能一刀切開兩種可能:
- 死亡當下記到
OB或電壓掉 → 問題在 UPS 上游,市電有事。 - 死亡當下全程
OL、電壓穩、電池滿 → UPS 送出去的電是乾淨的,問題在它下游,也就是電源線、PSU、主機板這一段。
這條分界線把搜尋範圍砍掉一半,而且不用再靠猜。
選型:1500VA / 純正弦波 / 有 AVR#
這顆 CyberPower CP1500PFCLCD 有三個對這次診斷有意義的點:
現在的主機 PSU 幾乎都是主動式 PFC。這類 PSU 遇到便宜 UPS 的,在切換到電池的瞬間可能直接關機——那會製造出跟我正在追的症狀一模一樣的新問題。診斷期間最不需要的就是這個。
直接處理掉一個假設:如果清晨真的有市電電壓跳動,它會當場壓平。壓平之後如果還死,那市電這條就能排除。
Linux 上的 走 usbhid-ups 驅動就能讀它,不需要廠商的專屬軟體。USB 插上去 lsusb 會看到:
Bus 001 Device 005: ID 0764:0601 Cyber Power System, Inc.NOTE
網路線不用接 這台 UPS 同時有 USB 和序列埠,而 NUT 走 USB 就夠了。網路那條是給有網管卡的機種或多機共享用的,單機監控用不到。
NUT:從零到自動安全關機#
NUT 的設定檔散在好幾個地方,而且權限沒設對服務就起不來。我把整套寫成一支腳本,重灌或換機時直接重跑。
先搞懂 NUT 的三層#
設定檔之所以看起來很亂,是因為 NUT 不是一支程式,是三支各司其職的程式。搞懂誰是誰,設定檔就自己對號入座了:
- driver(我這台是
usbhid-ups)——真正跟 UPS 講話的那支,把廠商的協定翻成 NUT 的資料。 - upsd——把 driver 拿到的資料開成一個服務,讓別人來問。
- upsmon——用戶端。它一直跟
upsd要資料,看到電量低就執行關機。
所以那五個設定檔是這樣分的:
/etc/nut/nut.conf # 這台扮演什麼角色(單機還是主從)
/etc/nut/ups.conf # driver:用哪個驅動、接哪台 UPS
/etc/nut/upsd.conf # upsd:資料服務聽哪個位址
/etc/nut/upsd.users # upsd:誰能讀、誰能下指令
/etc/nut/upsmon.conf # upsmon:什麼情況下關機、怎麼通知單機監控用 standalone:
# /etc/nut/nut.conf
MODE=standalone# /etc/nut/ups.conf
maxretry = 3
pollinterval = 5
[cyberpower]
driver = usbhid-ups
port = auto
desc = "CyberPower CP1500PFCLCD"# /etc/nut/upsd.conf —— 只聽 localhost,不對外
LISTEN 127.0.0.1 3493WARNING
LISTEN 預設值要改
NUT 的 upsd 如果聽在 0.0.0.0,區網任何人都能查詢你的 UPS 狀態、甚至在權限沒設好時下指令。單機監控沒有任何理由對外,綁 127.0.0.1 就好。密碼我是用 openssl rand -hex 16 隨機產生後寫進檔案,反正不用人記。
權限也要對,不然 driver 讀不到設定:
chown root:nut /etc/nut/*.conf /etc/nut/upsd.users
chmod 640 /etc/nut/*.conf /etc/nut/upsd.users安全關機是怎麼發生的#
重點在 upsmon.conf 這幾行:
MONITOR cyberpower@localhost 1 upsmon <password> primary
MINSUPPLIES 1
SHUTDOWNCMD "/sbin/shutdown -h +0"
POWERDOWNFLAG /etc/killpower
POLLFREQ 5
POLLFREQALERT 5
FINALDELAY 5一行一行看:
| 指令 | 在做什麼 |
|---|---|
MONITOR | 要監看哪台 UPS、用哪組帳密。結尾的 primary 代表這台機器有權在關機前叫 UPS 一起斷電 |
MINSUPPLIES | 至少要有幾組電源還活著才繼續跑。單電源主機填 1 |
SHUTDOWNCMD | 決定關機時執行什麼 |
POWERDOWNFLAG | 關機流程留下的旗標檔,下面會專門講 |
POLLFREQ / POLLFREQALERT | 平常幾秒問一次 upsd、出事後幾秒問一次 |
FINALDELAY | 送出關機指令前最後緩衝幾秒 |
實際流程是這樣,而最後一段是最多人漏掉的:
POWERDOWNFLAG 指到的是一個旗標檔。upsmon 決定要關機時會先建立它,而系統關機腳本跑到最後會看一眼:檔案在,就順便叫 UPS 把自己的輸出也切斷。
不這樣做的話,機器關了但 UPS 還在放電,電池會被榨到 0,而鉛酸電池深度放電一次就折壽一次。
而 BIOS 自動開機那一格如果沒設,前面全部白做——機器會乾淨地關機,然後停在那裡等人按電源鍵。
關機門檻的取捨#
觸發點是 UPS 自己認定的低電量。我這台的原廠值是:
battery.charge.low: 10 # 電量剩 10%
battery.runtime.low: 300 # 或估計剩餘 300 秒先用原廠值是刻意的,因為它最不會誤判。跑一陣子有真實數字之後再決定要不要調早。我現在的實測是:
負載 12%、續航一小時,剩 5 分鐘才開始關機的緩衝完全夠。但如果你的機器把 UPS 吃到 60%、續航只剩十幾分鐘,那 5 分鐘就有點緊,可以用 upssched 加一道更早的觸發(例如「吃電池滿 90 秒」就先關)。
BIOS:那個不能選錯的選項#
這是整套自動復原裡最容易踩雷的一格。
CAUTION
Restore after AC Power Loss 要選 On,不能選 Last State
Last State 的意思是「回到斷電前的狀態」。而 NUT 低電量關機之後,機器的狀態就是關機——市電回來時 BIOS 會判定「它本來就是關的」,於是保持關機。你做了一整套自動復原,結果卡在最後一步。
選 On 才是「只要有電就開機」,不管之前是開是關。
我的板子是 MSI MAG X870E TOMAHAWK,路徑是:
開機按 Del → F7 切到 Advanced Mode
→ SETTINGS → Advanced → Power Management Setup
→ Restore after AC Power Loss = OnBIOS 在作業系統之前,SSH 和遠端桌面都看不到,一定要接實體螢幕鍵盤。想省去狂按 Del 可以先下:
sudo systemctl reboot --firmware-setup它會直接重開進 BIOS 設定畫面,但人還是得在螢幕前。
把 UPS 狀態接出來#
設定完之後 upsc 隨時查得到,但我不想每次都 SSH 進去打指令。
每分鐘留一筆紀錄#
這支腳本掛 cron 每分鐘跑一次,它同時做兩件事:寫一行人看的 log,和一份給程式讀的 JSON。
DATA=$(upsc cyberpower 2>/dev/null) || exit 0
g() { grep -m1 "^$1:" <<<"$DATA" | cut -d' ' -f2-; }
echo "$(date '+%F %T') status=$(g ups.status) in=$(g input.voltage)V \
out=$(g output.voltage)V load=$(g ups.load)% batt=$(g battery.charge)% \
runtime=$(g battery.runtime)s" >> "$OUT"那行 log 長這樣,而它就是後來破案的東西:
2026-07-05 00:12:01 status=OL in=113.0V out=113.0V load=12% batt=100% runtime=3675s
2026-07-05 00:13:01 status=OL in=113.0V out=113.0V load=12% batt=100% runtime=3675s
2026-07-05 00:14:01 status=OL in=113.0V out=113.0V load=12% batt=100% runtime=3675s
↑ 機器就在這一分鐘之後硬斷電事件推到 Discord#
upsmon.conf 支援對每種事件掛外部指令。做法是加一行 NOTIFYCMD,再把要通知的事件旗標加上 +EXEC:
NOTIFYCMD /path/to/ups-discord-notify.sh
NOTIFYFLAG ONBATT SYSLOG+WALL+EXEC
NOTIFYFLAG LOWBATT SYSLOG+WALL+EXEC
NOTIFYFLAG ONLINE SYSLOG+WALL+EXEC推的事件有六種:切換到電池、電量低即將關機、觸發系統關機、市電恢復、電池老化該換、與 UPS 失聯。每則附上即時電量、剩餘分鐘、負載和市電電壓。
Webhook 檔案要放在 upsmon 讀得到的地方,權限是 root:nut 640——這是我第一次弄的時候卡住的點,放在家目錄底下 upsmon 讀不到。
不想再開一個子網域#
原本想過用 Home Assistant 的 NUT 整合,或架個 peaNUT 之類的面板。但我的子網域已經一堆了,為了看一顆 UPS 再開一個網站很多餘。
所以改成把它塞進我自己的 NAS 面板。這裡有個限制:後端跑在 docker bridge 網路裡,連不到 host 的 upsd:3493。與其把 upsd 對外暴露,不如讓 host 寫檔、容器讀檔:
JSON 就是上面那支腳本順手產的:
{
"status": "OL",
"online": true,
"battery_charge": 100,
"battery_runtime": 3675,
"ups_load": 12,
"input_voltage": 113.0,
"model": "CP1500PFCLCDa TW",
"updated_at": "2026-08-04T13:55:01+0800"
}這個模式跟後端本來就在讀掛載檔案的做法一致,不用為了一個唯讀狀態去開網路權限。
NOTE
順帶發現的一件事 弄到一半才發現手機上看不到監控,不是 PWA 的問題——那個 app 根本沒有 PWA,沒有 manifest 也沒有 service worker。手機看到的是完全獨立的行動版版面,而監控頁只掛在桌面版的 Dock 裡。所以不是「PWA 沒帶到」,是行動版根本沒放。這種「以為是 A 問題結果是 B」在這整件事裡出現了不只一次。
破案:一條被貓咬過的線#
UPS 上線幾天後,它又死了一次。
我去翻 log,死亡前後全程 OL、市電 113~115V 穩、電池 100%、沒有任何 OB 事件,nut-monitor 也沒有任何紀錄。但重開機又是 ext4 recovery。
市電乾淨,機器卻硬斷電。問題確定在 UPS 下游。
範圍縮到電源線、PSU、主機板之後,我把機殼打開重壓所有接頭,順便換了一條電源線。原本那條是 12A 125V,而它上面有貓咬出來的洞。
那條線的位置正好是 UPS 輸出 → PSU 入口,完全在 UPS 的視線之外。一條銅芯半斷的線會間歇性接觸,一點震動或熱脹冷縮就通、斷、通——那正是「隨機、瞬間、無預兆」的斷電。
而且它解釋了所有先前講不通的地方:
| 現象 | 貓咬線怎麼解釋 |
|---|---|
| UPS log 全程乾淨 | 斷點在 UPS 量測點的下游,它看不到 |
| idle 也會死 | 跟負載無關,是機械性接觸不良 |
| 搬家後才開始 | 搬運的震動讓半斷的銅芯變得更容易分離 |
| 挑清晨 | 溫度最低,熱脹冷縮讓接觸點收縮 |
換上的新線是 10A 125V。看起來規格降了,但 10A × 125V = 1250W,而整台滿載大約 350~400W、換算不到 3A,餘裕非常大。
從「幾乎每天死」變成「十天才死一次」,頻率掉了十倍。
那次差點被誤導的復發#
換線後第十一天,它又死了。而且我的遊戲機也重開了。
兩台一起出事,結論很明顯:真的停電了。於是接下來的推論一路展開——真停電時 UPS 應該要撐住 server 才對,既然沒撐住,那一定是電源線插在「Surge Only」那排而不是「Battery Backup」那排,CyberPower 背後那兩排插座長得幾乎一樣,插錯太常見了。
整套推論寫得煞有介事,直到我去查遊戲機為什麼重開。
是 Windows Update。
前提垮掉,整串推論作廢。回頭重看 log,那次跟前面四次一模一樣:OL 全程、市電穩、電池滿、無 OB——不是停電,也不是插錯排,是同一種「UPS 下游瞬斷」,只是頻率已經低了十倍。
TIP
兩個獨立事件同時發生,是最容易讓人推錯的情況 「兩台一起掛」看起來是強得不能再強的共因證據,但它只是巧合。當一個新證據讓你的假設突然變得非常有說服力時,那正是最該回頭驗證那個證據本身的時候。我當時如果沒去查 Windows Update 的紀錄,大概會花一整晚去研究 UPS 的插座分組。
那這次到底是什麼?老實說沒有查出來。
當下能確定的只有排除法的結果:不是市電(UPS log 全程乾淨)、也不是過熱(死的當下 CPU 85% idle、load 1~3、零溫度告警、溫度正常)。剩下最可能的是新線某一端沒插到底,或某個內部接頭又鬆了一點——C13/C14 那種「感覺插好了其實沒到底」非常常見。我把兩端重壓了一次,然後就沒有下文了。
沒有下文的原因不是我放棄追,是四天後它又死了一次,而那次是完全不同的死法。從那之後注意力整個轉過去,這條殘留再也沒有復發到足以驗證的程度。所以誠實的結論是:貓咬線把頻率降了十倍,剩下的那一次至今無解。
不過那天我多做了一件事。既然「過熱」這條被排除得有點快,我順手加了一支溫度記錄器,每分鐘記一筆 CPU 溫度和 load,當作下次的保險。
四天後它立刻立功:
15:12 cpu=90.1C load=8.65
15:22 cpu=90.0C load=32.53
15:31 cpu=90.0C load=84.42
15:43 cpu=90.0C load=156.54
15:50 cpu=90.0C load=196.01 ← cron 已經排不進去了那次不是斷電,是系統被燒到爆炸:溫度頂在 90°C 硬撐四十分鐘,load 從 8 衝到 196,而 UPS 全程正常。跟電力完全無關,是另一個故事。
NOTE
每一次追查都留下一個量測工具,而下一個案子是被上一個留下的儀器破的 買 UPS 是為了查斷電,而它的 log 破了貓咬線這案。加溫度記錄器是為了排除過熱,而它四天後抓到了熱爆炸的現行犯。這不是運氣好,是因為每次「我不確定」的時候,加的不是猜測而是一支記錄器——那些記錄器會在你還不知道自己需要它的時候就開始累積證據。
寫這篇的時候順手查了現況#
journalctl --list-boots 可以看出每次重開機是「乾淨關機」還是「硬斷」——有 systemd-shutdown 收尾的是前者,什麼都沒留下的是後者。
07-14 → 07-15 20:21 無關機紀錄 ← 電力殘留那次
07-16 → 07-19 15:56 無關機紀錄 ← 熱爆炸那次
07-19 → 07-20 17:15 systemd-shutdown
07-20 → 之後八次 systemd-shutdown ← 全部是我自己重開的最後一次非計畫性死亡是 07-19,之後十六天沒有任何一次硬斷。
但這條電力殘留還不能算結案。這段期間我因為各種原因重開過八次,單次連續運行最長只有七天,而上次產生電力故障的間隔是十點八天。也就是說,到目前為止還沒有任何一段連續運行超過上次的發作間隔——它不是被證明修好了,只是還沒有機會出現。
要真的宣告結案,得先讓它安靜地連續跑超過十一天。這個標準是我自己訂的,寫出來是因為「後來就再也沒發生了」這句話如果不附上時間尺度,基本上不構成證據。
一頁式清單#
UPS + NUT 自架檢查清單
- UPS 選型看三件事:PFC 純正弦波(主動式 PFC 的 PSU 會挑波形)、有 AVR(電壓波動不用動電池)、NUT 支援(不必裝廠商軟體)。
- UPS 也是量測儀器:每分鐘記一次
ups.status/input.voltage/battery.charge,下次出事就能一刀切開「市電問題」和「UPS 下游問題」。 upsd綁127.0.0.1,單機監控沒有理由對外。- 設定檔權限
root:nut 640,權限不對 driver 起不來。 POWERDOWNFLAG不能漏,否則 OS 關機後電池會被榨到 0。- BIOS 選
On不是Last State,選錯的話 NUT 乾淨關機後市電回來機器不會自己開。 - 關機門檻先用原廠值,跑出真實續航數字再決定要不要用
upssched提早。 - Webhook 檔案放
/etc/nut底下,upsmon讀不到家目錄。 - 狀態要給容器看就用檔案,host 寫 JSON、bind-mount 進去,比把
upsd對外暴露乾淨。 - 電線收進走線槽或套防咬管,這條跟伺服器無關,是為了貓。
- 排除掉一個假設之後順手加一支記錄器,下一次出事你就有它了。
- NUT —— Network UPS Tools 官方文件networkupstools.org
- NUT —— 相容硬體清單(買之前先查型號)HCL
- NUT —— usbhid-ups 驅動說明man usbhid-ups
- NUT —— upsmon.conf 設定參考man upsmon.conf
- CyberPower —— CP1500PFCLCD 產品頁cyberpowersystems.com
還沒有留言
✨ 成為第一個留言的人吧