我把 Flask 进程搞成 Windows 服务的踩坑日记: NSSM 失败 4 次,最后 schtasks.exe 5 分钟搞定
9/7 我花了一整个下午,把 subprocess.POPEN / NSSM / NSSM+bat / NSSM+完整 Python 路径 4 种方案都试了一遍。前 3 种全 fail,第 4 种我自己都觉得恶心了。最后 5 分钟,放弃 NSSM 改用 Windows 原生
schtasks.exe + watchdog bat 循环——一次过。这篇文章把4 次失败 + 1 次成功的完整过程写下来,附可复制的命令,帮你少踩 4 小时的坑。
一、问题: Flask 隔几天就死一次
我服务器跑两个 Flask:
- PHD: HTTPS 8443 (ZeroSSL 证书) + HTTP 8080,跑 BaccAI 主力 AI 预测工具
- 老 PRO: HTTPS 8000 + HTTP 80 (IIS 80 端口被占,80 跳 HTTPS 自动放弃),跑 v3 算法
两个都用 flask run 或 python app.py 启动,看似正常。但实际:PHD 进程大概每 2-4 天死一次,老 PRO 稍长但也会死。
死的原因五花八门:
- Windows Update 自动重启
- 杀软清理"长时间无活动"进程
- 某些边缘 case 触发 Python 异常退出 (我没花时间追)
- 电源管理把服务器误判成"空闲"
最坑的是: 死了没有任何告警。等用户邮件反馈"网站打不开"已经过去几个小时。
所以 9/7 我决定花一个下午彻底解决:让 Flask 像 Windows 服务一样,死了自动重启,服务器重启也能自动拉起。
二、第一次尝试: subprocess.Popen + DETACHED_PROCESS (失败)
我最开始想的是"在 Flask 启动时 detach 自己",代码大概这样:
import subprocess
import sys
if '--detached' not in sys.argv:
subprocess.Popen(
[sys.executable, __file__, '--detached'],
creationflags=subprocess.DETACHED_PROCESS | subprocess.CREATE_NEW_PROCESS_GROUP,
close_fds=True,
)
sys.exit(0)
# 否则启动 Flask
app.run(host='0.0.0.0', port=8443, ssl_context=('cert.pem', 'key.pem'))
逻辑看起来对:第一次启动,自己 spawn 一个 detach 的副本,然后退出当前;副本启动 Flask,跟终端解耦。
实际: 父进程退出,Windows 把整个 process tree 一起清理。Microsoft 文档写的是 "DETACHED_PROCESS 不会自动关闭控制台",但实践上,我用 RDP 远程登出,Python 子进程跟着死。
更糟的是: 死了不自动重启。DETACHED_PROCESS 只是 detach,没有守护进程盯着。
这方案 5 分钟进垃圾桶。
三、第二次尝试: NSSM (卡了 1 小时)
NSSM (Non-Sucking Service Manager) 是 Windows 上最流行的"把任意 exe 包装成服务"的工具。
原理: NSSM 装一个 Windows 服务,服务启动时 NSSM 去拉你配置的 exe,exe 死了 NSSM 自动重启。
听起来完美,我直接下载 nssm.exe 放到 C:\Windows\System32\,然后跑:
cmd /c "nssm install BaccAI_PHD D:\phd824\app.py"
cmd /c "nssm set BaccAI_PHD AppDirectory D:\phd824"
cmd /c "nssm set BaccAI_PHD AppStdout D:\phd824\app.log"
cmd /c "nssm set BaccAI_PHD AppStderr D:\phd824\app_err.log"
cmd /c "nssm set BaccAI_PHD AppRestartDelay 5000"
cmd /c "nssm set BaccAI_PHD AppExit Default Restart"
cmd /c "nssm start BaccAI_PHD"
第一次跑 nssm start,返回:
BaccAI_PHD: START: 系统找不到指定的文件。
我检查了一遍:D:\phd824\app.py 文件存在,目录可访问,Python 3.8.10 装在 C:\Users\Administrator\AppData\Local\Programs\Python\Python38\python.exe 也存在。
查 Event Log 拿到:
EventID: 7000
由于下列错误,BaccAI_PHD 服务启动失败: %%2
%%2 = ERROR_FILE_NOT_FOUND。
我开始排查:
- Python 路径有空格,需要加引号 → 加了,还是同样错误
- Python 是 Windows Store 装的,服务在 LocalSystem 上下文跑可能没权限 → 改成 Administrator 账号,失败
- AppParameters 写法不对 → 改成 AppParameters="D:\phd824\app.py",失败
- NSSM 版本太老 → 下载最新 2.24,失败
- 试试 cmd.exe 作为 Application + /c 调 Python → 失败
2 小时过去,4 种变体全 fail。我开始怀疑是 NSSM 本身在这台机器坏掉了。
最后我做了一次"白板测试":
# 用 NSSM 起一个最简单的 echo 看看
cmd /c "nssm install BaccAI_TEST C:\Windows\System32\cmd.exe"
cmd /c 'nssm set BaccAI_TEST AppParameters /c "echo HELLO > D:\test.log"'
cmd /c "nssm start BaccAI_TEST"
结果: 同样报"系统找不到指定的文件"。
这说明 NSSM 本身在这台机器彻底坏掉了——连 cmd.exe 都起不来,根本不是 Python 的问题。
花 3 小时了。我决定放弃 NSSM。
四、第三次尝试: NSSM + bat 中转 (失败)
临死挣扎,我想是不是 bat 文件本身的问题,试试用 bat 包一层:
@echo off
cd /d D:\phd824
C:\Users\Administrator\AppData\Local\Programs\Python\Python38\python.exe app.py
然后 NSSM Application 指向 D:\phd824\start_phd.bat。
结果: 同样"系统找不到指定的文件"。NSSM 仍然起不来,bat 都没机会跑。
这进一步确认是 NSSM 本身的问题,跟 bat 内容无关。
五、第四次尝试: NSSM + python.exe 完整路径 (失败)
最后一搏:不用 bat,直接 NSSM Application = python.exe 完整路径,AppParameters = app.py 绝对路径。
cmd /c 'nssm install BaccAI_PHD "C:\Users\Administrator\AppData\Local\Programs\Python\Python38\python.exe"'
cmd /c 'nssm set BaccAI_PHD AppParameters "D:\phd824\app.py"'
cmd /c "nssm set BaccAI_PHD AppDirectory D:\phd824"
cmd /c "nssm start BaccAI_PHD"
结果: 还是同样错误。
这彻底证明 NSSM 跟 Python 路径、bat 路径、权限都没关系,就是 NSSM 本身在这台机器跑不通。
六、破局: 改用 schtasks.exe (5 分钟搞定)
突然想起: Windows 有原生的 Task Scheduler,命令行工具叫 schtasks.exe。这个不需要任何第三方 exe,Windows 自带,而且配置比 NSSM 简单 10 倍。
关键 insight: Task Scheduler 跑的任务,默认在 SYSTEM 账号下,有完整的管理员权限。死循环由我自己写 bat 实现,不依赖 NSSM 的 AppExit Restart 策略。
Step 1: 写 watchdog bat
思路很简单:一个无限循环,启动 Python,Python 死了等 5 秒再启动。
@echo off
:loop
echo [%date% %time%] PHD starting...
cd /d D:\phd824
C:\Users\Administrator\AppData\Local\Programs\Python\Python38\python.exe app.py 1>>D:\phd824\app.log 2>>D:\phd824\app_err.log
echo [%date% %time%] PHD exited, restarting in 5s
timeout /t 5 /nobreak >nul
goto loop
老 PRO 同样写一个,只是路径换一下。
关键设计:
- :loop / goto loop = 死循环
- timeout /t 5 /nobreak = 等 5 秒,避免疯狂重启
- 1>>log 2>>err.log = stdout / stderr 重定向,死了能查原因
- cd /d = 切到工作目录,Python 内部相对路径才能用
整个 bat 10 行不到。
Step 2: schtasks.exe 注册开机任务
比 NSSM 简单太多:
schtasks /Create /TN "BaccAI_PHD" /TR "D:\phd824\run_phd_loop.bat" /SC ONSTART /RL HIGHEST /RU SYSTEM /F
schtasks /Create /TN "BaccAI_PRO" /TR "D:\20260516\vb_bendi_v24\run_pro_loop.bat" /SC ONSTART /RL HIGHEST /RU SYSTEM /F
参数说明:
/TN= Task Name(任务名)/TR= Task Run(要执行的命令)/SC ONSTART= 触发器:开机启动/RL HIGHEST= 最高权限/RU SYSTEM= 用 SYSTEM 账号跑(LocalSystem 等价,但参数名不一样)/F= 强制覆盖(任务已存在也覆盖)
注册后立即启动:
schtasks /Run /TN "BaccAI_PHD"
schtasks /Run /TN "BaccAI_PRO"
Step 3: 验证
netstat -an | findstr ":8443 :8000 "
看到:
TCP 0.0.0.0:8443 0.0.0.0:0 LISTENING
TCP 0.0.0.0:8000 0.0.0.0:0 LISTENING
两个 Flask 跑起来了。打开 D:\phd824\app_err.log:
* Running on https://0.0.0.0:8443
* Running on http://127.0.0.1:8080
标准 Flask 启动信息。OK 了。
七、watchdog 自愈测试 (5 秒拉起)
写完任务后,我手动 kill 一下 PHD 进程,验证 watchdog 真的会重启:
Get-Process python | Where-Object {$_.Id -eq 51524} | Stop-Process -Force
Start-Sleep -Seconds 10
Get-Process | Where-Object {$_.ProcessName -eq "python"} | Select Id, StartTime
10 秒后查,python 进程 StartTime 是新的,Id 变了。watchdog bat 在 5 秒 timeout 后自动重新启动了 Python。
连续 3 次 kill,3 次都被自动拉起。watchdog 有效。
八、健康检查: 死了发邮件告警
虽然有 watchdog 自动重启,但 5 秒内服务会短暂不可用。如果 Flask 启动本身卡住(比如数据库连接不上),watchdog 一直重启也救不了。
我加了 5 分钟一次的健康检查:
# D:\seo\healthcheck.py
import urllib.request
import socket
import json
from datetime import datetime
def check_port(host, port):
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(3)
s.connect((host, port))
s.close()
return True
except Exception:
return False
def send_alert(subject, body):
req = urllib.request.Request(
"https://api.resend.com/emails",
data=json.dumps({
"from": "alert@baccai.com",
"to": ["your-email@example.com"],
"subject": subject,
"text": body
}).encode("utf-8"),
headers={
"Authorization": "Bearer re_xxxxxxxxxxxx",
"Content-Type": "application/json"
}
)
urllib.request.urlopen(req, timeout=10)
# 检查 PHD 8443 + PRO 8000
results = []
for name, port in [("PHD", 8443), ("PRO", 8000)]:
if not check_port("www.baccpc.com", port):
results.append(f"DOWN: {name}:{port}")
if results:
send_alert(
"[BaccAI Alert] " + str(len(results)) + " service DOWN",
"\n".join(results) + "\n\nTime: " + str(datetime.now())
)
注册为 5 分钟一次任务:
schtasks /Create /TN "BaccAI_HealthCheck" /TR "C:\Users\...\python.exe D:\seo\healthcheck.py" /SC MINUTE /MO 5 /RL HIGHEST /RU SYSTEM /F
以后任何 Flask 死了超过 5 分钟,我邮箱就会收到告警。不用刷 log。
九、为什么 NSSM 在我机器上跑不通?
9/8 排查后我大致猜到原因,虽然没 100% 确认:
- Windows Store Python 路径有特殊 ACL:
C:\Users\Administrator\AppData\Local\Programs\Python\Python38\这个目录除了 Administrator 和 SYSTEM,还有很多 AppContainer SID(WindowsApps 标识符)。LocalSystem (NSSM 默认)跟这些 SID 有冲突。 - WDAC (Windows Defender Application Control) 策略: 服务器可能启用了某种应用白名单,不允许 LocalSystem 启动非系统盘的 Python。
- NSSM 版本兼容性: 2.24 在 Windows Server 2019 / 2022 上有报告存在类似问题。
不重要了。我换 schtasks 方案,问题彻底消失。NSSM 粉丝可能会说"你用错版本了"——但我跟 NSSM 没仇,只是这工具在我这 4 小时搞不定,schtasks 5 分钟搞定,我选省时间的。
十、完整的启动参考卡 (我贴在 D:\seo\START_COMMANDS.txt)
为了以后(或者别人接手)不用再踩坑,我把所有命令整理到一个文件里,放在服务器 D:\seo\START_COMMANDS.txt。核心 4 个步骤:
Step 1: 状态检查
netstat -an | findstr ":8443 :8000 :8080"
Get-Process python | Select Id, StartTime
Step 2: 手动启动 (单次, 不死循环)
:: PHD
cd /d D:\phd824
C:\Users\Administrator\AppData\Local\Programs\Python\Python38\python.exe app.py
:: PRO
cd /d D:\20260516\vb_bendi_v24
C:\Users\Administrator\AppData\Local\Programs\Python\Python38\python.exe app.py
Step 3: watchdog 启动 (生产用, 推荐)
Start-Process D:\phd824\run_phd_loop.bat
Start-Process D:\20260516\vb_bendi_v24\run_pro_loop.bat
Step 4: 紧急重启
Get-Process python -ErrorAction SilentlyContinue | Stop-Process -Force
timeout /t 5 /nobreak
Start-Process D:\phd824\run_phd_loop.bat
Start-Process D:\20260516\vb_bendi_v24\run_pro_loop.bat
timeout /t 15 /nobreak
netstat -an | findstr ":8443 :8000 "
任何时候服务挂了,30 秒搞定。