Deploying a Production ERP: EC2, PM2, and Nginx, the Un-Glamorous Way
Boring infra matched to actual scale - how a full-stack ERP runs on a single EC2 with PM2 and Nginx.
No Kubernetes, no fancy CI/CD pipeline with a dozen environments - just a single EC2 instance, PM2 keeping the process alive, and Nginx sitting in front of it. For a real ERP system with real ops usage, this combination is unglamorous, well-understood, and it works. Here's what actually goes into making it solid.
The Problem
The ERP needed to go from "runs on my machine" to "runs reliably for people using it to manage actual operations." That means:
- The Node process needs to survive crashes, restart automatically, and not lose logs when it does
- HTTPS, proper request routing, and reasonable request handling need to sit in front of the app, not be bolted on after
- Deploys need to happen without long downtime windows every time we ship a change
- None of this can depend on someone SSHing in and babysitting a terminal session
The Approach
EC2 as the base. A single instance sized appropriately for the traffic the ERP actually sees - no point over-provisioning for load that isn't there. Security groups locked down to only the ports actually needed (80/443 in from the world, SSH restricted).
PM2 for process management. This is the piece that turns "node server.js in a terminal" into something production-grade:
- Auto-restart on crash, so a single unhandled exception doesn't take down the whole app for hours
pm2 startup+pm2 saveso the app comes back up automatically if the instance itself reboots- Centralized log management through PM2's log rotation, instead of stdout scrolling off into nothing
- Zero-downtime reloads (
pm2 reload) for deploys that don't need a full restart, so updates don't cost users an outage window
Nginx as the front door. Nginx handles what the Node app shouldn't have to think about:
- TLS termination, so the app itself only ever talks plain HTTP internally
- Reverse proxying to the PM2-managed Node process on its internal port
- Serving static assets directly where it makes sense, instead of making Node do that work
- Setting the right proxy headers (
X-Forwarded-For,X-Forwarded-Proto) so the app can still tell what the original request looked like
The Result
A deploy process that's boring in the good way - push new code, pm2 reload (or a fresh deploy + reload), and the app comes back with no meaningful downtime. If the instance itself has to reboot for any reason, PM2 brings the app back up without anyone needing to log in and start it manually.
Is this the most sophisticated infrastructure setup? No - there's no auto-scaling, no blue-green deploys, no container orchestration. But for a single-instance production ERP system with a known, bounded user base, matching the infrastructure complexity to the actual problem is the right call. Reaching for Kubernetes here would have added a lot of operational overhead for zero real benefit at this scale.
The lesson I keep relearning: infrastructure decisions should match the actual traffic and team size you have, not the one you might have someday. EC2 + PM2 + Nginx isn't exciting, but "boring and reliable" is exactly what an ops-critical system needs.