Introduction
After years of deploying Node.js applications on Linux servers, I recently faced an interesting challenge: building a truly production-ready infrastructure on Windows 11. The goal? Auto-start capabilities, zero downtime, and enterprise-grade reliabilityβall without relying on WSL (Windows Subsystem for Linux).
In this comprehensive guide, I'll walk you through the exact setup I built for a full-stack photography portfolio platform serving thousands of requests daily. You'll learn how to configure nginx for Windows, manage Node.js processes with PM2, and create Windows Services that survive reboots.
Why Windows for Production?
Before diving in, you might ask: "Why Windows instead of Linux?" Here's why this matters:
The Architecture
Here's what we'll build:
Internet
β
HTTPS Reverse Proxy (Tailscale/Cloudflare)
β
nginx Windows Service (Port 8080)
βββ /api/* β Main Backend (Port 3000)
βββ /api/v1/* β Secondary API (Port 3001)
β
PM2 Process Manager
βββ main-backend (1 instance)
βββ secondary-api (2 instances, cluster mode)
β
PostgreSQL Database
Key Features:
Prerequisites
Before starting, ensure you have:
Step 1: Installing nginx for Windows
Unlike Linux, nginx on Windows requires a different approach. Here's how to set it up:
Download and Extract nginx
# Download nginx for Windows
$nginxUrl = "https://nginx.org/download/nginx-1.24.0.zip"
Invoke-WebRequest -Uri $nginxUrl -OutFile "C:\nginx-1.24.0.zip"Extract to C:\nginx
Expand-Archive -Path "C:\nginx-1.24.0.zip" -DestinationPath "C:\"
Rename-Item -Path "C:\nginx-1.24.0" -NewName "nginx"
Configure nginx
Create your production configuration at C:\nginx\conf\nginx.conf:
worker_processes auto;events { worker_connections 1024; }
http { include mime.types; default_type application/octet-stream;
# Performance settings sendfile on; tcp_nopush on; tcp_nodelay on; keepalive_timeout 65; keepalive_requests 100;
# Upstream servers upstream main_api { server 127.0.0.1:3000; keepalive 32; }
upstream secondary_api { server 127.0.0.1:3001; server 127.0.0.1:3001; keepalive 32; }
server { listen 8080; server_name _;
# Security headers add_header X-Frame-Options "SAMEORIGIN" always; add_header X-Content-Type-Options "nosniff" always; add_header X-XSS-Protection "1; mode=block" always;
# Main API location /api/ { proxy_pass http://main_api/api/; proxy_http_version 1.1; proxy_set_header Connection ""; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; }
# Secondary API location /api/v1/ { proxy_pass http://secondary_api/api/v1/; proxy_http_version 1.1; proxy_set_header Connection ""; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } } }
Key Configuration Notes:
keepalive 32: Maintains persistent connections to backendsproxy_set_header Connection "": Essential for HTTP/1.1 keepalivelisten 8080: Avoid port 80 (often blocked by Windows HTTP Service)Test nginx
cd C:\nginx
.\nginx.exe -t # Test configuration
.\nginx.exe # Start nginx
Visit http://localhost:8080 to verify it's running.
Step 2: Setting Up PM2 for Windows
PM2 is the de facto process manager for Node.js applications. Here's how to configure it for production on Windows:
Install PM2 Globally
npm install -g pm2
Create Ecosystem Configuration
For each application, create an ecosystem.config.js:
module.exports = {
apps: [{
name: 'main-backend',
script: './src/server.js',
instances: 1,
exec_mode: 'cluster',
env: {
NODE_ENV: 'production',
PORT: 3000
},
max_memory_restart: '500M',
error_file: './logs/pm2-error.log',
out_file: './logs/pm2-out.log',
time: true
}]
}
Start Your Applications
cd C:\Projects\your-backend
pm2 start ecosystem.config.jsSave PM2 state
pm2 saveCheck status
pm2 status
Pro Tip: Use cluster mode for CPU-bound applications to leverage multiple cores:
{
instances: 2, // Or 'max' for all CPU cores
exec_mode: 'cluster'
}
Step 3: Converting to Windows Services
This is where the magic happens. We'll use WinSW (Windows Service Wrapper) to create true Windows Services.
Download WinSW
$winswUrl = "https://github.com/winsw/winsw/releases/download/v3.0.0-alpha.11/WinSW-x64.exe"
Invoke-WebRequest -Uri $winswUrl -OutFile "C:\nginx\WinSW.exe"
Create nginx Service
Create C:\nginx\nginx-service.xml:
<service>
<id>nginx</id>
<name>nginx</name>
<description>nginx reverse proxy server</description>
<executable>C:\nginx\nginx.exe</executable>
<startmode>Automatic</startmode>
<logpath>C:\nginx\logs</logpath>
<log mode="roll-by-size">
<sizeThreshold>10240</sizeThreshold>
<keepFiles>8</keepFiles>
</log>
<onfailure action="restart" delay="10 sec"/>
<onfailure action="restart" delay="20 sec"/>
</service>
Install the service:
cd C:\nginx
.\WinSW.exe install nginx-service.xml
Start-Service nginx
Create PM2 Service
First, create a startup script C:\nginx\pm2-start.bat:
@echo off cd /d C:\Projects\main-backend call pm2 start ecosystem.config.js timeout /t 5 /nobreak > nulcd /d C:\Projects\secondary-backend call pm2 start ecosystem.config.js timeout /t 5 /nobreak > nul
call pm2 save --force
Then create C:\nginx\pm2-service.xml:
<service>
<id>pm2</id>
<name>PM2</name>
<description>PM2 Process Manager for Node.js</description>
<executable>C:\nginx\pm2-start.bat</executable>
<startmode>Automatic</startmode>
<logpath>C:\nginx\logs</logpath>
</service>
Install PM2 service:
Copy-Item "C:\nginx\WinSW.exe" "C:\nginx\WinSW-PM2.exe"
.\WinSW-PM2.exe install pm2-service.xml
Start-Service PM2
Step 4: CORS Configuration for Authenticated Requests
One critical lesson I learned: CORS behaves differently with authenticated requests. When using JWT tokens or OAuth, you must explicitly allow the Authorization header:
// Backend CORS configuration app.use(cors({ origin: [ 'https://yoursite.com', 'http://localhost:4200' ], methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'], allowedHeaders: ['Content-Type', 'x-api-key', 'Authorization'], // β Critical! credentials: true }));
// Also enable trust proxy for nginx app.set('trust proxy', 1);
Without this, OPTIONS preflight requests will succeed (204 No Content), but the actual authenticated requests will fail silently.
Step 5: Public Access Options
For secure HTTPS access, you have several options:
Option 1: Tailscale Funnel (Easiest)
# Install Tailscale from https://tailscale.com/downloadEnable Funnel
tailscale funnel --bg --https=443 8080Check status
tailscale funnel status
Your application is now accessible at https://your-machine.your-tailnet.ts.net!
Option 2: Cloudflare Tunnel
# Install cloudflaredFollow https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/
cloudflared tunnel --url http://localhost:8080
Option 3: Traditional Port Forwarding
Configure your router to forward port 443 to your machine's port 8080, then use Let's Encrypt with Certbot for SSL.
Recommended Resources
π Essential Books for Windows Server Administration
Want to master Windows Server beyond this guide? These books are invaluable:
Mastering Windows Server 2022 by Jordan Krause
Windows Server Automation with PowerShell Cookbook
Mastering Active Directory (3rd Edition)
Windows Internals, Part 1 by Mark Russinovich
π₯οΈ Hardware Recommendations for Home Lab Servers
If you're serious about running production workloads at home, consider these mini PCs. They're perfect for the setup we just built:
Budget Option ($160-200)
Beelink EQ14 Mini PCMid-Range Powerhouse ($400-500)
Beelink SER5 Mini PCAdvanced/Prosumer ($600-800)
Minisforum MS-01 WorkstationIntel NUC Option ($500-700)
Intel NUC 12 Pro Mini PCπ‘ Why These Mini PCs?
All of these can run the exact infrastructure we built in this guide:
Power Cost Comparison: A Beelink SER5 running 24/7 costs ~$2-3/month in electricity vs $15-20/month for a traditional server.
---
Production Checklist
Before going live, verify:
Get-Service nginx, PM2pm2 statusnetstat -ano | findstr :8080curl http://localhost:3000/healthTroubleshooting Common Issues
Issue: Services don't start after reboot
Solution:
Get-Service nginx, PM2
Start-Service nginx
C:\nginx\pm2-start.bat
Issue: 502 Bad Gateway
Diagnosis:
pm2 status # Check if backends are running
Get-Content C:\nginx\logs\error.log -Tail 20
Solution: Usually means backends aren't running or listening on wrong ports.
Issue: CORS errors with authentication
Add Authorization to allowedHeaders in your CORS configuration (see Step 4).
Issue: Multiple nginx processes
This is normal! nginx spawns worker processes based on CPU cores. Check with:
Get-Process nginx | Measure-Object
Performance Considerations
With this setup on a modern Windows machine, typical resource usage:
Response times average <50ms for API requests, with nginx efficiently load balancing across PM2 instances.
Security Best Practices
1. Never expose raw backends: Always use nginx as a reverse proxy 2. Enable rate limiting:
const rateLimit = require('express-rate-limit');
app.use(rateLimit({
windowMs: 15 60 1000,
max: 100
}));
Monitoring and Maintenance
Daily Health Checks
# Quick status
pm2 status
Get-Service nginx, PM2
Weekly Maintenance
# Review error logs
pm2 logs --lines 100Check disk space
Get-PSDrive CUpdate dependencies
cd C:\Projects\your-backend
npm outdated
Monthly Tasks
Lessons Learned
After running this setup in production for several months:
1. All-Windows is simpler than WSL hybrid: Fewer networking issues, easier troubleshooting 2. WinSW is rock-solid: Never had a service failure 3. PM2 cluster mode is essential: Automatic load balancing and zero-downtime restarts 4. nginx on Windows performs well: Don't let myths discourage you 5. CORS authentication gotchas are real: Always test with auth headers
Conclusion
Building production-grade Node.js infrastructure on Windows 11 is not only possibleβit's practical and performant. By using nginx, PM2, and Windows Services, you can achieve:
This setup can power production applications serving thousands of requests daily with 99.9%+ uptime.
Whether you're running this on a dedicated mini PC or your development machine, you now have enterprise-grade infrastructure that rivals Linux deployments.
Additional Resources
---
About the Author: Ricardo Gil is a Full Stack Software Engineer with 6+ years of experience specializing in C#/.NET Core, Angular, and cloud platforms. He builds scalable web applications and shares DevOps insights at gilricardo.com.
Disclaimer: This post contains Amazon affiliate links. As an Amazon Associate, I earn from qualifying purchases at no additional cost to you. I only recommend products I genuinely use or would use in my own infrastructure.
