我把 Flask 进程搞成 Windows 服务的踩坑日记: NSSM 失败 4 次,最后 schtasks.exe 5 分钟搞定

📅 2026-09-18 ⏱️ 8 分钟阅读 🏷️ DevOps ✍️ BaccAI Team
上周搞 W7 续期的时候发现一个事: PHD Flask 进程又双叒叕死了。重启就行,但不知道什么时候死这件事让我很崩溃——

9/7 我花了一整个下午,把 subprocess.POPEN / NSSM / NSSM+bat / NSSM+完整 Python 路径 4 种方案都试了一遍。前 3 种全 fail,第 4 种我自己都觉得恶心了。最后 5 分钟,放弃 NSSM 改用 Windows 原生 schtasks.exe + watchdog bat 循环——一次过。

这篇文章把4 次失败 + 1 次成功的完整过程写下来,附可复制的命令,帮你少踩 4 小时的坑。
Flask Windows 服务化 4 次失败 1 次成功时间线
9/7 下午的完整时间线,15:00 开始到 18:30 收工

一、问题: Flask 隔几天就死一次

我服务器跑两个 Flask:

两个都用 flask runpython app.py 启动,看似正常。但实际:PHD 进程大概每 2-4 天死一次,老 PRO 稍长但也会死

死的原因五花八门:

最坑的是: 死了没有任何告警。等用户邮件反馈"网站打不开"已经过去几个小时。

所以 9/7 我决定花一个下午彻底解决:让 Flask 像 Windows 服务一样,死了自动重启,服务器重启也能自动拉起。

二、第一次尝试: subprocess.Popen + DETACHED_PROCESS (失败)

花了 20 分钟写代码,实际跑 1 秒死。失败原因: subprocess.Popen 在父进程退出时,子进程被一起带走。

我最开始想的是"在 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。

NSSM 创建服务成功,但启动失败,错误代码 2。NSSM 自查失败,没写任何 log。

我开始排查:

  1. Python 路径有空格,需要加引号 → 加了,还是同样错误
  2. Python 是 Windows Store 装的,服务在 LocalSystem 上下文跑可能没权限 → 改成 Administrator 账号,失败
  3. AppParameters 写法不对 → 改成 AppParameters="D:\phd824\app.py",失败
  4. NSSM 版本太老 → 下载最新 2.24,失败
  5. 试试 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 本身在这台机器跑不通。

累计花了 3.5 小时。我决定彻底放弃 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 同样写一个,只是路径换一下。

关键设计:

整个 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

参数说明:

注册后立即启动:

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 了。

从决定放弃 NSSM 到 5 分钟内搞定 schtasks 方案,两个服务全部稳定跑起来。
schtasks 注册和 watchdog 循环流程
最终方案: schtasks + watchdog bat + 5 秒循环重启

七、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。

到 9/13 这篇文章发布为止,这套方案跑了 6 天,PHD + PRO 都稳定。两个加起来 288 小时无故障,期间服务器重启过 1 次,30 秒后自动恢复。

九、为什么 NSSM 在我机器上跑不通?

9/8 排查后我大致猜到原因,虽然没 100% 确认:

不重要了。我换 schtasks 方案,问题彻底消失。NSSM 粉丝可能会说"你用错版本了"——但我跟 NSSM 没仇,只是这工具在我这 4 小时搞不定,schtasks 5 分钟搞定,我选省时间的。

如果你也遇到 NSSM "系统找不到指定的文件",别挣扎,直接换 schtasks.exe。Windows 原生、零依赖、零 ACL 问题,稳定运行 6 天没毛病。

十、完整的启动参考卡 (我贴在 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 秒搞定。

常见问题 FAQ

Flask 在 Windows 上用什么方式跑最稳?
直接双击 python.exe app.py 是最简单的,但服务器重启就死了。NSSM 是经典方案但有兼容性坑。schtasks.exe (Task Scheduler) + watchdog bat 是最稳的: 开机自启 + 死了 5 秒自动重启,比 NSSM 配置简单 10 倍。我自己 4 次失败后最终用这套方案。
NSSM 一直报 '系统找不到指定的文件' 怎么办?
通常是这 3 个原因之一: 1) Application 路径写错(用 cmd /c 'nssm get ServiceName Application' 验证),2) Windows Store 装的 Python 路径有空格导致 NSSM 解析失败,3) NSSM 版本跟系统不兼容。直接放弃 NSSM 改用 schtasks.exe,零依赖,Windows 原生,5 分钟搞定。
watchdog bat 的循环怎么写才不卡?
用 :loop 标签 + goto loop 死循环,中间 timeout /t 5 /nobreak 强制等 5 秒。关键是: stdout 和 stderr 重定向到日志文件 (1>>log 2>>err.log),这样死了能查原因。Python 启动前先 cd /d 切到工作目录,否则相对路径全部失效。整段不超过 10 行。
Flask 进程被杀毒软件杀掉怎么办?
Windows Defender 默认会扫描 python.exe 启动的子进程,如果你的 Flask 监听了高位端口 (8443/8000 等) 或启动了子进程,可能触发 WD 启发式扫描。加白名单: Windows Security → Virus & threat protection → Exclusions → Add folder exclusion,把 Python 完整路径加进去。如果还死,改用 waitress (替代 Flask dev server) 可以减少子进程被 WD 怀疑的概率。
schtasks 和 NSSM 哪个更适合 Flask 生产?
我站 schtasks.exe。3 个理由: 1) Windows 原生,不需要下载第三方 exe;2) 配置简单 8 行命令搞定,NSSM 至少 15 行 + 各种 ACL 排查;3) 出问题容易诊断,Event Viewer 直接看 NSSM 错误,schtasks 任务状态 PowerShell 一行命令。NSSM 唯一优势是 'AppExit Default Restart' 自动重启策略,但 watchdog bat 5 秒循环重启一样能实现,代码更短。

📌 想看 HTTPS / Flask 部署的其他实操?

📱 8000 端口装 SSL 6 小时 🔄 90 天续期 SOP 🔐 HTTPS 加密原理 5 分钟