🔑 关键洞察
✦ 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
还没有留言
✨ 成为第一个留言的人吧