Getting an Express app running in production reliably involves more than just node server.js, process management, environment configuration, and often a reverse proxy. This lesson covers the essential pieces.
Why You Need a Process Manager
Running node server.js directly means the process dies, and stays dead, if it crashes, and there's no automatic restart, log rotation, or clustering. A process manager like PM2 keeps your app running, restarts it automatically on crashes, and can run multiple instances across CPU cores.
Most hosting platforms (Render, Railway, Heroku, AWS Elastic Beanstalk) handle some of this for you automatically; when managing your own server (a VPS), a process manager is essential.
npm install -g pm2
pm2 start server.js --name my-app -i max
pm2 save
pm2 startup
-i max runs one instance per available CPU core (clustering); pm2 startup configures PM2 to restart your app automatically after a server reboot.
Environment-Aware Configuration
NODE_ENV=production node server.js
// in code
if (process.env.NODE_ENV === 'production') {
app.set('trust proxy', 1);
}
Set NODE_ENV=production in production, some libraries (like Express itself) enable optimizations based on it.
Never run with development-only middleware (verbose error pages, permissive CORS) in production.
Store all production secrets as real environment variables, not a committed .env file.
Ensure app.set('trust proxy', 1) is set when running behind any reverse proxy or load balancer.
Deployment Cheatsheet
Common deployment tools and what problem each solves.
A Dockerfile packages your app and its exact dependency versions into a reproducible image, eliminating "works on my machine" deployment issues.
# Dockerfile
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]
npm ci --omit=dev installs exact dependency versions from the lockfile, skipping devDependencies for a smaller production image.
Using Nginx as a Reverse Proxy
Running Express directly on port 80/443 is uncommon in production. A reverse proxy like Nginx typically terminates TLS, handles static file caching, and forwards requests to your Node process running on an internal port.
Remember to set app.set('trust proxy', 1) in Express so req.ip and secure cookies work correctly behind this proxy.
Common Mistakes
Running node server.js directly in production with no process manager and no automatic restart on crash.
Forgetting trust proxy when deployed behind Nginx or a load balancer, breaking req.ip and secure cookies.
Committing a .env file with real production secrets instead of configuring them on the hosting platform.
Not setting NODE_ENV=production, missing framework-level optimizations and accidentally leaving dev-only behavior enabled.
Key Takeaways
A process manager (PM2) or managed platform keeps your app running and restarts it automatically on crashes.
Docker packages your app into a reproducible, consistent runtime environment.
A reverse proxy (Nginx) typically handles TLS termination and forwards traffic to your Node process.
Always set trust proxy and NODE_ENV=production appropriately for a proxied production deployment.
Pro Tip
Before deploying to a real server, test your production Docker image locally with the exact same environment variables and NODE_ENV=production set, catching configuration mistakes before they reach a live environment.
You now understand how to deploy an Express app. Next, review a full set of Express.js Best Practices.