Flask on Windows Service Pitfalls: 4 NSSM Fails, schtasks Solved It in 5 Minutes

📅 September 18, 2026 ⏱️ 8 min read 🏷️ DevOps ✍️ BaccAI Team
While working on the W7 HTTPS renewal last week, I noticed: my PHD Flask process had died again. A restart would fix it, but not knowing when it dies was driving me crazy —

On Sept 7 I spent an entire afternoon trying 4 different approaches: subprocess.POPEN / NSSM / NSSM+bat / NSSM+full Python path. The first 3 failed, the 4th made me sick of NSSM. In the last 5 minutes, I gave up on NSSM and switched to Windows-native schtasks.exe + watchdog bat loop — first try worked.

This article documents the 4 fails + 1 success complete process, with copy-paste commands, to save you 4 hours of pain.
Flask Windows service 4 fails 1 success timeline
Complete timeline from Sept 7 afternoon, 15:00 to 18:30 wrap-up

1. Problem: Flask Dies Every Few Days

My server runs two Flask apps:

Both start with flask run or python app.py, seems normal. But in reality: PHD dies every 2-4 days, old PRO lasts a bit longer but also dies.

Death reasons are a mixed bag:

Worst part: no alert on death. By the time users email me "site is down", hours have passed.

So on Sept 7 I decided to spend an afternoon fixing this: get Flask behaving like a Windows service, auto-restart on death, auto-launch on server reboot.

2. First Attempt: subprocess.Popen + DETACHED_PROCESS (Failed)

20 minutes writing code, dies in 1 second. Reason: when parent process exits, subprocess.Popen child is taken down with it.

My initial idea was "have Flask detach itself on startup", code like this:

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)
# else start Flask
app.run(host='0.0.0.0', port=8443, ssl_context=('cert.pem', 'key.pem'))

Logic looks right: first launch, self-spawn a detached copy, then exit; copy starts Flask, decoupled from terminal.

Reality: when parent exits, Windows cleans up the whole process tree. Microsoft docs say "DETACHED_PROCESS won't auto-close console", but in practice, when I logged out via RDP, the Python child died with it.

Worse: no auto-restart on death. DETACHED_PROCESS just detaches, no supervisor process watching.

This approach hit the trash in 5 minutes.

3. Second Attempt: NSSM (Stuck for 1 Hour)

NSSM (Non-Sucking Service Manager) is the most popular tool on Windows for "wrapping any exe as a service".

Principle: NSSM installs a Windows service, on service start NSSM launches the exe you configured, when exe dies NSSM auto-restarts it.

Sounds perfect. I downloaded nssm.exe, dropped it in C:\Windows\System32\, and ran:

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"

First nssm start returned:

BaccAI_PHD: START: The system cannot find the file specified.

I checked everything: D:\phd824\app.py exists, directory accessible, Python 3.8.10 at C:\Users\Administrator\AppData\Local\Programs\Python\Python38\python.exe also exists.

Event Log showed:

EventID: 7000
The BaccAI_PHD service failed to start due to the following error: %%2

%%2 = ERROR_FILE_NOT_FOUND.

NSSM created the service OK, but failed to start with error 2. NSSM self-diagnosis failed, wrote nothing to log.

My troubleshooting steps:

  1. Python path has spaces, need quotes → added, still same error
  2. Python is Windows Store installed, service runs in LocalSystem context may lack permission → switched to Administrator account, failed
  3. AppParameters format wrong → set to AppParameters="D:\phd824\app.py", failed
  4. NSSM version too old → downloaded latest 2.24, failed
  5. Try cmd.exe as Application + /c calling Python → failed

2 hours in, 4 variants all fail. I started suspecting NSSM itself was broken on this machine.

Final "whiteboard test":

# Use NSSM to launch the simplest echo to see
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"

Result: same "file not found" error.

This confirmed NSSM was completely broken on this machine — couldn't even launch cmd.exe, not a Python issue at all.

3 hours spent. Decision: abandon NSSM.

4. Third Attempt: NSSM + bat Wrapper (Failed)

Last gasp, I thought maybe it was the .bat file itself. Wrap it in a bat:

@echo off
cd /d D:\phd824
C:\Users\Administrator\AppData\Local\Programs\Python\Python38\python.exe app.py

Then NSSM Application pointed to D:\phd824\start_phd.bat.

Result: same "file not found" error. NSSM still couldn't start, the bat never got a chance to run.

This further confirmed it was NSSM itself, not the bat content.

5. Fourth Attempt: NSSM + python.exe Full Path (Failed)

Final throw: skip the bat, NSSM Application = python.exe full path, AppParameters = app.py absolute path.

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"

Result: same error.

This conclusively proved NSSM was broken on this machine, unrelated to Python path, bat path, or permissions.

Cumulative 3.5 hours. Decision: completely abandon NSSM, switch approach.

6. Breakthrough: Switched to schtasks.exe (5 Minutes)

Suddenly remembered: Windows has native Task Scheduler, command-line tool is schtasks.exe. No third-party exe needed, Windows built-in, and config is 10x simpler than NSSM.

Key insight: Task Scheduler tasks run under SYSTEM account by default with full admin permissions. The death loop I implement in my own bat, not relying on NSSM's AppExit Restart policy.

Step 1: Write the Watchdog Bat

Simple idea: infinite loop, launch Python, when Python dies wait 5 seconds and launch again.

@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

Old PRO gets a similar bat, just different path.

Key design points:

Total bat is under 10 lines.

Step 2: schtasks.exe Register Boot Tasks

Way simpler than 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

Parameter explanation:

Launch immediately after registration:

schtasks /Run /TN "BaccAI_PHD"
schtasks /Run /TN "BaccAI_PRO"

Step 3: Verify

netstat -an | findstr ":8443  :8000 "

Output:

  TCP    0.0.0.0:8443           0.0.0.0:0              LISTENING
  TCP    0.0.0.0:8000           0.0.0.0:0              LISTENING

Both Flask apps running. Opened D:\phd824\app_err.log:

* Running on https://0.0.0.0:8443
* Running on http://127.0.0.1:8080

Standard Flask startup info. Done.

From deciding to abandon NSSM to having both services stable in 5 minutes.
schtasks registration and watchdog loop flow
Final solution: schtasks + watchdog bat + 5-second loop restart

7. Watchdog Self-Healing Test (5-Second Recovery)

After setting up tasks, I manually killed PHD to verify watchdog really restarts:

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 seconds later, check: python process StartTime is new, Id changed. Watchdog bat auto-restarted Python after the 5-second timeout.

Killed 3 times in a row, all auto-restarted. Watchdog works.

8. Health Check: Email Alert on Death

Even with watchdog auto-restart, the service is briefly unavailable for 5 seconds. If Flask startup itself hangs (e.g., database connection fails), watchdog keeps restarting uselessly.

I added a 5-minute health check:

# 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)

# Check 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())
    )

Register as 5-minute task:

schtasks /Create /TN "BaccAI_HealthCheck" /TR "C:\Users\...\python.exe D:\seo\healthcheck.py" /SC MINUTE /MO 5 /RL HIGHEST /RU SYSTEM /F

Now if any Flask stays down for more than 5 minutes, I get an email alert. No more log watching.

As of this article's publication (Sept 18), this stack has been running 11 days stable. PHD + PRO both up. Combined 528 hours no failure. Server rebooted once, auto-recovered in 30 seconds.

9. Why Did NSSM Not Work on My Machine?

After Sept 8 investigation, I have a rough guess (not 100% confirmed):

Doesn't matter anymore. Switched to schtasks, problem disappeared. NSSM fans might say "you used the wrong version" — but I have no grudge against NSSM, the tool just couldn't be made to work in 4 hours here, and schtasks solved it in 5 minutes. I choose what saves time.

If you also hit NSSM "file not found", don't struggle. Switch to schtasks.exe. Windows native, zero dependencies, zero ACL issues. 11 days stable, no issues.

10. Complete Start Reference Card (Saved to D:\seo\START_COMMANDS.txt)

To prevent future me (or whoever inherits this) from re-discovering all this, I consolidated all commands into one file at D:\seo\START_COMMANDS.txt on the server. 4 core steps:

Step 1: Status Check

netstat -an | findstr ":8443  :8000  :8080"
Get-Process python | Select Id, StartTime

Step 2: Manual Launch (One-time, no loop)

:: 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 Launch (Production, recommended)

Start-Process D:\phd824\run_phd_loop.bat
Start-Process D:\20260516\vb_bendi_v24\run_pro_loop.bat

Step 4: Emergency Restart

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 "

Any time service dies, 30 seconds to recover.

FAQ

What's the most stable way to run Flask on Windows?
Double-clicking python.exe app.py is simplest but dies on server reboot. NSSM is the classic but has compatibility issues. schtasks.exe (Task Scheduler) + watchdog bat is the most stable: autostart on boot + 5-second auto-restart on crash, 10x simpler to configure than NSSM. After 4 failed attempts, I ended up using this stack.
NSSM keeps saying 'The system cannot find the file specified' — what to do?
Usually one of 3 reasons: 1) Application path is wrong (verify with 'nssm get ServiceName Application'), 2) Windows Store Python has spaces in the path that confuse NSSM, 3) NSSM version incompatible with the OS. Skip NSSM entirely and use schtasks.exe — zero dependencies, Windows native, 5 minutes to set up.
How to write a watchdog bat loop that doesn't hang?
Use :loop label + goto loop for infinite loop, with timeout /t 5 /nobreak to force 5-second wait between restarts. Key: redirect stdout and stderr to log files (1>>log 2>>err.log) so you can diagnose failures. cd /d to the working directory before running Python, otherwise relative paths break. The whole bat is under 10 lines.
What if Windows Defender keeps killing my Flask process?
Windows Defender heuristically scans child processes spawned by python.exe, especially if your Flask binds high ports (8443/8000). Add exclusions: Windows Security → Virus & threat protection → Exclusions → Add folder exclusion, add the full Python path. If it still dies, switch to waitress (replaces Flask dev server) which spawns fewer child processes that WD might flag.
Is schtasks or NSSM better for Flask production?
I go with schtasks.exe. 3 reasons: 1) Windows native, no third-party exe to download; 2) 8 lines of config vs NSSM's 15+ lines plus ACL troubleshooting; 3) Easy to diagnose — Event Viewer shows NSSM errors, schtasks task status is one PowerShell line. NSSM's only edge is 'AppExit Default Restart' but watchdog bat's 5-second loop achieves the same thing with shorter code.

📌 Want more HTTPS / Flask deployment guides?

📱 Installing SSL on port 8000 🔄 90-day renewal SOP 🔐 HTTPS Encryption Explained