NSSM 2.24实战:Python脚本Windows服务化与自动重启日志管理
简介NSSM服务管理器Non-Sucking Service Manager的构建版本面向系统管理员、运维人员及服务开发调试者用于将任意可执行程序封装为Windows后台服务解决程序需随系统启动、用户未登录也可持续运行的问题尤其适合需要长期运行的Web服务、数据库和自研工具等常驻程序。压缩包共40个文件包含32位与64位的nssm.exe可执行程序以及15个.h和14个.cpp完整C/C源码、Visual Studio工程文件、版本脚本、图标资源、说明文档与更新日志整体仅376KBWin32与Win64目录划分清晰结构紧凑。已有429人学习下载。使用时可选用现成工具通过命令行或图形界面把exe注册为服务配置启动参数、环境变量、日志记录与失败自动重启也可阅读源码了解服务封装机制按需定制后自行编译。总体来看这份资源兼顾日常部署与源码级研究适合不同层级的Windows服务使用者。1. NSSM 2.24-101把脚本交给 Windows 服务管理器前先搞懂这三件事NSSMNon-Sucking Service Manager是个老牌的 Windows 服务封装工具2.24-101-g897c7ad 是 2.24 系列里的一个开发构建版本作用是把普通 exe、bat、python 脚本注册成系统服务让它们在后台稳定运行、崩溃自动拉起、日志可重定向。之前维护某公司内部运维系统时一批轮询脚本老是被“计划任务 窗口常驻”的方式搞得半死不活后来统一换成 NSSM 才彻底消停。适合想跑后台任务又不想改代码去写 Windows 服务的那类从业者也适合给生产环境做服务化封装时不想踩 sc create 那些暗坑的人。这篇把 NSSM 的参数、安装步骤、验证方法和常见翻车点完整拆一遍。2. 为什么要用 NSSMsc create、srvany 与 NSSM 的选型对比2.1 原生 sc create 注册服务的三个尴尬Windows 自带 sc create 可以把程序注册成服务但真用起来会碰到很多别扭的地方。最典型的写法是这样:: 把 bat 脚本注册成服务不推荐的写法仅示范 sc create my-task binPath cmd.exe /c D:\scripts\run.bat start auto先说第一个问题这条命令能注册成功但服务启动瞬间会退出因为 cmd.exe 把脚本跑完就结束了服务状态跟着变成“已停止”。要让它常驻要么脚本里自己写死循环要么借助工具sc 本身没有“进程退出后自动拉起”的能力脚本崩了就是崩了。第二个问题是输出全部丢进黑洞脚本里 print 出来的内容、异常堆栈都没地方看排查问题只能靠猜。第三个问题是路径转义binPath 里一旦出现空格、引号、斜杠组合转义规则很容易把人绕进去经常是注册完启动报 1053 错误平台日志里什么有效信息都没有。那这段 sc create 到底适合什么场景适合临时起一个不需要管死活、不需要看日志的一次性服务。生产环境里的脚本服务化光靠 sc create 显然不够后面才去找 NSSM 这类第三方工具。2.2 NSSM 相对 srvany 的核心能力自动拉起、日志重定向、GUI 管理除了 sc create另一个常被提到的方案是 Windows SDK 里自带的 srvany早期很多人用它把 exe 包成服务。srvany 的问题在于功能太素没有自动重启程序退出后服务不会主动再拉起没有日志重定向所有输出照样丢失配置存在注册表里想改一个参数得手动打开注册表编辑器找个键找半天改错了服务直接起不来。NSSM 在这些方面是碾压级的。我整理过一张对比表基本能看出差距能力sc createsrvanyNSSM进程退出后自动重启无无默认支持stdout/stderr 重定向到文件无无内置参数直配日志按大小/时间轮转无无内置图形界面管理参数无无nssm install/edit 弹窗环境变量注入无注册表手改AppEnvironmentExtra停止行为控制无无可配置杀进程/发消息开机启动延迟手动算时间手动算时间Startup 直接选 Delayed自动重启是 NSSM 最值钱的能力。程序异常退出后默认行为就是等几秒重新拉起不用在脚本里写 watchdog。日志重定向则是把服务进程的 stdout 和 stderr 分别指向文件配合轮转参数日志再也不会无限长下去。而这一切在 GUI 里都能改装完服务后随时 nssm edit 打开配置界面这比 sc create 删了重建、srvany 手改注册表舒服得多。2.3 版本号 2.24-101-g897c7ad 意味着什么这个版本号值得解释一下。“2.24”是上游的正式版本基线“101”表示从 2.24 标签之后又合入了 101 个提交“g897c7ad”是 Git 提交哈希的前缀表示当前这个构建对应的准确代码状态。也就是说它不是一个官方打的稳定版本号而是 2.24 之后的一个持续构建产物通常包含后续修复过的 bug 和一些小改进整体行为与 2.24 基本一致但在个别边界情况下更稳。实际部署时不必纠结于“开发构建不用于生产”的刻板印象。我的习惯是优先选用同期官方发布渠道里的最新构建且在测试机上先把重启、日志轮转两条主链路验证通过再上线。要注意别拿 1.x 老版本的参数经验直接套到 2.24 上比如 AppRotateOnline、AppKillProcessConsole 这些参数在老版本里没有配置了反而会被忽略。3. 把 Python 脚本注册成系统服务安装、启动、验证三连3.1 先写一个不自爆的脚本不是所有脚本都适合直接注册成服务。脚本第一不能弹窗口服务运行在 Session 0GUI 根本显示不出来第二不能依赖用户登录状态第三要能一直跑下去不能跑一次就自己退出。一个合格的常驻脚本长这样import os import time import datetime # 模拟一个轮询任务每 5 秒写一条心跳日志 def main(): # 打印进程 PID方便后面去 Task Manager / tasklist 里核对 print(service started, pid, os.getpid(), flushTrue) while True: now datetime.datetime.now().strftime(%Y-%m-%d %H:%M:%S) print(f{now} heartbeat, flushTrue) time.sleep(5) if __name__ __main__: main()逻辑说明while True 让进程保持常驻这是服务化脚本的基本前提每次循环打印带时间戳的心跳NSSM 会把 stdout 重定向到日志文件方便确认服务活着。flushTrue 很关键python 的 print 默认有缓冲不强制 flush 的话日志会积在内存里服务崩溃时最后几条记录就丢了排查时少一堆线索。参数说明time.sleep(5) 控制心跳频率实际项目里按业务需要改成几秒或几十秒都行os.getpid() 只为了让服务端进程和任务管理器里的 PID 对得上人多手杂时排查进程残留非常有用。3.2 安装服务命令行与 GUI 两种姿势NSSM 安装服务的命令行写法很直接:: 进入 nssm.exe 所在目录按实际路径修改 cd /d D:\tools\nssm :: 安装服务服务名 my-task程序 python.exe参数是脚本路径 nssm install my-task C:\Python39\python.exe D:\scripts\run.py逻辑说明nssm install 的语法是“服务名 程序 参数”参数会原样保存到服务配置里。上面这条命令等价于在 GUI 里把 Path 填成 python.exe、Arguments 填成脚本路径。服务名 my-task 是后续启动、停止、修改配置时都要用的唯一标识建议起得有意义一点比如按业务模块加前缀。参数说明程序路径建议写全路径不要依赖 PATH 环境变量因为服务启动时用的是系统环境用户 PATH 不一定被加载。脚本路径如果带空格用引号包住整体nssm install my-task C:\Python39\python.exe D:\my scripts\run.py。注册完之后一般还要补两件事日志重定向和重启延迟。命令行下用 nssm set 配置nssm set my-task AppStdout D:\logs\my-task.log nssm set my-task AppStderr D:\logs\my-task.log nssm set my-task AppRestartDelay 10000 nssm set my-task AppExit Default Restart逻辑说明AppStdout 和 AppStderr 把脚本输出和报错分别写进文件这里指向同一个文件方便整体看AppRestartDelay 单位是毫秒10000 表示进程异常退出后等 10 秒再拉起避免程序反复崩溃时疯狂重启AppExit Default Restart 显式声明“不管什么退出码都重启”防止其他人误改成 Exit。参数说明如果希望“脚本正常退出也重启”保持上面配置即可如果希望脚本正常退出时服务真的停掉把 AppExit Default 改成 Exit异常崩溃时 NSSM 依然会拉起。这个按业务逻辑取舍不能照搬。另一种安装姿势是 GUI。在 cmd 里执行 nssm install my-task直接弹出安装窗口几个字段填一下就行Pathpython.exe 的完整路径不要加引号Startup directory脚本所在目录脚本里有相对路径时必填Arguments脚本路径和参数带空格时用引号Service name安装时定了GUI 里是灰的Display name显示在服务列表里的名字比如“模拟项目X后台任务”GUI 和命令行最终写进同一份配置不存在两套结果只是有些参数命令行写起来更快有些参数比如多行环境变量GUI 更方便。3.3 启动、查状态、杀进程验证自动重启安装完先别急着收工必须做一轮完整的启停验证。先走启动和状态检查# 启动服务 nssm start my-task # 查看服务状态 Get-Service -Name my-task | Select-Object Status, Name, StartType # 查看进程是否活着按服务名过滤 tasklist /fi services eq my-task逻辑说明nssm start 是同步阻塞的返回后服务应该处于 Running。tasklist 的 services 过滤器只显示绑定到该服务的进程能直接确认 nssm.exe 和它拉起的 python.exe 是否都在。Get-Service 看到的是服务状态不一定能看到子进程状态所以两个命令要配合用。参数说明如果 tasklist 过滤出来只有 nssm.exe 没有 python.exe说明脚本没起来按第 4 章第一个坑的方向查如果两个进程都在就可以进入自动重启测试。# 根据上一步查到的 PID强制结束 python 进程模拟脚本崩溃 taskkill /f /pid 上一步查到的PID # 等 11 秒AppRestartDelay 10 秒 启动缓冲 Start-Sleep -Seconds 11 # 再看进程有没有被重新拉起 tasklist /fi services eq my-task逻辑说明这一步是 NSSM 自动重启能力的核心验证。taskkill /f 强制杀掉 python.exe 后注意 Get-Service 的状态不会变成 Stopped因为真正的服务进程是 nssm.exe它还在跑只是子进程没了等 AppRestartDelay 毫秒过去NSSM 会重新拉起脚本。所以判断标准是 tasklist 里再次出现 python.exe而不是看服务状态。这个误判我见过不止一次有人发现“服务状态一直是 Running”就以为不用测重启结果进程根本没被拉起来。参数说明测试完的恢复动作是 nssm stop my-task 再 nssm start my-task把进程清干净避免残留多个 python 实例互相抢资源。多实例场景下杀进程前一定要确认 PID 属于当前服务别误杀别的服务。4. 避坑指南五条翻车记录与排查路径4.1 服务启动即退出事件日志一片空白现象nssm start 返回成功但稍后 Get-Service 状态变成 Stopped脚本没有任何输出事件查看器里也找不到像样的报错一片空白。原因脚本本身启动就报错了。NSSM 只是把进程拉起来脚本一秒内崩溃退出日志没来得及写或者被缓冲吞掉自然什么都不留。另一个隐蔽原因是脚本放在 Session 0 环境下运行时访问了网络盘、数据库等还没就绪的资源启动阶段直接异常。解决先在 cmd 里手动跑一遍脚本确认脚本本身能长期运行。确认脚本没问题后把 AppStdout 配置到位然后 nssm start等几秒看日志文件有没有内容。如果日志文件是空的再看退出码nssm status my-task 会显示最后一次的退出码结合 AppExit 配置判断 NSSM 是否有在拉。我一般会在脚本最开头加一个“打印启动完成”的日志这样能区分“脚本没起来”和“脚本起来了但在初始化时挂掉”。4.2 Path 带引号导致启动失败现象安装时把可执行文件路径写成 C:\Python39\python.exe 带引号服务启动直接失败nssm 报错或者 Windows 返回“服务没有响应控制功能”。原因GUI 里的 Path 字段和 Windows 的 binPath 不一样NSSM 自己会处理路径解析Path 不需要引号引号如果出现在 Path 里NSSM 反而会把引号当成路径的一部分去解析结果程序真的找不到。解决记住一条规则——Path 只填裸路径不带引号路径里的空格由 NSSM 自己处理。参数要带引号时放在 Arguments 字段里。命令行安装时同样逻辑nssm install my-task C:\Python39\python.exe D:\my scripts\run.py程序路径裸写脚本路径按需带引号。这个坑特别邪门因为 Windows 其他工具都习惯性要求路径加引号到 NSSM 这里反过来总有人被绕进去。4.3 日志无限制增长磁盘被写满现象服务跑了一个月后日志文件从几百 MB 涨到几个 GB服务器 C 盘直接被占满其他程序跟着出问题。原因AppStdout 只做了重定向没有开轮转。脚本里不加控制的 print 或异常堆栈持续输出日志单文件无限增长。NSSM 的日志轮转不是默认开启的必须显式配置 AppRotateFiles。解决在 nssm edit 的 I/O 选项卡里打开日志轮转或者命令行设置nssm set my-task AppRotateFiles 1 nssm set my-task AppRotateBytes 10485760 nssm set my-task AppRotateOnline 1逻辑说明AppRotateFiles 开启后NSSM 会在文件大小达到 AppRotateBytes 设定的字节数时切换到一个带时间戳的新文件不中断服务。AppRotateOnline 是让重定向目标在滚动时保持在线旧版本的坑是轮转时必须先停掉重定向会有十几秒的日志丢失窗口2.24 系列基本没这个问题。参数说明AppRotateBytes 推荐从 10MB 起步生产环境里如果单条日志很大可以放到 50MB。要按天轮转再加 AppRotateSeconds 86400。注意 NSSM 的轮转是“切新文件”不是“删除老文件”历史日志要自己用计划任务清理比如只保留最近 7 天的日志文件删文件不会影响正在运行的服务。4.4 服务停止后子进程还赖着不走现象nssm stop my-task 后服务状态显示已停止但 tasklist 里 python.exe 还在端口还被占用再次启动时报地址已被占用。原因脚本里起了子进程、线程池或者没处理停止信号NSSM 停止服务时默认先尝试优雅关闭等到超时后杀进程树但有些 python 程序不响应控制台关闭事件或者子进程脱离了进程树管理导致 NSSM 认为已经停了实际子进程成了孤儿进程。解决先设置停止行为参数再优化脚本退出逻辑nssm set my-task AppKillProcessConsole 1 nssm set my-task AppStopMethodConsole 1000 nssm set my-task AppStopMethodWindow 1000 nssm set my-task AppStopMethodThreads 5000逻辑说明这几个参数表示停止服务时NSSM 先发控制台事件等 1 秒再向窗口发消息等 1 秒最后等线程退出等 5 秒超时后强制终止进程树。这是比较保守的配置。脚本侧更彻底的做法是监听退出信号比如 python 里捕获 SIGINT/SIGTERM在退出前把子线程 join 干净。参数说明生产环境里如果脚本里确实有不好杀的第三方子进程调大 AppStopMethodThreads 的等待时间比直接加暴力参数靠谱强制杀进程容易留下脏状态下次重启服务时可能检测到上次残留而拒绝启动。4.5 开机自启失败手动启动却正常现象服务器重启后服务状态是空的或者启动失败但登录服务器手动 nssm start 一切正常服务跑得很欢。原因服务启动时依赖的环境还没就绪。常见的有脚本访问的数据库服务还没起来、依赖的网络共享盘还没挂载、脚本读的配置文件所在目录属于延迟加载的磁盘。Windows 服务默认的启动顺序又不保证你的服务在依赖对象之后跑于是启动就是撞运气时好时坏。解决把服务启动类型改成“自动延迟启动”让服务在系统引导完成后 2 分钟左右再启动给依赖服务和网络留出时间nssm set my-task Startup AutomaticDelayed逻辑说明AutomaticDelayed 是 Windows 服务管理器层面的延迟启动机制比自己在脚本里写“先 sleep 30 秒再干活”优雅得多。延迟启动是在其他自动启动服务基本完成后才触发适合对启动时序有依赖但不是强依赖的服务。参数说明如果脚本依赖的是某个具体服务更可靠的方案是设置服务依赖。nssm set 本身不直接管这个要在服务属性里配置依赖或者在注册表 DependOnService 里手动加目标服务名。注意依赖服务名填的是服务内部名不是显示名。5. 日志轮转与多实例部署把 NSSM 用到生产环境5.1 日志轮转参数表与推荐组合日志轮转是 NSSM 生产化绕不开的配置。我按实际经验整理过一组参数这里直接列出来参数示例值作用AppRotateFiles1开启日志轮转AppRotateBytes10485760单个日志文件超过 10MB 时切换AppRotateOnline1轮转时不中断 stdout/stderr 重定向AppRotateSeconds86400可选超过 24 小时也切换一份推荐组合是 AppRotateFiles1、AppRotateBytes10485760、AppRotateOnline1配合按天轮转的 AppRotateSeconds86400。这个组合的好处是大文件有上限单日日志也能独立成文件排查问题的时候直接按日期定位不用在几十 MB 的大文件里翻来翻去。轮转产生的日志命名规则是“原文件名时间戳”比如 my-task.log 在切换后会变成 my-task.2025-06-01_14-30-22.log新写入内容继续落到 my-task.log。这个命名规则在写日志采集脚本时可以直接利用按前缀加时间戳去匹配。有一点必须强调NSSM 不做日志清理。轮转只是切文件切下来的旧文件会一直留在磁盘上。我在一个项目里吃过亏以为开了轮转就万事大吉结果文件每天切一个300 天下来照样吃了几 GB 空间。我的习惯是加一个计划任务每天凌晨清理超过 N 天的日志文件目录路径固定清理脚本可以写成“按文件名时间戳过滤 删除”。5.2 多实例部署同一套脚本跑多个服务很多时候同一套脚本代码要在同一台机器上跑多个实例比如按端口拆分、按业务线拆分。NSSM 不需要为这种场景做任何特殊处理装多个服务名就行:: 两个服务实例同一脚本不同端口参数 nssm install api-8001 C:\Python39\python.exe D:\deploy\api\run.py --port 8001 nssm install api-8002 C:\Python39\python.exe D:\deploy\api\run.py --port 8002逻辑说明每个服务名对应独立的 NSSM 配置包括 Path、Arguments、日志路径互不干扰。两个实例的日志文件要分开否则多个进程写同一个文件会有写入竞争日志内容互相穿插排查问题时分不清哪行是哪来的。可以在配置时把 AppStdout 指向带端口号的路径nssm set api-8001 AppStdout D:\logs\api-8001.log nssm set api-8002 AppStdout D:\logs\api-8002.log参数说明服务名前缀加端口号是我比较推荐的做法比 my-task-1、my-task-2 这种纯序号清晰得多看到服务名就大概知道是哪个业务、哪个端口。服务名长度别超过 32 个字符命令行的长度限制和注册表服务的命名规范都要注意。另外提醒一句多实例时测试自动重启要格外小心杀进程前务必看清 PID 属于哪个服务tasklist /fi services eq 服务名 能直接套用比从进程名猜靠谱得多。5.3 环境变量注入脚本配置外置把配置写死在脚本里是服务化大忌。NSSM 支持给服务注入额外的环境变量这样同一份脚本代码在不同实例里跑读到的配置不同不用改一行代码nssm set api-8001 AppEnvironmentExtra INSTANCE_ID8001 nssm set api-8001 AppEnvironmentExtra APP_ENVproduction逻辑说明AppEnvironmentExtra 里每个 KEYVALUE 都会在服务启动时追加到系统环境变量里。脚本侧直接用 os.environ 读取import os instance_id os.environ.get(INSTANCE_ID, 0) env os.environ.get(APP_ENV, dev) print(finstance_id{instance_id}, env{env}, flushTrue)参数说明环境变量注入比改配置文件好用的地方在于服务重装或迁移时所有配置跟着服务一起走不用去记“改哪行配置”。多实例场景下尤其省事脚本只读环境变量不同实例注册时 set 不同的 INSTANCE_ID 就行代码和配置彻底解耦。命令行方式设置多个环境变量时注意 nssm set 的逻辑是覆盖式还是追加式版本之间有些差异最稳妥的方式是安装时一次性在 GUI 的 Environment 选项卡里把多行变量都填完避免后来追加时把之前的覆盖掉。6. 最后一点经验nssm edit 与注册表导出让服务管理有后悔药6.1 nssm edit改参数不用删服务重装NSSM 最让我省心的是配置可改。以前用 sc create 时代改一个参数得先删服务再注册动作大还容易出岔子NSSM 里一条命令直接打开图形界面nssm edit my-task改完点确定服务会自动应用新配置。改 Path、Arguments、日志路径、环境变量这些都在 GUI 里完成。要注意的是改完配置后服务不会自动重启需要手动 nssm restart my-task 让新配置生效。这个操作模式用顺了之后部署脚本的维护成本能低不少。6.2 参数导出与批量部署注册表克隆思路NSSM 的服务配置最终存在注册表里位置是 HKLM\SYSTEM\CurrentControlSet\Services\服务名\Parameters。这个特性意味着可以导出、迁移、批量部署:: 备份单个服务的完整配置 reg export HKLM\SYSTEM\CurrentControlSet\Services\my-task D:\backup\my-task.reg /y逻辑说明导出的 reg 文件里包含 NSSM 的 Parameters 键以及 Windows 服务本身的不少配置项。拿到另一台机器上导入后服务就能带着原参数克隆出来。批量部署的场景一般是测试机上把服务调好导出 reg然后拿 reg import 到一批新服务器上。参数说明导入前检查四样东西否则服务会启动失败——python 或 exe 路径在目标机器上存在、脚本路径存在、日志文件目录存在、服务名没有被占用。路径不一致时打开 reg 文件全文替换路径文本再导入这是最快的做法。另外提醒不要跨机器直接导入到服务名不同的环境注册表项里的服务名是主键改名要谨慎处理。从那次批量部署翻车之后我每次导入完都强制走一遍 nssm start、tasklist、杀进程验证这三步才安心。希望这套经验能帮你把 NSSM 用得顺手少走弯路。本文还有配套的精品资源点击获取