Skip to content

Run a Node.js App as a Windows Service

decerto edited this page Aug 11, 2026 · 1 revision

Run a Node.js app as a Windows Service

A Node app started from a terminal dies when you sign out. A scheduled task is close but has no supervision and no clean stop. The right answer on Windows is a service: it starts at boot, runs with nobody signed in, restarts on failure, and can be started and stopped by anything that speaks to the Service Control Manager.

sc.exe create cannot do it on its own, because a Windows Service has to respond to service control messages and node.exe does not. You need a wrapper.


Which wrapper

Tool Use when
WinSW You want the definition in a file you can commit, copy and template. This is what WinPanel uses.
NSSM You want a dialog box and are setting up one or two by hand.
node-windows You want to register the service from JavaScript. Fine, but it wraps the same idea.
sc.exe Managing a service you already registered — not creating one for node.exe.

WinSW, start to finish

1. Download WinSW.NET4.exe (or the .NET Core build) and put it next to your app, renamed to match the service:

C:\Sites\my-app\
  my-app.exe        <- renamed WinSW
  my-app.xml        <- the definition below
  server.js

2. Write my-app.xml:

<service>
  <id>my-app</id>
  <name>My App</name>
  <description>Node.js application for example.com</description>

  <executable>C:\Program Files\nodejs\node.exe</executable>
  <arguments>server.js</arguments>
  <workingdirectory>C:\Sites\my-app</workingdirectory>

  <env name="PORT" value="3001" />
  <env name="NODE_ENV" value="production" />

  <onfailure action="restart" delay="10 sec" />
  <onfailure action="restart" delay="30 sec" />
  <onfailure action="restart" delay="60 sec" />

  <logpath>C:\Sites\my-app\logs</logpath>
  <log mode="roll-by-size">
    <sizeThreshold>10240</sizeThreshold>
    <keepFiles>8</keepFiles>
  </log>

  <startmode>Automatic</startmode>
  <delayedAutoStart>true</delayedAutoStart>
</service>

3. Install and start:

my-app.exe install
my-app.exe start

my-app.exe status, stop, restart and uninstall do what they say. Windows now owns the app.


The five things that catch people

Absolute paths, always. A service has a different working directory and a different PATH from your session. Anything relying on node being "on the PATH" works when you test it and fails as a service.

The environment is not inherited. Variables you set in your own profile do not exist for the service account. Declare every one the app needs in <env>.

Log the output somewhere. Without <logpath>, a crash on startup is invisible — the service reads as "stopped" with no explanation. With it, my-app.err.log holds the stack trace. Note that Node prints its version banner after a stack trace, so the last line of that file is usually Node.js v24.x and the useful line is above it.

Bind to 127.0.0.1, not 0.0.0.0. Otherwise the app is reachable on its raw port from the internet, bypassing your proxy, your certificate and your logging.

The orphan. If the wrapper is killed without a clean stop — a sleep/wake cycle is the usual cause — the node.exe underneath survives. Windows reports the service as stopped while the app is still running and still holding its port, so every restart fails to bind and the service flaps forever. On a website this is worse than an outage, because the old process keeps answering and nothing looks wrong while every deploy lands on code that is no longer running.

To clear it by hand:

Get-NetTCPConnection -LocalPort 3001 -State Listen |
  Select-Object -ExpandProperty OwningProcess |
  ForEach-Object { Get-Process -Id $_ }

then end that process and start the service. WinPanel checks for exactly this every minute, and before every start, restart and deploy, and clears it automatically — but only when the process holds one of the service's own ports and is one of its own executables.


Then put a proxy in front

The service gives you an app on 127.0.0.1:3001. It still has no domain and no certificate. Three lines of Caddy give it both:

example.com, www.example.com {
    reverse_proxy 127.0.0.1:3001
}

See SSL certificates on Windows Server without IIS and Node.js hosting on Windows Server.

Clone this wiki locally