How to Deploy a Node.js Application on Windows Server 2022

Node.js is widely used for building APIs, real-time applications, and server-side rendered web apps. Deploying Node.js on Windows Server 2022 requires a few Windows-specific considerations: process management, IIS as a reverse proxy, environment variable handling, and service installation. This guide covers each step from a fresh Windows Server 2022 installation through a production-ready deployment.

Installing Node.js on Windows Server 2022

Download the Windows installer from nodejs.org. Use the LTS (Long Term Support) release for production workloads. The installer adds node.exe and npm.cmd to PATH automatically for all users when you select “Add to PATH” during setup.

# Verify installation (run in PowerShell or CMD after install)
node --version
npm --version

# Example output:
# v20.14.0
# 10.7.0

# If node is not in PATH, add it manually:
$env:PATH += ";C:Program Filesnodejs"
# Or permanently via System Properties > Advanced > Environment Variables

For automated deployments or multiple Node.js versions on the same server, use nvm-windows (Node Version Manager for Windows). Download the installer from github.com/coreybutler/nvm-windows:

# After installing nvm-windows:
nvm install 20.14.0
nvm use 20.14.0
nvm list

# Switch between versions
nvm use 18.20.0

npm Basics for Windows Server

npm (Node Package Manager) installs project dependencies defined in package.json. On Windows Server, the default npm cache location is %APPDATA%npm-cache, and global packages install to %APPDATA%npm.

# Install production dependencies only (no devDependencies)
npm install --omit=dev

# Install a package globally
npm install -g pm2

# Clear npm cache if you encounter checksum errors
npm cache clean --force

# Audit for known vulnerabilities
npm audit

# Check globally installed packages
npm list -g --depth=0

When running npm scripts on Windows Server, be aware that shell scripts (.sh files) referenced in package.json scripts will not work. Use .cmd or .ps1 wrappers, or use cross-platform tools like cross-env for environment variable setting in scripts.

PM2 for Process Management

PM2 is the standard process manager for Node.js in production. It handles automatic restarts on crash, clustering across CPU cores, log management, and startup integration. Install it globally:

npm install -g pm2

# Start an application
pm2 start app.js --name "myapp"

# Start with cluster mode (use all CPU cores)
pm2 start app.js --name "myapp" -i max

# Start a specific number of instances
pm2 start app.js --name "myapp" -i 4

# Start with environment variables
pm2 start app.js --name "myapp" --env production

# View all running processes
pm2 list

# View detailed info for one process
pm2 show myapp

# View real-time logs
pm2 logs myapp
pm2 logs myapp --lines 100

# Stop, restart, delete
pm2 stop myapp
pm2 restart myapp
pm2 delete myapp
pm2 reload myapp     # Zero-downtime reload for cluster mode

PM2 uses an ecosystem configuration file for managing multiple applications or complex start options. Create ecosystem.config.js in your project root:

module.exports = {
  apps: [
    {
      name: 'myapp',
      script: './src/server.js',
      instances: 'max',
      exec_mode: 'cluster',
      watch: false,
      max_memory_restart: '500M',
      env: {
        NODE_ENV: 'development',
        PORT: 3000
      },
      env_production: {
        NODE_ENV: 'production',
        PORT: 3000
      },
      error_file: 'C:/logs/myapp-error.log',
      out_file:   'C:/logs/myapp-out.log',
      log_date_format: 'YYYY-MM-DD HH:mm:ss Z'
    }
  ]
};

// Start with production env:
// pm2 start ecosystem.config.js --env production

PM2 Startup on Windows (pm2-windows-startup or NSSM)

On Linux, pm2 startup registers a systemd service. On Windows, the equivalent is pm2-windows-startup or wrapping PM2 with NSSM.

Option 1: pm2-windows-startup

npm install -g pm2-windows-startup

# Register PM2 to start with Windows
pm2-startup install

# Save current PM2 process list so it is restored on boot
pm2 save

Option 2: NSSM (Non-Sucking Service Manager) — Download NSSM from nssm.cc and use it to wrap PM2 as a proper Windows service:

# Open elevated Command Prompt
# Navigate to nssm directory
cd C:toolsnssmwin64

# Install PM2 as a service
nssm install PM2Service

# NSSM opens a GUI. Set:
#   Path:         C:UsersAdministratorAppDataRoamingnpmpm2.cmd
#   Startup dir:  C:appsmyapp
#   Arguments:    start ecosystem.config.js --env production

# Or do it non-interactively:
nssm install PM2Service "C:UsersAdministratorAppDataRoamingnpmpm2.cmd" "start ecosystem.config.js --env production"
nssm set PM2Service AppDirectory "C:appsmyapp"
nssm set PM2Service Start SERVICE_AUTO_START
nssm set PM2Service ObjectName LocalSystem

# Start the service
nssm start PM2Service

# Check status
nssm status PM2Service

Configuring IIS as a Reverse Proxy to Node.js

IIS can sit in front of Node.js to handle SSL termination, static files, and port 80/443 binding. This requires Application Request Routing (ARR) and URL Rewrite modules, both free from Microsoft.

Install them via Web Platform Installer or download directly:

  • ARR: iis.net/downloads/microsoft/application-request-routing

  • URL Rewrite: iis.net/downloads/microsoft/url-rewrite

After installing both, enable proxy in ARR:

# In IIS Manager:
# Click the server node > Application Request Routing Cache
# Click "Server Proxy Settings" in the Actions pane
# Check "Enable proxy"
# Click Apply

Then configure URL Rewrite on your IIS site to forward requests to Node.js. Open the site in IIS Manager, click URL Rewrite, then Add Rule > Reverse Proxy. Or edit web.config directly:



  
    
      
        
          
          
            
          
          
        
      
    

    
    
      
    
  

Place this web.config in the IIS site’s root directory (e.g., C:inetpubwwwroot).

Environment Variables

Never hard-code secrets in source code. Use Windows system environment variables or a .env file read by the dotenv package.

# Set a persistent system-level environment variable (requires admin)
[System.Environment]::SetEnvironmentVariable("DATABASE_URL", "mssql://user:pass@server/db", "Machine")

# Or via CMD (machine-wide):
setx DATABASE_URL "mssql://user:pass@server/db" /M

# In Node.js with dotenv:
# npm install dotenv
# At top of app.js:
# require('dotenv').config();
# Then use: process.env.DATABASE_URL

# Pass env vars directly to PM2:
pm2 start app.js --name myapp --env production
# PM2 reads env_production from ecosystem.config.js

Deploying from Git

A typical deployment workflow on Windows Server using Git and PM2:

# Initial clone
cd C:apps
git clone https://github.com/yourorg/myapp.git
cd myapp
npm install --omit=dev
pm2 start ecosystem.config.js --env production
pm2 save

# Update deployment (zero-downtime with cluster mode)
cd C:appsmyapp
git pull origin main
npm install --omit=dev
pm2 reload myapp

# If you need to run database migrations:
npm run migrate
pm2 reload myapp

For automated CI/CD, use a deploy script that is triggered by a webhook or called from a CI pipeline (GitHub Actions, Azure DevOps). Ensure the service account running the Node.js process has read/write access to the application directory and write access to the log directory.

Handling Crashes and Restarts

PM2 automatically restarts a process when it crashes. You can control restart behaviour:

# Limit total restarts (prevents infinite restart loops)
pm2 start app.js --max-restarts 10

# Set minimum uptime before a restart is considered successful
pm2 start app.js --min-uptime 3000     # 3 seconds in ms

# Set restart delay
pm2 start app.js --restart-delay 4000  # 4 seconds

# View restart count and status
pm2 list
pm2 show myapp | findstr "restarts"

# Reset restart counter
pm2 reset myapp

Node.js Performance on Windows

Windows Server 2022 runs Node.js well, but a few settings improve performance:

# Increase the default V8 heap size for memory-intensive applications
# Set via environment variable before starting:
set NODE_OPTIONS=--max-old-space-size=4096

# Or in ecosystem.config.js:
# node_args: '--max-old-space-size=4096'

# Enable HTTP keep-alive (already default in Node.js 19+, but explicit for older):
const http = require('http');
const server = http.createServer(app);
server.keepAliveTimeout = 65000;      // must be > IIS/ARR keep-alive timeout
server.headersTimeout = 66000;

# On multi-core servers, always use PM2 cluster mode:
pm2 start app.js -i max

Logging with PM2

PM2 writes stdout and stderr to log files. Rotate logs to prevent disk fill:

# Install PM2 log rotation module
pm2 install pm2-logrotate

# Configure rotation
pm2 set pm2-logrotate:max_size 50M
pm2 set pm2-logrotate:retain 10
pm2 set pm2-logrotate:compress true
pm2 set pm2-logrotate:dateFormat YYYY-MM-DD_HH-mm-ss
pm2 set pm2-logrotate:rotateInterval "0 0 * * *"   # Daily at midnight

# View current PM2 log rotate settings
pm2 get pm2-logrotate

# Flush current log files manually
pm2 flush myapp

# View logs in real time
pm2 logs myapp --timestamp

PM2’s log files are stored under %USERPROFILE%.pm2logs by default (for example, C:UsersAdministrator.pm2logsmyapp-out.log). You can override these paths in ecosystem.config.js using the out_file and error_file keys to point to a centralized log directory.

With Node.js, PM2, and IIS ARR configured as described here, you have a production-grade deployment on Windows Server 2022: automatic restarts on crash, cluster-mode load balancing across CPU cores, SSL termination at IIS, and persistent logging with rotation. This stack handles thousands of concurrent connections reliably on modern Windows Server hardware.