📑 Table of Contents
🔍 Overview
This guide provides step-by-step instructions for deploying MinusNow ITSM on a Windows Server on-premises environment. The application uses Node.js runtime with PostgreSQL database and IIS as reverse proxy.
Architecture Diagram
┌─────────────────────────────────────────────────────────────────┐
│ Load Balancer / DNS │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ IIS with URL Rewrite & ARR │
│ Port 80/443 (SSL) │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ MinusNow ITSM Application │
│ Node.js Windows Service (Port 5000) │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ PostgreSQL Database │
│ Port 5432 │
└─────────────────────────────────────────────────────────────────┘
💻 Server Requirements
Hardware Requirements
| Component | Minimum | Recommended | Enterprise |
|---|---|---|---|
| CPU | 8 vCPU (x86_64) | 16 vCPU (Xeon/EPYC) | 32+ vCPU (Xeon/EPYC) |
| RAM | 32 GB DDR4 | 64 GB DDR4 | 128+ GB DDR4/DDR5 |
| Storage | 500 GB NVMe SSD | 1 TB NVMe SSD | 2+ TB NVMe SSD RAID |
| Network | 1 Gbps | 10 Gbps | 25 Gbps+ |
Software Requirements
| Component | Version | Purpose |
|---|---|---|
| OS | Windows Server 2022 (Minimum 2019) | Operating System |
| Node.js | 22.x LTS (Minimum 20.x LTS) | Application Runtime |
| PostgreSQL | 16+ (Minimum 15) | Database Server |
| IIS | 10.0+ (Windows Server 2022) | Web Server / Reverse Proxy |
| URL Rewrite | 2.1+ | IIS Module |
| ARR | 3.0+ | Application Request Routing |
| NSSM | 2.24+ Latest | Service Wrapper |
| .NET Runtime | 8.0+ | IIS Dependencies |
Network Requirements
| Port | Protocol | Direction | Purpose |
|---|---|---|---|
| 3389 | TCP | Inbound | RDP Access |
| 80 | TCP | Inbound | HTTP (Redirect to HTTPS) |
| 443 | TCP | Inbound | HTTPS |
| 5000 | TCP | Internal | Application |
| 5432 | TCP | Internal | PostgreSQL |
| 25/587 | TCP | Outbound | SMTP Email |
📋 Pre-Installation Checklist
Before proceeding, ensure you have:
- Administrator access to the server
- Static IP address assigned
- DNS A record pointing to server IP (e.g.,
itsm.yourdomain.com) - SMTP server credentials for email notifications
- SSL certificate (.pfx format or use Let's Encrypt)
- Windows Firewall rules configured
- Backup storage location identified
🔧 Server Preparation
1Install Windows Features
Open PowerShell as Administrator:
# Install IIS and required features
Install-WindowsFeature -Name Web-Server -IncludeManagementTools
Install-WindowsFeature -Name Web-WebSockets
Install-WindowsFeature -Name Web-Asp-Net45
Install-WindowsFeature -Name Web-Http-Redirect
# Verify installation
Get-WindowsFeature | Where-Object {$_.Installed -eq $true} | Format-Table Name,Installed
2Configure Windows Firewall
# Allow HTTP
New-NetFirewallRule -DisplayName "HTTP" -Direction Inbound -Protocol TCP -LocalPort 80 -Action Allow
# Allow HTTPS
New-NetFirewallRule -DisplayName "HTTPS" -Direction Inbound -Protocol TCP -LocalPort 443 -Action Allow
# Allow PostgreSQL (internal only)
New-NetFirewallRule -DisplayName "PostgreSQL" -Direction Inbound -Protocol TCP -LocalPort 5432 -Action Allow -RemoteAddress LocalSubnet
# Verify rules
Get-NetFirewallRule | Where-Object {$_.Enabled -eq $true -and $_.Direction -eq 'Inbound'} | Format-Table DisplayName,Profile,Action
3Create Directory Structure
# Create application directories
New-Item -ItemType Directory -Path "C:\MinusNow\ITSM" -Force
New-Item -ItemType Directory -Path "C:\MinusNow\ITSM\data" -Force
New-Item -ItemType Directory -Path "C:\MinusNow\ITSM\data\support-tickets" -Force
New-Item -ItemType Directory -Path "C:\MinusNow\ITSM\data\ticket-responses" -Force
New-Item -ItemType Directory -Path "C:\MinusNow\ITSM\logs" -Force
New-Item -ItemType Directory -Path "C:\MinusNow\ITSM\backups" -Force
New-Item -ItemType Directory -Path "C:\MinusNow\Tools" -Force
# Set permissions
$acl = Get-Acl "C:\MinusNow"
$rule = New-Object System.Security.AccessControl.FileSystemAccessRule("IIS_IUSRS", "FullControl", "ContainerInherit,ObjectInherit", "None", "Allow")
$acl.SetAccessRule($rule)
Set-Acl "C:\MinusNow" $acl
4Install IIS URL Rewrite & ARR
# Download and install URL Rewrite
$rewriteUrl = "https://download.microsoft.com/download/1/2/8/128E2E22-C1B9-44A4-BE2A-5859ED1D4592/rewrite_amd64_en-US.msi"
Invoke-WebRequest -Uri $rewriteUrl -OutFile "C:\MinusNow\Tools\rewrite.msi"
Start-Process msiexec.exe -ArgumentList "/i C:\MinusNow\Tools\rewrite.msi /quiet" -Wait
# Download and install ARR
$arrUrl = "https://download.microsoft.com/download/E/9/8/E9849D6A-020E-47E4-9FD0-A023E99B54EB/requestRouter_amd64.msi"
Invoke-WebRequest -Uri $arrUrl -OutFile "C:\MinusNow\Tools\arr.msi"
Start-Process msiexec.exe -ArgumentList "/i C:\MinusNow\Tools\arr.msi /quiet" -Wait
# Enable ARR proxy
Set-WebConfigurationProperty -PSPath 'MACHINE/WEBROOT/APPHOST' -Filter "system.webServer/proxy" -Name "enabled" -Value "True"
🗄️ Database Installation
1Download and Install PostgreSQL
# Download PostgreSQL installer
$pgUrl = "https://get.enterprisedb.com/postgresql/postgresql-15.5-1-windows-x64.exe"
Invoke-WebRequest -Uri $pgUrl -OutFile "C:\MinusNow\Tools\postgresql-installer.exe"
# Run installer (interactive GUI)
Start-Process "C:\MinusNow\Tools\postgresql-installer.exe" -Wait
# Or silent install
Start-Process "C:\MinusNow\Tools\postgresql-installer.exe" -ArgumentList @(
"--mode unattended",
"--unattendedmodeui minimal",
"--superpassword YourSuperSecurePassword",
"--serverport 5432"
) -Wait
2Configure PostgreSQL
# Add PostgreSQL to PATH (if not already)
$env:Path += ";C:\Program Files\PostgreSQL\15\bin"
[Environment]::SetEnvironmentVariable("Path", $env:Path, [EnvironmentVariableTarget]::Machine)
# Create database user and database
$psqlCommands = @"
CREATE USER minusnow WITH PASSWORD 'your_secure_password_here';
CREATE DATABASE minusnow_itsm OWNER minusnow;
GRANT ALL PRIVILEGES ON DATABASE minusnow_itsm TO minusnow;
\c minusnow_itsm
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
"@
$psqlCommands | & "C:\Program Files\PostgreSQL\15\bin\psql.exe" -U postgres
3Configure pg_hba.conf
# Edit C:\Program Files\PostgreSQL\15\data\pg_hba.conf
# Add these lines:
# TYPE DATABASE USER ADDRESS METHOD
# host minusnow_itsm minusnow 127.0.0.1/32 md5
# host minusnow_itsm minusnow ::1/128 md5
# Restart PostgreSQL service
Restart-Service postgresql-x64-15
📦 Application Installation
1Install Node.js
# Download Node.js 20 LTS
$nodeUrl = "https://nodejs.org/dist/v20.11.0/node-v20.11.0-x64.msi"
Invoke-WebRequest -Uri $nodeUrl -OutFile "C:\MinusNow\Tools\node.msi"
Start-Process msiexec.exe -ArgumentList "/i C:\MinusNow\Tools\node.msi /quiet" -Wait
# Verify installation (open new PowerShell)
node --version
npm --version
2Download Application
# Option 1: Download from portal
# Navigate to https://www.minusnow.com/site/downloads
# Download minusnow-itsm-artifact.zip
# Extract to application folder
Expand-Archive -Path "C:\Users\Administrator\Downloads\minusnow-itsm-artifact.zip" -DestinationPath "C:\MinusNow\ITSM" -Force
# Option 2: Use installation script
Invoke-WebRequest -Uri "https://www.minusnow.com/site/download/install-windows.ps1" -OutFile "C:\MinusNow\Tools\install.ps1"
& "C:\MinusNow\Tools\install.ps1"
3Configure Environment Variables
# Create .env file
$envContent = @"
# Application
NODE_ENV=production
PORT=5000
HOST=0.0.0.0
# Database
DATABASE_URL=postgresql://minusnow:your_password@localhost:5432/minusnow_itsm
# Session
SESSION_SECRET=$([guid]::NewGuid().ToString('N') + [guid]::NewGuid().ToString('N'))
# Email (SMTP)
SMTP_HOST=smtp.yourdomain.com
SMTP_PORT=587
SMTP_USER=noreply@yourdomain.com
SMTP_PASS=your_smtp_password
# Application URL
APP_URL=https://itsm.yourdomain.com
"@
$envContent | Out-File -FilePath "C:\MinusNow\ITSM\.env" -Encoding UTF8
4Install Dependencies and Build
cd C:\MinusNow\ITSM
# Install dependencies
npm install
# Run database migrations
npm run db:push
# Build application (if from source)
npm run build
🌐 IIS Configuration
1Create web.config
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.webServer>
<rewrite>
<rules>
<!-- Redirect HTTP to HTTPS -->
<rule name="HTTP to HTTPS" stopProcessing="true">
<match url="(.*)" />
<conditions>
<add input="{HTTPS}" pattern="^OFF$" />
</conditions>
<action type="Redirect" url="https://{HTTP_HOST}/{R:1}" redirectType="Permanent" />
</rule>
<!-- Reverse proxy to Node.js with X-Forwarded headers -->
<rule name="ReverseProxy" stopProcessing="true">
<match url="(.*)" />
<serverVariables>
<set name="HTTP_X_FORWARDED_FOR" value="{REMOTE_ADDR}" />
<set name="HTTP_X_FORWARDED_PROTO" value="{HTTPS}" />
<set name="HTTP_X_FORWARDED_HOST" value="{HTTP_HOST}" />
<set name="HTTP_X_REAL_IP" value="{REMOTE_ADDR}" />
</serverVariables>
<action type="Rewrite" url="http://localhost:5000/{R:1}" />
</rule>
</rules>
<!-- Allow server variables to be set -->
<allowedServerVariables>
<add name="HTTP_X_FORWARDED_FOR" />
<add name="HTTP_X_FORWARDED_PROTO" />
<add name="HTTP_X_FORWARDED_HOST" />
<add name="HTTP_X_REAL_IP" />
</allowedServerVariables>
</rewrite>
<!-- WebSocket support -->
<webSocket enabled="true" />
<!-- Proxy settings -->
<proxy enabled="true" preserveHostHeader="true" reverseRewriteHostInResponseHeaders="true" />
<!-- Security headers -->
<httpProtocol>
<customHeaders>
<add name="X-Content-Type-Options" value="nosniff" />
<add name="X-Frame-Options" value="SAMEORIGIN" />
<add name="X-XSS-Protection" value="1; mode=block" />
<add name="Strict-Transport-Security" value="max-age=31536000; includeSubDomains" />
</customHeaders>
</httpProtocol>
</system.webServer>
</configuration>
2Configure IIS Site
# Import IIS module
Import-Module WebAdministration
# Create application pool
New-WebAppPool -Name "MinusNowITSM"
Set-ItemProperty "IIS:\AppPools\MinusNowITSM" -Name "managedRuntimeVersion" -Value ""
Set-ItemProperty "IIS:\AppPools\MinusNowITSM" -Name "startMode" -Value "AlwaysRunning"
Set-ItemProperty "IIS:\AppPools\MinusNowITSM" -Name "processModel.idleTimeout" -Value "00:00:00"
# Remove default website
Remove-Website -Name "Default Web Site" -ErrorAction SilentlyContinue
# Create website
New-Website -Name "MinusNowITSM" `
-PhysicalPath "C:\MinusNow\ITSM" `
-ApplicationPool "MinusNowITSM" `
-Port 80
# Add HTTPS binding (after SSL certificate is configured)
New-WebBinding -Name "MinusNowITSM" -Protocol "https" -Port 443 -HostHeader "itsm.yourdomain.com"
# Enable ARR proxy
Set-WebConfigurationProperty -PSPath 'MACHINE/WEBROOT/APPHOST' -Filter "system.webServer/proxy" -Name "enabled" -Value "True"
Set-WebConfigurationProperty -PSPath 'MACHINE/WEBROOT/APPHOST' -Filter "system.webServer/proxy" -Name "preserveHostHeader" -Value "True"
3Configure X-Forwarded Headers (Server Variables)
When running behind a reverse proxy, X-Forwarded headers pass the original client information to your application.
# Allow server variables at the server level (run once)
Add-WebConfigurationProperty -PSPath 'MACHINE/WEBROOT/APPHOST' `
-Filter "system.webServer/rewrite/allowedServerVariables" `
-Name "." `
-Value @{name="HTTP_X_FORWARDED_FOR"}
Add-WebConfigurationProperty -PSPath 'MACHINE/WEBROOT/APPHOST' `
-Filter "system.webServer/rewrite/allowedServerVariables" `
-Name "." `
-Value @{name="HTTP_X_FORWARDED_PROTO"}
Add-WebConfigurationProperty -PSPath 'MACHINE/WEBROOT/APPHOST' `
-Filter "system.webServer/rewrite/allowedServerVariables" `
-Name "." `
-Value @{name="HTTP_X_FORWARDED_HOST"}
Add-WebConfigurationProperty -PSPath 'MACHINE/WEBROOT/APPHOST' `
-Filter "system.webServer/rewrite/allowedServerVariables" `
-Name "." `
-Value @{name="HTTP_X_REAL_IP"}
Why X-Forwarded Headers?
- X-Forwarded-For: Original client IP address
- X-Forwarded-Proto: Original protocol (http/https)
- X-Forwarded-Host: Original host header
- X-Real-IP: Alternative to X-Forwarded-For for single proxy
🔒 SSL/TLS Configuration
Option A: Import PFX Certificate
# Import certificate
$certPath = "C:\MinusNow\Certs\certificate.pfx"
$certPassword = ConvertTo-SecureString -String "YourCertPassword" -AsPlainText -Force
$cert = Import-PfxCertificate -FilePath $certPath -CertStoreLocation "Cert:\LocalMachine\My" -Password $certPassword
# Bind to IIS
$thumbprint = $cert.Thumbprint
$binding = Get-WebBinding -Name "MinusNowITSM" -Protocol "https"
$binding.AddSslCertificate($thumbprint, "My")
Option B: Using Let's Encrypt (win-acme)
# Download win-acme
$wacsUrl = "https://github.com/win-acme/win-acme/releases/download/v2.2.9/win-acme.v2.2.9.1701.x64.pluggable.zip"
Invoke-WebRequest -Uri $wacsUrl -OutFile "C:\MinusNow\Tools\win-acme.zip"
Expand-Archive -Path "C:\MinusNow\Tools\win-acme.zip" -DestinationPath "C:\MinusNow\Tools\win-acme"
# Run win-acme (interactive)
cd C:\MinusNow\Tools\win-acme
.\wacs.exe
# Or automated mode
.\wacs.exe --target iis --siteid 1 --installation iis --accepttos --emailaddress admin@yourdomain.com
⚙️ Windows Service Setup (NSSM)
1Install NSSM
# Download NSSM
$nssmUrl = "https://nssm.cc/release/nssm-2.24.zip"
Invoke-WebRequest -Uri $nssmUrl -OutFile "C:\MinusNow\Tools\nssm.zip"
Expand-Archive -Path "C:\MinusNow\Tools\nssm.zip" -DestinationPath "C:\MinusNow\Tools"
Copy-Item "C:\MinusNow\Tools\nssm-2.24\win64\nssm.exe" "C:\Windows\System32\"
2Create Windows Service
# Install service
nssm install MinusNowITSM "C:\Program Files\nodejs\node.exe"
nssm set MinusNowITSM AppParameters "dist\index.js"
nssm set MinusNowITSM AppDirectory "C:\MinusNow\ITSM"
nssm set MinusNowITSM AppEnvironmentExtra "NODE_ENV=production"
nssm set MinusNowITSM DisplayName "MinusNow ITSM Application"
nssm set MinusNowITSM Description "MinusNow IT Service Management Platform"
nssm set MinusNowITSM Start SERVICE_AUTO_START
nssm set MinusNowITSM AppStdout "C:\MinusNow\ITSM\logs\stdout.log"
nssm set MinusNowITSM AppStderr "C:\MinusNow\ITSM\logs\stderr.log"
nssm set MinusNowITSM AppRotateFiles 1
nssm set MinusNowITSM AppRotateBytes 10485760
# Start service
Start-Service MinusNowITSM
# Verify service
Get-Service MinusNowITSM
✅ Verification & Testing
Check Services Status
# Check all services
Get-Service MinusNowITSM, postgresql-x64-15, W3SVC | Format-Table Name, Status, StartType
# Check ports
Get-NetTCPConnection -LocalPort 5000,5432,80,443 -State Listen | Format-Table LocalPort, State
Test Application
# Test local access
Invoke-WebRequest -Uri "http://localhost:5000/health" -UseBasicParsing
# Test through IIS
Invoke-WebRequest -Uri "https://itsm.yourdomain.com/" -UseBasicParsing
# Check application logs
Get-Content "C:\MinusNow\ITSM\logs\stdout.log" -Tail 50
✅ Browser Testing Checklist:
- Open
https://itsm.yourdomain.comin browser - Verify SSL certificate (padlock icon)
- Test login functionality
- Test all major features
- Check browser console for errors
🎛️ Service Management
Application Service
# Start
Start-Service MinusNowITSM
# Stop
Stop-Service MinusNowITSM
# Restart
Restart-Service MinusNowITSM
# Status
Get-Service MinusNowITSM
# View logs
Get-Content "C:\MinusNow\ITSM\logs\stdout.log" -Tail 100 -Wait
IIS & Database
# IIS
Start-Service W3SVC
Stop-Service W3SVC
iisreset /restart
# PostgreSQL
Start-Service postgresql-x64-15
Stop-Service postgresql-x64-15
Restart-Service postgresql-x64-15
# All services
Get-Service MinusNowITSM, postgresql-x64-15, W3SVC
🔧 Troubleshooting
Application Won't Start
# Check service status
Get-Service MinusNowITSM
# Check logs
Get-Content "C:\MinusNow\ITSM\logs\stderr.log" -Tail 50
# Check Node.js
node --version
# Test manually
cd C:\MinusNow\ITSM
node dist\index.js
IIS 502 Bad Gateway
# Check if app is running
Get-Service MinusNowITSM
# Test local connection
Invoke-WebRequest -Uri "http://localhost:5000/" -UseBasicParsing
# Check IIS logs
Get-Content "C:\inetpub\logs\LogFiles\W3SVC1\*.log" -Tail 50
# Check ARR is enabled
Get-WebConfigurationProperty -PSPath 'MACHINE/WEBROOT/APPHOST' -Filter "system.webServer/proxy" -Name "enabled"
Database Connection Failed
# Check PostgreSQL service
Get-Service postgresql-x64-15
# Test connection
& "C:\Program Files\PostgreSQL\15\bin\psql.exe" -U minusnow -d minusnow_itsm -h localhost -c "SELECT 1"
# Check pg_hba.conf permissions
Get-Content "C:\Program Files\PostgreSQL\15\data\pg_hba.conf"
SSL Certificate Issues
# List certificates
Get-ChildItem Cert:\LocalMachine\My
# Check IIS bindings
Get-WebBinding -Name "MinusNowITSM"
# Test SSL
Test-NetConnection -ComputerName itsm.yourdomain.com -Port 443
Log Locations
| Log Type | Location |
|---|---|
| Application Output | C:\MinusNow\ITSM\logs\stdout.log |
| Application Errors | C:\MinusNow\ITSM\logs\stderr.log |
| IIS Logs | C:\inetpub\logs\LogFiles\ |
| PostgreSQL | C:\Program Files\PostgreSQL\15\data\log\ |
| Windows Events | Event Viewer → Windows Logs → Application |
💾 Backup & Recovery
Automated Backup Script
# Save as C:\MinusNow\ITSM\scripts\backup.ps1
$BackupDir = "C:\MinusNow\ITSM\backups"
$Date = Get-Date -Format "yyyyMMdd_HHmmss"
$RetentionDays = 30
# Database backup
$env:PGPASSWORD = "your_password"
& "C:\Program Files\PostgreSQL\15\bin\pg_dump.exe" -U minusnow -h localhost minusnow_itsm | Out-File "$BackupDir\db_$Date.sql" -Encoding UTF8
# Compress database backup
Compress-Archive -Path "$BackupDir\db_$Date.sql" -DestinationPath "$BackupDir\db_$Date.zip"
Remove-Item "$BackupDir\db_$Date.sql"
# Data backup
Compress-Archive -Path "C:\MinusNow\ITSM\data" -DestinationPath "$BackupDir\data_$Date.zip"
# Config backup
Compress-Archive -Path "C:\MinusNow\ITSM\.env", "C:\MinusNow\ITSM\web.config" -DestinationPath "$BackupDir\config_$Date.zip"
# Remove old backups
Get-ChildItem $BackupDir -Recurse | Where-Object { $_.LastWriteTime -lt (Get-Date).AddDays(-$RetentionDays) } | Remove-Item
Write-Host "Backup completed: $Date"
Schedule Backup Task
# Create scheduled task
$action = New-ScheduledTaskAction -Execute "PowerShell.exe" -Argument "-File C:\MinusNow\ITSM\scripts\backup.ps1"
$trigger = New-ScheduledTaskTrigger -Daily -At "02:00"
$principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount
Register-ScheduledTask -TaskName "MinusNow ITSM Backup" -Action $action -Trigger $trigger -Principal $principal
Recovery Procedure
# Stop services
Stop-Service MinusNowITSM
# Restore database
& "C:\Program Files\PostgreSQL\15\bin\psql.exe" -U minusnow -h localhost -d minusnow_itsm -f "C:\MinusNow\ITSM\backups\db_YYYYMMDD.sql"
# Restore data
Expand-Archive -Path "C:\MinusNow\ITSM\backups\data_YYYYMMDD.zip" -DestinationPath "C:\MinusNow\ITSM" -Force
# Start services
Start-Service MinusNowITSM