简介:NSSM 2.10 是一款在 Windows 平台下将任意可执行文件便捷注册为系统服务的开源工具,面向需要让程序开机自启、后台持续运行的开发与运维人员。该工具通过图形界面即可指定服务名称、启动参数、依赖项及运行账户,并支持日志记录与异常处理,尤其适合数据同步、监控脚本、自动化任务等常驻型应用。压缩包共 24 个文件,内含 win32 与 win64 两个可直接运行的 nssm.exe、7 个头文件与 6 个 C++ 源码,以及 README、ChangeLog、工程文件和图标资源,便于学习服务封装原理并对照源码二次改造;整个 zip 仅 158KB,轻量便携。资源目前已有 223 人学习浏览,既能作为 NSSM 的免安装发行包直接投入使用,也是一份完整的源码级参考资料,可帮助读者理解 Windows 服务与应用程序交互的核心机制。
1. NSSM:把任意 exe 变成 Windows 服务的免费方案
先抛一个反直觉的结论:Windows 自带的sc create也能把 exe 注册成服务,但你得到的是一个“孤儿进程”——程序崩溃后系统不会自动拉起它,标准输出和错误输出也没有落盘入口,排查问题全靠猜。NSSM(Non-Sucking Service Manager)2.10 这个工具包解决的正是这件事:它是一个服务包装器,把你的 exe 作为子进程托管起来,服务挂了自动重启,输出日志按天切割,退出码还能决定要不要拉起第三次。这个版本包里同时带了 win32 和 win64 两个目录的 nssm.exe,拿到手解压就能用,适合那些需要把后台同步工具、定时任务脚本、内网服务端程序变成“开机自启、无人值守”状态的场景。
2. NSSM 是什么与为什么选它:从服务机制到 2.10 包结构
2.1 Windows 服务的运行机制决定了你不能直接跑 exe
Windows 服务与普通进程最大的区别在于会话隔离:服务由 SCM(Service Control Manager)启动,运行在 Session 0 里,用户注销后它依然存在。普通 exe 即使手动丢进启动文件夹,登录界面一出现就可能被杀掉,更别说系统还没登录时就要拉起的场景。要绕过这个限制,传统做法是自己写一个 .NET Windows 服务项目,再用 InstallUtil 注册——但为了跑一个 Python 脚本去写 C# 服务工程,代价明显过高。
NSSM 的定位就是省掉这一步。它的核心工作方式是:你告诉 NSSM“帮我跑哪个 exe、参数是什么”,NSSM 自己注册为系统服务,然后在服务启动时把目标 exe 拉起来作为子进程。注意一个关键点:NSSM 不会把你的 exe 合并成服务,而是把“管理 exe 生命周期”这件事做成了服务。这个设计带来两个直接好处:一是目标程序完全不需要改造,命令行能跑的程序都能被托管;二是 NSSM 能够感知子进程退出,并在配置的策略下决定是退出服务还是重新拉起子进程。
2.2 对比 sc、winsw、任务计划程序:为什么监控和日志是最短木板
很多人问过“既然 sc create 能注册,为什么还要 NSSM”。我用一个表格把几个常见方案的差异列一下:
| 方案 | 崩溃自动重启 | 日志重定向 | 服务依赖配置 | 图形界面 |
|---|---|---|---|---|
| sc create | 不自动,需要额外 watchdog | 不支持 | 有限支持 | 无 |
| 任务计划程序 | 重启需要额外触发器配置 | 不支持原生 | 无 | 有 |
| winsw | 支持较少参数 | 需自行配 log 节点 | 支持 | 无 |
| NSSM 2.10 | 支持,可配置延迟和重启次数 | AppStdout/AppStderr 重定向 | 支持 DependOnService | 有 |
任务计划程序在“用户登录后触发”的场景下够用,但如果是数据库服务、消息队列这类需要开机就在后台跑的进程,任务计划程序会显得不稳定——它的触发条件基于事件而不是 SCM 的服务控制协议,服务停止时系统不会主动通知它。winsw 是另一个开源选择,配置走 XML,适合喜欢把一切写进文件的场景,但它的进程监控逻辑相对简单,子进程被杀掉后恢复行为没有 NSSM 那么精细。NSSM 对“服务停了要不要拉起、ExitAction 怎么处理、重启延迟多少秒”都有独立参数,这一点对长期运行的服务更加重要。
2.3 2.10 压缩包内部结构:先确认你要的是 win32 还是 win64
拿到 nssm-2.10----Windows-service.zip 之后,解压出来是一套源码加可执行文件的组合。常见文件清单如下:
| 文件/目录 | 作用 |
|---|---|
| win32/nssm.exe | 32 位版本,适合老旧程序或 32 位 Windows |
| win64/nssm.exe | 64 位版本,Windows 10 x64 首选 |
| src/、.cpp、.h | 完整源码,需要自己编译时用 |
| README.txt | 版本说明和基础用法 |
| ChangeLog.txt | 版本变更记录 |
选择依据很简单:Windows 10 基本都是 x64,直接用 win64\nssm.exe 即可。需要注意一个小坑:32 位 exe 可以被 64 位系统运行,但 NSSM 的位数最好与你托管的程序一致。如果你托管的是 32 位旧程序,NSSM 用 64 位版本通常也没问题,因为 NSSM 只是启动子进程。我一般直接测试为主——先跑 64 位,遇到加载异常再换 32 位。
提示:NSSM 不需要安装,它在注册服务时会把自身路径写入服务配置。所以不要解压后乱动位置,建议固定放在
C:\tools\nssm\这类路径下再注册服务。
3. 用 GUI 注册服务:nssm install 的完整字段解读
3.1 以管理员身份启动 GUI 注册服务
NSSM 的图形界面在 2.10 版本里做得相当完整,适合第一次接触服务托管的新手。打开管理员命令行(win10 下按 Win+X 选择 Windows PowerShell(管理员)),切到解压目录,执行:
cd C:\tools\nssm\win64 nssm install MyService执行后弹出图形窗口。这里有个细节:如果当前命令行不是管理员权限,NSSM 会直接报错或者在注册服务时失败。在 Windows 10 上,UAC 弹窗确认之后,nssm install第二个参数就是服务名,这个服务名将来会出现在服务管理控制台(services.msc)里,建议用英文,不要带空格。
界面核心字段是 Application 标签页下的几个输入框:
| 字段 | 说明 |
|---|---|
| Application path | 要托管的可执行文件绝对路径 |
| Startup directory | 程序的工作目录,必须显式填写,否则部分程序无法找到相对路径的配置文件 |
| Arguments | 启动参数,空格分隔,带路径的参数要用引号 |
| Service name | 服务名称,创建时不可修改 |
很多人第一次在这界面翻车的原因是填了 Application path 但没填 Startup directory。NSSM 不会自动继承命令行当前目录,如果你的 exe 依赖相对路径读配置文件,就必须把 Startup directory 指到 exe 所在目录。填写完成后点击“Install service”按钮,提示服务创建成功。
3.2 在 GUI 中配置进程退出后的行为
GUI 里的 Exit actions 和 Process 标签页值得单独说。Exit actions 就是定义“当被托管进程退出时,NSSM 怎么办”的规则,默认是 Exit 退出服务。但对于需要保持“服务永远在线”的进程,需要改成 Restart,然后在 Process 标签页设置 Restart delay(重启延迟,单位毫秒)和 Restart throttle(重启节流时间)。这两个参数的效果是:进程崩溃后 NSSM 等待设定的延迟再拉起,避免进程还没释放完端口就被重新启动导致绑定失败。
Process 标签页里还有一个 Pause/Continue 选项,可以配置“当 NSSM 收到暂停服务指令时,是否向子进程发送 Ctrl-Break 事件”。如果被托管程序是 Python 脚本或 Node 服务,建议勾选“Send Ctrl-Break to the application”,这样在命令行执行nssm stop MyService时,子进程能收到信号做优雅退出,而不是被 nssm 强杀。
3.3 GUI 模式看不到日志配置?去 I/O 标签页
日志重定向界面的位置有点隐蔽,需要在 I/O 标签页里配置。GUI 模式下,你可以勾选“Redirect stdout to log file”和“Redirect stderr to log file”,并分别指定日志文件路径。路径上注意不要用相对路径,NSSM 在 GUI 模式下创建的日志路径是相对于当前进程启动目录的,如果改过工作目录,日志可能会落到意想不到的位置。
GUI 模式的整体节奏是适合调试阶段:先注册服务,然后从服务控制台手动启动,观察日志输出。但实际部署时我更推荐命令行方式,因为配置可以写成脚本,重装系统不至于再手动点一遍图形界面。
4. 命令行注册与参数调优:nssm set/edit 的完整参考
4.1 用 nssm install 回车参数完成静默注册
命令行方式比 GUI 更适合批量部署。注册命令如下:
nssm install MyService "C:\app\my-server.exe" "--port=8080 --config=server.ini" nssm set MyService AppDirectory "C:\app" nssm set MyService DisplayName "My Background Server" nssm set MyService Description "Handles scheduled tasks and TCP calls" nssm set MyService Start SERVICE_AUTO_START关键点:install命令接收的参数按顺序是服务名、exe 路径、启动参数。如果你想在服务名不变的情况下修改 exe 路径,用nssm edit MyService打开 GUI 改 Application path,但生产环境我更推荐用nssm set MyService Application "C:\app\server.exe"直接改,这样变更记录留在脚本里。AppDirectory是多数人容易忽略的参数——即使命令行里配置了 exe 路径,也必须设置工作目录,否则运行时读不到相对路径的配置文件。
参数Start SERVICE_AUTO_START的含义是把启动类型设为自动,等价于服务控制台里的“自动(延迟启动)”下方的“自动”。如果要延迟启动,可以换成SERVICE_AUTO_START配合nssm set MyService DelayedAutostart 1。
4.2 核心参数表:重启策略、退出码与日志旋转
命令行方式能够设置 GUI 里不直接暴露的一些参数,下面这张表是我实际用过并验证过的:
| 参数 | 命令示例 | 作用 |
|---|---|---|
| AppRestartDelay | nssm set MyService AppRestartDelay 5000 | 进程退出后延迟多少毫秒再重启 |
| AppExit | nssm set MyService AppExit 3 Restart | 当退出码为 3 时执行 Restart 动作 |
| AppStdout | nssm set MyService AppStdout "C:\logs\out.log" | 重定向标准输出 |
| AppStderr | nssm set MyService AppStderr "C:\logs\err.log" | 重定向错误输出 |
| AppRotateFiles | nssm set MyService AppRotateFiles 1 | 启用日志按大小/时间切割 |
| AppRotateBytes | nssm set MyService AppRotateBytes 10485760 | 日志超过 10MB 自动旋转 |
| DependOnService | nssm set MyService DependOnService mysql | 当前服务依赖 mysql 服务 |
AppExit配置逻辑:当进程退出码等于指定值时,NSSM 执行 Restart/Exit/Throttle 动作。默认情况下,任何退出码都会导致 NSSM 服务退出,这恰恰是很多人配置 NSSM 后“服务启动就停止”的原因——你的程序可能因为缺少环境变量退出了,NSSM 则“尽职尽责”地把服务状态标记为停止。这时候要看的是 AppStderr 日志,而不是反复重启。
AppRotateBytes的细节:NSSM 检查日志文件大小是在子进程每次写 stdout/stderr 之后,并非严格实时。如果把旋转阈值设得很小(比如 1KB),会频繁触发文件关闭和重命名,影响性能。我一般在 5MB~50MB 之间选值,配合AppRotateOnline 1让旋转过程中不中断输出。
4.3 服务操作命令:start/stop/status 的几条实际用法
注册完之后,日常操作命令如下:
nssm start MyService nssm status MyService nssm stop MyService nssm restart MyService nssm remove MyService confirmnssm status输出一个数字状态码:1 表示 SERVICE_STOPPED,4 表示 SERVICE_RUNNING。脚本里做健康检查时可以用这个命令,但要注意,运行nssm start时如果服务已经在运行,它会返回一个非零退出码,但服务本身不受影响。remove后面加confirm是为了跳过交互式确认框,在批量卸载场景下是必要的。如果你只删服务但保留日志文件,直接 remove 即可,NSSM 不会动日志目录。
5. 日志重定向与应用托管:Java、Python、Node 进程的落地配置
5.1 把 stdout/stderr 同时接入滚动日志
无登录用户运行程序的另一大痛点是“程序输出去了哪里”。手动启动时你能看到控制台打印,但作为服务运行时没有终端。NSSM 的做法是把子进程的 stdout 与 stderr 重定向到文件。配置示例:
nssm set MyService AppStdout "C:\logs\app-out.log" nssm set MyService AppStderr "C:\logs\app-err.log" nssm set MyService AppRotateFiles 1 nssm set MyService AppRotateBytes 20971520 nssm set MyService AppRotateOnline 1这里AppStdout与AppStderr是两个独立文件。如果只配了 stdout,程序异常堆栈打到 stderr 上就不会被记录,排查问题时容易产生“没报错但程序死了”的错觉。AppRotateOnline的作用是 NSSM 在旋转日志文件时,继续把新内容写入新文件,而不是阻塞子进程。常见做法是设置默认的AppRotateBytes与AppRotateInterval(单位秒)双条件,谁先触发谁旋转。
注意:日志目录必须提前创建,NSSM 2.10 不会自动帮你在盘上建目录。如果
C:\logs不存在,服务启动后日志文件无法创建,但 NSSM 不会报错,直接在服务日志里记录一条异常信息。
5.2 Java 应用托管:路径带空格的完整写法
Java 后端服务往往带有lib目录,启动脚本是一长串java -cp参数。用 NSSM 托管 Java 服务时,不要在 Application path 里填java.exe的路径就完事,因为 classpath 中的相对路径会失效。正确配置是 Application 填 java.exe 绝对路径,参数里用-jar或-cp指向绝对路径:
nssm install MyJavaService "C:\Program Files\Java\jdk-17\bin\java.exe" "-Xms512m -Xmx1024m -jar C:\app\myapp.jar" nssm set MyJavaService AppDirectory "C:\app" nssm set MyJavaService AppStdout "C:\logs\java-out.log" nssm set MyJavaService AppStderr "C:\logs\java-err.log"注意Arguments参数带空格时需要整体用引号包住,并且传递给 NSSM 的是一个字符串,所以命令行长度的转义规则要小心。Windows 命令行上"会被解析为参数边界,NSSM 内部会拼接成完整命令行。最稳的写法是把启动参数提前在本地试跑一遍,确认在没有 NSSM 介入时,命令行能直接拉起应用,再加到 NSSM 配置里。
如果 Java 进程不是通过java -jar启动,而是.bat,不要直接把 NSSM Application 指向.bat。.bat由 cmd.exe 解释执行,而 NSSM 不会帮你启动 cmd。常见做法是改成cmd.exe /c "C:\app\start.bat",但这样退出码处理会变得复杂。更干净的方式是把.bat里的 java 命令提出来,直接交给 NSSM。
5.3 Python 与 Node:虚拟环境和 npm 脚本怎么交给 NSSM
Python 服务最常见的问题是激活虚拟环境:手动跑时先activate,但服务会话里没有这个动作。NSSM 配置里不要写python.exe或python3,要写虚拟环境的绝对路径:
nssm set MyPythonService Application "C:\app\venv\Scripts\python.exe" nssm set MyPythonService AppParameters "C:\app\server.py --worker=2" nssm set MyPythonService AppDirectory "C:\app" nssm set MyPythonService AppExit Default Exit nssm set MyPythonService AppExit 1 Restart虚拟环境中的 python.exe 本质是个重定向入口,它依赖同目录下的 activate 脚本和pyvenv.cfg,所以AppDirectory建议设置为项目根目录(不是 venv 目录),这样server.py里读相对路径的模板文件不会出错。AppExit Default Exit表示如果退出码不是 1,就让服务退出;退出码为 1(通常表示业务异常)则执行 Restart。这种配置比“无论什么退出码都重启”更合理——如果 Python 脚本因为语法错误或配置缺失退出,重启一万次都是白费。
Node 服务同理,Application 指向node.exe(而不是 npm.cmd),参数写你的入口 js 文件。npm 脚本启动的复杂场景,我建议单独写一个不带守护逻辑的入口文件,NSSM 只负责拉起 node,不负责帮你 npm run。
5.4 服务账户与权限:LocalSystem 与指定账户的区别
NSSM 默认服务账户是 LocalSystem,这个账户在本地系统上权限极大,能访问绝大多数目录,但不能直接访问网络共享(SMB 访问需要凭据)。如果你的应用需要访问内网其他机器的数据库,LocalSystem 可能因为凭据问题无法建立连接。此时需要给服务指定账户:
nssm set MyService ObjectName ".\myuser" "mypassword".\myuser的写法表示本地用户,如果是域账户,写domain\user。设置之后,NSSM 会更新服务配置,注意服务账户密码过期后服务会启动失败。Windows 10 上如果发现服务在重启后自动停止,检查事件查看器里的错误码,如果是 Logon failure,直接去服务控制台重新输入密码。我的习惯是给服务单独建一个“永远不会过期的本地账户”,比如svc_myapp,这样两年内不会再遇到凭据问题。
6. 排查避坑:NSSM 服务启动失败与退出码的五个现场
6.1 服务启动后立即停止,事件日志里只有“服务特定错误”
现象:注册完服务,点“启动”,一两秒后状态变回“已停止”,事件查看器里只有服务控制管理器记录的服务特定错误,没有具体退出原因。
原因:绝大多数时候是被托管程序因环境变量、缺失 DLL 或端口被占用直接退出。NSSM 默认 ExitAction 是退出服务,于是 SCM 看到服务进程退出就标记为停止。这不是 NSSM 的问题,而是目标程序没有正常起来。
解决:先看 NSSM 配置的 AppStderr 日志文件,里面是程序自己的报错;如果日志没生成,尝试以 Interactive 方式先在普通命令行启动一遍同样的命令,观察输出。我一般会在注册服务前先手动执行一遍目标命令,确认它能稳定跑起来再交给 NSSM。
6.2 日志文件有内容,但服务还是不断重启
现象:AppStderr 日志里反复出现同一段报错(比如数据库连接失败),NSSM 按照 AppRestartDelay 不停重启,日志持续增长,服务永远处于 running 和 stopping 之间摇摆。
原因:AppExit Default Restart配置在生效,但程序犯了非致命错误,始终没有以非零退出码退出,而是被 NSSM 反复拉起。
解决:给 AppExit 配一个更明确的退出码策略,比如AppExit 3 Restart,然后让程序在遇到无法恢复的数据库连接错误时exit(3)。这样不会无限重启。另一种做法是设置AppRestartDelay 30000加上AppThrottle 300000(节流时间),让重启频率降低,给外部依赖恢复留出时间。
6.3 GUI 里改了配置点了确定,但服务行为没变化
现象:用nssm edit改完重启延迟,保存成功,nssm get MyService AppRestartDelay也返回了新值,但服务崩溃后还是按旧延迟重启。
原因:NSSM 读取配置是启动服务时读取一次,而不是每次重启子进程都重新读取。改配置后没有重启 NSSM 服务本身,导致内存中的配置还是旧的。
解决:改完任何参数后都执行nssm restart MyService而不是nssm restart子进程。NSSM 服务的启动和停止是针对整个服务,改为新配置必须让服务进入 stopped 状态后再启动。
6.4 删除服务后,注册表残留导致重装失败
现象:nssm remove MyService之后重新注册同名服务,报“指定的服务已存在”,但 services.msc 里看不到 MyService。
原因:NSSM 删除服务时不会删除注册表项HKLM\SYSTEM\CurrentControlSet\Services\MyService里的部分键值,SCM 因为没有完全清理导致重名冲突。
解决:用sc delete MyService强制删除服务条目,然后检查注册表是否有残留,手动删除整个 MyService 键,再重新注册。注意这个操作需要管理员权限,而且要先确保服务处于停止状态,否则删除过程会报错。
6.5 Windows 10 上 NSSM 运行但服务不随开机启动
现象:注册为自动启动,但系统重启后服务状态是 stopped,不在自动启动的服务列表里。
原因:Windows 10 快速启动机制导致部分服务在启动阶段被跳过;或者服务的 Start 参数是SERVICE_DEMAND_START,没有真正改为自动。
解决:执行nssm set MyService Start SERVICE_AUTO_START,然后去看服务控制台“启动类型”列是否为“自动”。如果已经是自动但仍不启动,检查事件日志中是否有超时错误——服务启动时间超过 30 秒时,SCM 会直接终止它。这种情况要把启动脚本里的耗时逻辑移到程序内部异步执行,或者改成延迟启动。
7. 进阶技巧:多实例隔离、服务账户最小权限与卸载残留清理
多实例部署是 NSSM 比较实用的进阶场景。同一个 exe,需要同时跑两个实例监听不同端口时,注册两个服务名(比如myapp-8001与myapp-8002),每个服务单独设置 AppDirectory 和参数即可。但要注意日志文件必须分开,若两个服务共用同一个 stdout 日志文件,NSSM 的文件锁机制会导致第二个服务启动时无法写入,直接在 stderr 里报错。我一般会在服务名里带端口号,同时日志路径也带服务名后缀。
服务账户的最小权限配置也值得动手做一遍。默认 LocalSystem 虽然方便,但安全风险不小。如果程序只需要读写某个目录和访问本机 TCP 端口,可以用nssm set MyService ObjectName ".\svc_user" "password"指定一个普通用户,然后给这个用户授予应用目录的读写权限。需要注意的是 AppDirectory 目录如果位于 Program Files 下,普通用户可能没有写入权限,导致程序启动时无法写临时文件。我的习惯是先创建C:\app\这样的自定义目录,避免与 Program Files 的 ACL 冲突。
停止与卸载是另一个容易被忽略的地方。nssm stop MyService是在向 NSSM 发停止指令,NSSM 默认会通知子进程退出。如果子进程无法响应通知,NSSM 会在超时后强制终止。要在卸载时彻底清理,先保证子进程已经被杀掉,再执行nssm remove MyService confirm,最后手动删除日志目录。我见过有同事卸载后忘删日志,一年后磁盘满了才发现是 NSSM 的旧日志在持续增长——虽然服务删了,但日志文件还在。
在多实例与权限这条链路上,我踩过一次比较深的坑:两个实例用了同一个配置文件的相对目录,结果第二个实例启动时把第一个实例的配置覆盖了。从那以后,我每次给服务分配参数时都强制把 AppDirectory 变量写进服务描述里,并先在本地用同样的路径结构跑一遍命令再注册。如果你也准备部署多实例,建议把每个服务的完整配置(exe 路径、AppDirectory、AppStdout、AppStderr、AppExit、AppRestartDelay)输出到一份文本里,存成服务台账。这个习惯能让你在半年后重装系统时,用一条循环命令把服务全部重建;也让你在排查问题时不用打开六七个 GUI 窗口逐个看配置。NSSM 的真正价值不在于图形界面多好看,而是让你能用几行脚本把一个服务的生命周期——启动、监控、日志、重启、删除——完整地管理起来,希望这篇实战拆解能帮你在自己的 Windows 服务器上少走几步弯路。
本文还有配套的精品资源,点击获取